/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//  plMipmap Class Header                                                    //
//  Derived bitmap class representing a single mipmap.                       //
//  Cyan, Inc.                                                               //
//                                                                           //
//// Version History //////////////////////////////////////////////////////////
//                                                                           //
//  6.7.2001 mcn - Created.                                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#ifndef _plMipmap_h
#define _plMipmap_h

#include "plBitmap.h"
#include "plString.h"

#ifdef HS_DEBUGGING
    #define ASSERT_PIXELSIZE(bitmap, pixelsize)     hsAssert((bitmap)->fPixelSize == (pixelsize), "pixelSize mismatch")
    #define ASSERT_XY(bitmap, x, y)                 hsAssert(x < (bitmap)->fWidth && y < (bitmap)->fHeight, "bad XY")
    #define ASSERT_UNCOMPRESSED()                   hsAssert(fCompressionType!=kDirectXCompression, "Can't operate on compressed map.")

    // Define the following konstant to enable mipmap leak checking. This is because our normal
    // memory manager sucks when trying to track down these problems
    #define MEMORY_LEAK_TRACER
#else
    #define ASSERT_PIXELSIZE(bitmap, pixelsize)
    #define ASSERT_XY(bitmap, x, y)
    #define ASSERT_UNCOMPRESSED()               
#endif

//// Class Definition /////////////////////////////////////////////////////////

class plBitmapCreator;
class plTextGenerator;

class plMipmap : public plBitmap
{
    friend class plBitmapCreator;
    friend class plTextGenerator;

    public:
        //// Public Flags ////


        //// Public Data /////
        
        
        //// Public Members ////


        plMipmap();
        plMipmap( uint32_t width, uint32_t height, unsigned config, uint8_t numLevels = 0, uint8_t compType = kUncompressed, uint8_t format = UncompressedInfo::kRGB8888 );
        plMipmap( plMipmap *bm, float sig, uint32_t createFlags, 
                            float detailDropoffStart, float detailDropoffStop, 
                            float detailMax, float detailMin );
        virtual ~plMipmap();

        CLASSNAME_REGISTER( plMipmap );
        GETINTERFACE_ANY( plMipmap, plBitmap );


        void            Create( uint32_t width, uint32_t height, unsigned config, uint8_t numLevels, uint8_t compType = kUncompressed, uint8_t format = UncompressedInfo::kRGB8888 );

        virtual void    Reset();

        // Get the total size in bytes
        virtual uint32_t  GetTotalSize() const;

        virtual void    Read( hsStream *s, hsResMgr *mgr ) { hsKeyedObject::Read( s, mgr ); this->Read( s ); }
        virtual void    Write( hsStream *s, hsResMgr *mgr ) { hsKeyedObject::Write( s, mgr ); this->Write( s ); }

        virtual uint8_t   GetNumLevels() const { return fNumLevels; }
        virtual uint32_t  GetLevelSize( uint8_t level );        // 0 is the largest

        virtual void        Colorize();
        virtual plMipmap    *Clone() const;
        virtual void        CopyFrom( const plMipmap *source );

        inline uint32_t   GetWidth() const { return fWidth; }
        inline uint32_t   GetHeight() const { return fHeight; }
        inline uint32_t   GetRowBytes() const { return fRowBytes; }

        void            *GetImage() const { return fImage; }
        void            SetImagePtr( void *ptr ) { fImage = ptr; }
        uint8_t           *GetLevelPtr( uint8_t level, uint32_t *width = nil, uint32_t *height = nil, uint32_t *rowBytes = nil );

        // Sets the current level pointer for use with GetAddr*
        virtual void    SetCurrLevel(uint8_t level);
        void            *GetCurrLevelPtr() const { return fCurrLevelPtr; }
        uint32_t          GetCurrWidth() const { return fCurrLevelWidth; }
        uint32_t          GetCurrHeight() const { return fCurrLevelHeight; }
        uint32_t          GetCurrLevelSize() const { return fLevelSizes[ fCurrLevel ]; }
        uint32_t          GetCurrLevel() const { return fCurrLevel; }

        //  These methods return the address of the pixel specified by x and y
        //  They are meant to be fast, therefore they are inlined and do not check
        //  the fPixelSize field at runtime (except when debugging)

