/*==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"

#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 width, UInt32 height, unsigned config, UInt8 numLevels = 0, UInt8 compType = kUncompressed, UInt8 format = UncompressedInfo::kRGB8888 );
        plMipmap( plMipmap *bm, hsScalar sig, UInt32 createFlags, 
                            hsScalar detailDropoffStart, hsScalar detailDropoffStop, 
                            hsScalar detailMax, hsScalar detailMin );
        virtual ~plMipmap();

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


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

        virtual void    Reset();

        // Get the total size in bytes
        virtual UInt32  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   GetNumLevels() const { return fNumLevels; }
        virtual UInt32  GetLevelSize( UInt8 level );        // 0 is the largest

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

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

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

        // Sets the current level pointer for use with GetAddr*
        virtual void    SetCurrLevel(UInt8 level);
        void            *GetCurrLevelPtr() const { return fCurrLevelPtr; }
        UInt32          GetCurrWidth() const { return fCurrLevelWidth; }
        UInt32          GetCurrHeight() const { return fCurrLevelHeight; }
        UInt32          GetCurrLevelSize() const { return fLevelSizes[ fCurrLevel ]; }
        UInt32          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*  GetAddr8(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 8);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (UInt8*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + x);
                }
        UInt16* GetAddr16(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 16);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (UInt16*)((char*)fCurrLevelPtr + y * fCurrLevelRowBytes + (x << 1));
                }
        UInt32* GetAddr32(unsigned x, unsigned y) const
                {
                    ASSERT_PIXELSIZE(this, 32);
                    ASSERT_XY(this, x, y);
                    ASSERT_UNCOMPRESSED();
                    return (UInt32*)((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 iDst, plMipmap *bm, hsScalar sig, UInt32 createFlags, 
                                              hsScalar detailDropoffStart, hsScalar detailDropoffStop, 
                                              hsScalar detailMax, hsScalar detailMin);
        void    ICreateLevelNoDetail(UInt8 iDst, const plFilterMask& mask);
        void    IBlendLevelDetailAlpha(UInt8 iDst, const plFilterMask& mask, 
                                          hsScalar detailDropoffStart, hsScalar detailDropoffStop, 
                                          hsScalar detailMax, hsScalar detailMin);
        void    IBlendLevelDetailAdd(UInt8 iDst, const plFilterMask& mask, 
                                          hsScalar detailDropoffStart, hsScalar detailDropoffStop, 
                                          hsScalar detailMax, hsScalar detailMin);
        void    IBlendLevelDetailMult(UInt8 iDst, const plFilterMask& mask, 
                                          hsScalar detailDropoffStart, hsScalar detailDropoffStop, 
                                          hsScalar detailMax, hsScalar detailMin);
        void    Filter(hsScalar sig);
        UInt32  CopyOutPixels(UInt32 destXSize, UInt32 destYSize, UInt32 dstFormat, void *destPixels, UInt32 copyOptions);

        void    ClipToMaxSize( UInt32 maxDimension );
        void    RemoveMipping();

        void    EnsureKonstantBorder( hsBool clampU, hsBool 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      fFlags;
                UInt8       fSrcLevelsToSkip;
                UInt8       fOpacity;
                hsScalar    fRedTint, fGreenTint, fBlueTint;
                UInt16      fSrcClipX, fSrcClipY;           // Clipping is applied AFTER levelSkip
                UInt16      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 flags, UInt8 srcLevelsToSkip = 0, hsScalar red = 1.f, hsScalar green = 1.f,
                                    hsScalar blue = 1.f, UInt16 srcClipX = 0, UInt16 srcClipY = 0, 
                                    UInt16 srcClipWidth = 0, UInt16 srcClipHeight = 0, UInt8 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 x, UInt16 y, CompositeOptions *options = nil );

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

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

        virtual hsBool  ResizeNicely( UInt16 newWidth, UInt16 newHeight, plMipmap::ScaleFilter filter );

    protected:

        //// Protected Members ////

        void        *fImage;
        UInt32      fWidth, fHeight, fRowBytes, fTotalSize;
        UInt8       fNumLevels;
        UInt32      *fLevelSizes;

        void        *fCurrLevelPtr;
        UInt8       fCurrLevel;
        UInt32      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 level, const UInt8 *colorMask );

        hsScalar    IGetDetailLevelAlpha( UInt8 level, hsScalar dropStart, hsScalar dropStop, hsScalar min, hsScalar max );

        void        ICarryZeroAlpha(UInt8 iDst);
        void        ICarryColor(UInt8 iDst, UInt32 col);

        hsBool      IGrabBorderColor( hsBool grabVNotU, UInt32 *color );
        void        ISetCurrLevelUBorder( UInt32 color );
        void        ISetCurrLevelVBorder( UInt32 color );

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

        friend class plCubicEnvironmap;

#ifdef MEMORY_LEAK_TRACER

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

                char        fKeyName[ 256 ];
                void        *fImage;
                UInt32      fWidth, fHeight, fRowBytes;
                UInt8       fNumLevels; 
                UInt8       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   fNumMipmaps;

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

#endif
};


#endif // _plMipmap_h