/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */

#include "KeosImage.h"
#include "KeosPixelUtils.h"
#include "KeosException.h"
#include "KeosColor.h"
#include "KeosRectangle.h"

namespace Keos
{

    //=======================================================================
    // CImage implementation
    //=======================================================================

    //-----------------------------------------------------------------------
    CImage::CImage(const TVector2I& Size, TPixelFormat Format) :
    m_Size  (Size),
    m_Format(Format),
    m_Pixels(Size.x * Size.y * GetBytesPerPixel(Format), 0x00)
    {

    }

    //-----------------------------------------------------------------------
    CImage::CImage(const TVector2I& Size, TPixelFormat Format, const uchar* pPixels) :
    m_Size  (Size),
    m_Format(Format),
    m_Pixels(pPixels, pPixels + Size.x * Size.y * GetBytesPerPixel(Format))
    {

    }

    //-----------------------------------------------------------------------
    const TVector2I& CImage::GetSize() const
    {
        return m_Size;
    }

    //-----------------------------------------------------------------------
    TPixelFormat CImage::GetFormat() const
    {
        return m_Format;
    }

    //-----------------------------------------------------------------------
    const uchar* CImage::GetData() const
    {
        return &m_Pixels[0];
    }

    //-----------------------------------------------------------------------
    void CImage::Fill(const CColor& Color)
    {
        SetPixel(0, 0, Color);

        uint Bpp = GetBytesPerPixel(m_Format);
        std::vector<uchar>::iterator Begin = m_Pixels.begin();
        for (std::vector<uchar>::iterator i = Begin + Bpp; i != m_Pixels.end(); i += Bpp)
            std::copy(Begin, Begin + Bpp, i);
    }

    //-----------------------------------------------------------------------
    void CImage::SetPixel(int x, int y, const uchar* pPix)
    {
        std::copy(pPix, pPix + GetBytesPerPixel(m_Format), &m_Pixels[(x + y * m_Size.x) * GetBytesPerPixel(m_Format)]);
    }

    //-----------------------------------------------------------------------
    void CImage::SetPixel(int x, int y, const CColor& Color)
    {
        uchar* pPix = &m_Pixels[(x + y * m_Size.x) * GetBytesPerPixel(m_Format)];

        uchar Components[4] = {Color.GetBlue(), Color.GetGreen(), Color.GetRed(), Color.GetAlpha()};
        ConvertPixel(PXF_A8R8G8B8, Components, m_Format, pPix);
    }

    //-----------------------------------------------------------------------
    void CImage::GetPixel(int x, int y, uchar* pPix) const
    {
        const uchar* pBegin = &m_Pixels[(x + y * m_Size.x) * GetBytesPerPixel(m_Format)];
        std::copy(pBegin, pBegin + GetBytesPerPixel(m_Format), pPix);
    }

    //-----------------------------------------------------------------------
    CColor CImage::GetPixel(int x, int y) const
    {
        uchar Components[4];
        const uchar* pPix = &m_Pixels[(x + y * m_Size.x) * GetBytesPerPixel(m_Format)];
        ConvertPixel(m_Format, pPix, PXF_A8R8G8B8, Components);

        return CColor(Components[2], Components[1], Components[0], Components[3]);
    }

    //-----------------------------------------------------------------------
    void CImage::CopyImage(const CImage& Src)
    {
        // If the dimensions are not the same we make a filtering, otherwise we make a simple copy
        if (m_Size == Src.m_Size)
        {
            // If the sizes are the same we copy simply pixels, otherwise we make a conversion
            if (m_Format == Src.m_Format)
            {
                m_Pixels = Src.m_Pixels;
            }
            else
            {
                // Recovery of the parameters for the copy (pointer on pixels and bpps)
                const uchar* pSrcPix  = &Src.m_Pixels[0];
                uchar*       pDestPix = &m_Pixels[0];
                uint         SrcBpp  = GetBytesPerPixel(Src.m_Format);
                uint         DestBpp = GetBytesPerPixel(m_Format);

                // For each pixels of the image source, conversion / copy in the current image
                for (int i = 0; i < m_Size.x; ++i)
                    for (int j = 0; j < m_Size.y; ++j)
                    {
                        ConvertPixel(Src.m_Format, pSrcPix, m_Format, pDestPix);
                        pSrcPix  += SrcBpp;
                        pDestPix += DestBpp;
                    }
            }
        }
        else
        {
            // Calculation of the step of progress in the image source
            TVector2F Step(static_cast<float>(Src.m_Size.x) / m_Size.x, static_cast<float>(Src.m_Size.y) / m_Size.y);

            // If the formats are the same we copy simply pixels, otherwise we make a conversion
            if (m_Format == Src.m_Format)
            {
                // Copy of the pixels
                uchar Pixel[16];
                for (int i = 0; i < m_Size.x; ++i)
                    for (int j = 0; j < m_Size.y; ++j)
                    {
                        Src.GetPixel(static_cast<int>(i * Step.x), static_cast<int>(j * Step.y), Pixel);
                        SetPixel(i, j, Pixel);
                    }
            }
            else
            {
                // For each pixels of the image source, conversion / copy in the current image
                uchar SrcPix[16], DestPix[16];
                for (int i = 0; i < m_Size.x; ++i)
                    for (int j = 0; j < m_Size.y; ++j)
                    {
                        Src.GetPixel(static_cast<int>(i * Step.x), static_cast<int>(j * Step.y), SrcPix);
                        ConvertPixel(Src.m_Format, SrcPix, m_Format, DestPix);
                        SetPixel(i, j, DestPix);
                    }
            }
        }
    }

    //-----------------------------------------------------------------------
    CImage CImage::SubImage(const CRectangle& Rect) const
    {
        Assert(CRectangle(0, 0, m_Size.x, m_Size.y).Intersects(Rect) == INT_IN);

        CImage Img(TVector2I(Rect.Width(), Rect.Height()), m_Format);

        const uchar* pSrc       = &m_Pixels[(Rect.Left() + Rect.Top() * m_Size.x) * GetBytesPerPixel(m_Format)];
        uchar*       pDest      = &Img.m_Pixels[0];
        const uint   SrcPitch  = m_Size.x * GetBytesPerPixel(m_Format);
        const uint   DestPitch = Img.m_Size.x * GetBytesPerPixel(Img.m_Format);

        for (int i = Rect.Left(); i < Rect.Right(); ++i)
        {
            std::copy(pSrc, pSrc + DestPitch, pDest);
            pSrc  += SrcPitch;
            pDest += DestPitch;
        }

        return Img;
    }

    //-----------------------------------------------------------------------
    void CImage::Flip()
    {
        int Bpp = GetBytesPerPixel(m_Format);

        for (int j = 0; j < m_Size.y / 2; ++j)
        {
            std::swap_ranges(&m_Pixels[j * m_Size.x * Bpp],
                            &m_Pixels[(j + 1) * m_Size.x * Bpp - 1],
                            &m_Pixels[(m_Size.y - j - 1) * m_Size.x * Bpp]);
        }
    }

    //-----------------------------------------------------------------------
    void CImage::Mirror()
    {
        int Bpp = GetBytesPerPixel(m_Format);

        for (int i = 0; i < m_Size.x / 2; ++i)
            for (int j = 0; j < m_Size.y; ++j)
            {
                std::swap_ranges(&m_Pixels[(i + j * m_Size.x) * Bpp],
                                &m_Pixels[(i + j * m_Size.x + 1) * Bpp],
                                &m_Pixels[(m_Size.x - i - 1 + j * m_Size.x) * Bpp]);
            }
    }

} // namespace Keos