        uint8_t*  GetAddr8(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 8);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (uint8_t*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + x);
                }
        uint16_t* GetAddr16(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 16);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (uint16_t*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + (x << 1));
                }
        uint32_t* GetAddr32(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 32);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (uint32_t*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + (x << 2));
                }
        void*   GetAddr64(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 64);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (void*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + (x << 3));
                }

        //  This sets fPixelSize, fSpace, fFlags, for you
        //  All you need to set is
        //      fWidth, fHeight, fRowBytes, fImage and fColorTable
        enum {
            kColor8Config = 0,
            kGray44Config = 1,
            kGray4Config = 2,
            kGray8Config = 8,       // So we can use bit depths instead
            kRGB16Config = 16,
            kRGB32Config = 24,
            kARGB32Config = 32,
        };

        void    SetConfig( unsigned config );


        //// Really complex creation stuff ////

        enum {
            kCreateDetailAlpha      = 0x1,
            kCreateDetailAdd        = 0x2,
            kCreateDetailMult       = 0x4,
            kCreateDetailMask       = kCreateDetailAlpha | kCreateDetailAdd | kCreateDetailMult,
            kCreateCarryAlpha       = 0x8,
            kCreateCarryWhite       = 0x10,
            kCreateCarryBlack       = 0x20,
            kCreateCarryMask        = kCreateCarryAlpha | kCreateCarryWhite | kCreateCarryBlack
        };
        enum hsGPixelType {
            kPixelARGB4444,
            kPixelARGB1555,
            kPixelAI88,
            kPixelI8
        };
        enum hsGCopyOptions {
            kCopyLODMask,
        };

        enum {
            kColorDataRLE   = 0x1,
            kAlphaDataRLE   = 0x2
        };

        void    SetBitmapAsLevel(uint8_t iDst, plMipmap *bm, float sig, uint32_t createFlags, 
                                              float detailDropoffStart, float detailDropoffStop, 
                                              float detailMax, float detailMin);
        void    ICreateLevelNoDetail(uint8_t iDst, const plFilterMask& mask);
        void    IBlendLevelDetailAlpha(uint8_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin);
        void    IBlendLevelDetailAdd(uint8_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin);
        void    IBlendLevelDetailMult(uint8_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin);
        void    Filter(float sig);
        uint32_t  CopyOutPixels(uint32_t destXSize, uint32_t destYSize, uint32_t dstFormat, void *destPixels, uint32_t copyOptions);

        void    ClipToMaxSize( uint32_t maxDimension );
        void    RemoveMipping();

        void    EnsureKonstantBorder( bool clampU, bool clampV );

        enum CompositeFlags
        {
            kForceOpaque    = 0x0001,       // Copy src pixels raw, force dest alphas to opaque
            kCopySrcAlpha   = 0x0002,       // Copy the src pixels raw, including alphas, overwrite dest
            kBlendSrcAlpha  = 0x0004,       // Blend src pixels onto dest using src alpha, dest alpha = src alpha
            kMaskSrcAlpha   = 0x0008,       // Same as copySrcAlpha, but dest is untouched when src alpha = 0
            kBlendWriteAlpha= 0x0010        // Like default (0), but writes dest alpha values
        };

        class CompositeOptions
        {
            // Helper class for specifying options to Composite()
            public:
                uint16_t      fFlags;
                uint8_t       fSrcLevelsToSkip;
                uint8_t       fOpacity;
                float    fRedTint, fGreenTint, fBlueTint;
                uint16_t      fSrcClipX, fSrcClipY;           // Clipping is applied AFTER levelSkip
                uint16_t      fSrcClipWidth, fSrcClipHeight;  // 0 means max width/height

                CompositeOptions() { fFlags = 0; fSrcLevelsToSkip = 0; fRedTint = fGreenTint = fBlueTint = 1.f;
                                    fSrcClipX = fSrcClipY = fSrcClipWidth = fSrcClipHeight = 0; fOpacity = 255;}

                CompositeOptions( uint16_t flags, uint8_t srcLevelsToSkip = 0, float red = 1.f, float green = 1.f,
                                    float blue = 1.f, uint16_t srcClipX = 0, uint16_t srcClipY = 0, 
                                    uint16_t srcClipWidth = 0, uint16_t srcClipHeight = 0, uint8_t opacity = 255 ) 
                {
                    fFlags = flags;
                    fSrcLevelsToSkip = srcLevelsToSkip;
                    fRedTint = red;
                    fGreenTint = green;
                    fBlueTint = blue;
                    fSrcClipX = srcClipX;
                    fSrcClipY = srcClipY;
                    fSrcClipWidth = srcClipWidth;
                    fSrcClipHeight = srcClipHeight;
                    fOpacity = opacity;
                }
        };

        // Compositing function. Take a (smaller) mipmap and composite it onto this one at the given location. Nil options means use default
        virtual void    Composite( plMipmap *source, uint16_t x, uint16_t y, CompositeOptions *options = nil );

        // Scaling function
        enum ScaleFilter
        {
            kBoxFilter,
            kDefaultFilter = kBoxFilter
        };

        virtual void    ScaleNicely( uint32_t *destPtr, uint16_t destWidth, uint16_t destHeight,
                                uint16_t destStride, plMipmap::ScaleFilter filter ) const;

        virtual bool    ResizeNicely( uint16_t newWidth, uint16_t newHeight, plMipmap::ScaleFilter filter );

    protected:

        //// Protected Members ////

        void        *fImage;
        uint32_t    fWidth, fHeight, fRowBytes, fTotalSize;
        uint8_t     fNumLevels;
        uint32_t    *fLevelSizes;

        void        *fCurrLevelPtr;
        uint8_t     fCurrLevel;
        uint32_t    fCurrLevelWidth, fCurrLevelHeight, fCurrLevelRowBytes;

        void    IReadRawImage( hsStream *stream );
        void    IWriteRawImage( hsStream *stream );
        plMipmap *ISplitAlpha();
        void    IRecombineAlpha( plMipmap *alphaChannel );
        plMipmap *IReadRLEImage( hsStream *stream );
        void    IWriteRLEImage( hsStream *stream, plMipmap *mipmap );
        void    IReadJPEGImage( hsStream *stream );
        void    IWriteJPEGImage( hsStream *stream );
        void    IBuildLevelSizes();

        void    IColorLevel( uint8_t level, const uint8_t *colorMask );

        float    IGetDetailLevelAlpha( uint8_t level, float dropStart, float dropStop, float min, float max );

        void        ICarryZeroAlpha(uint8_t iDst);
        void        ICarryColor(uint8_t iDst, uint32_t col);

        bool        IGrabBorderColor( bool grabVNotU, uint32_t *color );
        void        ISetCurrLevelUBorder( uint32_t color );
        void        ISetCurrLevelVBorder( uint32_t color );

        virtual uint32_t  Read( hsStream *s );
        virtual uint32_t  Write( hsStream *s );

        friend class plCubicEnvironmap;

