/*
Bitmap.h
Written by Matthew Fisher

A bitmap class (a 2D array of RGBColor's)
Rather self-explanitory, can only save and load a few primitive formats.
*/

#pragma once

struct BitmapSaveOptions
{
    BitmapSaveOptions()
    {
        //BGREncoding = false;
        //VerticalFlip = false;

        Region = Rectangle2i(Vec2i::Origin, Vec2i::Origin);
        ScratchSpace = NULL;
        ScratchSpaceSize = 0;
        SaveAlpha = false;
        UseBGR = false;
    }
    //bool BGREncoding;
    //bool VerticalFlip;

    Rectangle2i Region;
    BYTE *ScratchSpace;
    UINT ScratchSpaceSize;
    bool SaveAlpha;
    bool UseBGR;
};

class Bitmap
{
public:
    Bitmap();
    Bitmap(const Bitmap &B);
    Bitmap(const Bitmap &B, UINT x, UINT y, UINT Width, UINT Height);
    Bitmap(UINT Width, UINT Height);

    ~Bitmap();

    //
    // Memory
    //
    void FreeMemory();
    void Allocate(UINT Width, UINT Height);
    Bitmap& operator = (const Bitmap &Bmp);

    //
    // Accessors
    //
    __forceinline RGBColor* operator [] (UINT Row) {return &_Data[Row * _Width];}
    __forceinline const RGBColor* operator [] (UINT Row) const {return &_Data[Row * _Width];}
    __forceinline UINT Width() const {return _Width;}
    __forceinline UINT Height() const {return _Height;}
    __forceinline Vec2i Dimensions() const {return Vec2i(_Width, _Height);}
    __forceinline RGBColor SampleNearest(const Vec2f &Pos)
    {
        Vec2i SamplePos(Utility::Bound(Math::Round(Pos.x * (_Width - 1)), 0, int(_Width) - 1),
                        Utility::Bound(Math::Round(Pos.y * (_Height - 1)), 0, int(_Height) - 1));
        return _Data[SamplePos.y * _Width + SamplePos.x];
    }

    //
    // File Functions
    //
    void SaveBMP(const String &Filename) const; //saves bitmap to Filename in 32-bit *.BMP format
    void SavePPM(const String &Filename) const; //saves bitmap to Filename in *.PPM format
    void SavePGM(const String &Filename, int Channel) const;
    void SavePNG(const String &Filename) const; //saves bitmap to Filename in 24-bit *.PNG format
    void SavePNG(const String &Filename, const BitmapSaveOptions &Options) const;
    void SavePNGToMemory(Vector<BYTE> &Buffer) const; //save bitmap to Buffer in *.PNG format
    void SavePNGToMemory(Vector<BYTE> &Buffer, const BitmapSaveOptions &Options) const;
    UINT SaveDelta(const Bitmap &Bmp, const String &Filename) const;
    void LoadBMP(const String &Filename); //loads bitmap from Filename in *.BMP format
    void LoadPNG(const String &Filename); //loads bitmap from Filename in *.PNG format
    void LoadPNGFromMemory(const Vector<BYTE> &Buffer); //loads bitmap from Buffer in *.PNG format
    void LoadSDL(const String &Filename); //loads bitmap from most file formats using SDL
    void LoadDelta(const Bitmap &Bmp, const String &Filename);

#ifdef USE_D3D9
    void LoadFromSurface(LPDIRECT3DSURFACE9 Surface);
    static void SaveSurfaceToPNG(LPDIRECT3DSURFACE9 Surface, const String &Filename, const BitmapSaveOptions &Options);
#endif

    //
    // Transfer Functions
    //
    __forceinline void BltTo(Bitmap &B, const Vec2i &Target) const
    {
        BltTo(B, Target.x, Target.y);
    }
    void BltTo(Bitmap &B, int TargetX, int TargetY) const;
    void BltToNoClipMemcpy(Bitmap &B, UINT TargetX, UINT TargetY) const;
    void BltTo(Bitmap &B, int TargetX, int TargetY, int SourceX, int SourceY, int Width, int Height) const;
    
    enum SamplingType
    {
        SamplingPoint,
        SamplingLinear,
    };
    void StretchBltTo(Bitmap &B, const Rectangle2i &TargetRect, const Rectangle2i &SourceRect, SamplingType Sampling) const;
    void StretchBltTo(Bitmap &B, int TargetX, int TargetY, int TargetWidth, int TargetHeight, int SourceX, int SourceY, int SourceWidth, int SourceHeight, SamplingType Sampling) const;
    
    void LoadGrid(const Grid<float> &Values);
    void LoadGrid(const Grid<float> &Values, float Min, float Max);
    void FlipHorizontal();
    void FlipVertical();
    
    //
    // Query
    //
    UINT Hash32() const;
    UINT64 Hash64() const;
    bool PerfectMatch(const Bitmap &Bmp) const;
    bool Monochrome(RGBColor Color) const;
    bool MonochromeIncludingAlpha(RGBColor Color) const;
    UINT CountPixelsWithColor(RGBColor Color) const;

    //
    // Modifiers
    //
    void ReplaceColor(RGBColor SourceColor, RGBColor NewColor);
    void Clear(const RGBColor &Color);    //clears all pixels to Color
    void Clear();                         //clears all pixels to RGBColor::Black
    void LoadAlphaChannelAsGrayscale();
    void FlipBlueAndRed();

    //
    // Query
    //
    RGBColor AverageColorOverRegion(const Rectangle2f &Region) const;
    
private:
#ifdef USE_PNG
    static void __cdecl PNGReadFromBuffer(png_structp png_ptr, png_bytep data, png_size_t length);
    static void __cdecl PNGWriteToBuffer(png_structp png_ptr, png_bytep data, png_size_t length);
    static void __cdecl PNGFlushBuffer(png_structp png_ptr);
    void PNGCompleteRead(png_structp PngRead, png_infop PngInfo, const String &Filename);
    void PNGCompleteWrite(png_structp PngWrite, png_infop PngInfo, const BitmapSaveOptions &Options) const;
#endif

    UINT _Width, _Height;   //width and height of the bitmap
    RGBColor* _Data;        //Raw RGBColor data in one big array
};

namespace Utility
{
    __forceinline bool PointInsideBitmap(const Bitmap &Bmp, int x, int y)
    {
        return (between(x, 0, int(Bmp.Width()) - 1) &&
                between(y, 0, int(Bmp.Height()) - 1));
    }
}