#ifdef MEMORY_LEAK_TRACER

    protected:
    
        class plRecord
        {
            public:
                plRecord    *fNext;
                plRecord    **fBackPtr;

                plString    fKeyName;
                void        *fImage;
                uint32_t    fWidth, fHeight, fRowBytes;
                uint8_t     fNumLevels;
                uint8_t     fCompressionType;
                union 
                {
                    DirectXInfo         fDirectXInfo;
                    UncompressedInfo    fUncompressedInfo;
                };
                enum Method
                {
                    kViaCreate,
                    kViaRead,
                    kViaClipToMaxSize,
                    kViaDetailMapConstructor,
                    kViaCopyFrom,
                    kViaResize
                } fCreationMethod;

                void    Link( plRecord **backPtr )
                {
                    fBackPtr = backPtr;
                    fNext = *backPtr;
                    if( fNext != nil )
                        fNext->fBackPtr = &fNext;
                    *backPtr = this;
                }

                void    Unlink()
                {
                    *fBackPtr = fNext;
                    if( fNext != nil )
                        fNext->fBackPtr = fBackPtr;
                }
        };

        static plRecord *fRecords;
        static uint32_t   fNumMipmaps;

        static void IAddToMemRecord( plMipmap *mip, plRecord::Method method );
        static void IRemoveFromMemRecord( uint8_t *image );
        static void IReportLeaks();

#endif
};


#endif // _plMipmap_h