/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2005 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_IMAGE
#define OSG_IMAGE 1

#include <osg/BufferObject>

#include <string>
#include <vector>

#ifndef GL_VERSION_1_2
    // 1.2 definitions...
    #define GL_BGR                          0x80E0
    #define GL_BGRA                         0x80E1
    #define GL_UNSIGNED_BYTE_3_3_2          0x8032
    #define GL_UNSIGNED_BYTE_2_3_3_REV      0x8362
    #define GL_UNSIGNED_SHORT_5_6_5         0x8363
    #define GL_UNSIGNED_SHORT_5_6_5_REV     0x8364
    #define GL_UNSIGNED_SHORT_4_4_4_4       0x8033
    #define GL_UNSIGNED_SHORT_4_4_4_4_REV   0x8365
    #define GL_UNSIGNED_SHORT_5_5_5_1       0x8034
    #define GL_UNSIGNED_SHORT_1_5_5_5_REV   0x8366
    #define GL_UNSIGNED_INT_8_8_8_8         0x8035
    #define GL_UNSIGNED_INT_8_8_8_8_REV     0x8367
    #define GL_UNSIGNED_INT_10_10_10_2      0x8036
    #define GL_UNSIGNED_INT_2_10_10_10_REV  0x8368
#endif

#ifndef GL_COMPRESSED_ALPHA
    #define GL_COMPRESSED_ALPHA             0x84E9
    #define GL_COMPRESSED_LUMINANCE         0x84EA
    #define GL_COMPRESSED_LUMINANCE_ALPHA   0x84EB
    #define GL_COMPRESSED_INTENSITY         0x84EC
    #define GL_COMPRESSED_RGB               0x84ED
    #define GL_COMPRESSED_RGBA              0x84EE
#endif

namespace osg {

/** Image class for encapsulating the storage texture image data. */
class OSG_EXPORT Image : public Object
{

    public :

        Image();
        
        /** Copy constructor using CopyOp to manage deep vs shallow copy. */
        Image(const Image& image,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        virtual Object* cloneType() const { return new Image(); }
        virtual Object* clone(const CopyOp& copyop) const { return new Image(*this,copyop); }
        virtual bool isSameKindAs(const Object* obj) const { return dynamic_cast<const Image*>(obj)!=0; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "Image"; }

        /** Return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs. */
        virtual int compare(const Image& rhs) const;

        void setFileName(const std::string& fileName);
        inline const std::string& getFileName() const { return _fileName; }
        
        enum AllocationMode {
            NO_DELETE,
            USE_NEW_DELETE,
            USE_MALLOC_FREE
        };
        
        /** Set the method used for deleting data once it goes out of scope. */
        void setAllocationMode(AllocationMode mode) { _allocationMode = mode; }

        /** Get the method used for deleting data once it goes out of scope. */
        AllocationMode getAllocationMode() const { return _allocationMode; }


        /** Allocate a pixel block of specified size and type. */
        void allocateImage(int s,int t,int r,
                           GLenum pixelFormat,GLenum type,
                           int packing=1);
        
        
        /** Set the image data and pixelFormat.
          * Note, when the packing value is negative (the default is -1) this method assumes
          * a _packing width of 1 if the width is not a multiple of 4,
          * otherwise automatically sets to _packing to 4. If a positive
          * value of packing is supplied than _packing is simply set to that value.
        */
        void setImage(int s,int t,int r,
                      GLint internalTextureformat,
                      GLenum pixelFormat,GLenum type,
                      unsigned char* data,
                      AllocationMode mode,
                      int packing=1);
            
        /** Read pixels from current frame buffer at specified position and size, using glReadPixels.
          * Create memory for storage if required, reuse existing pixel coords if possible.
        */
        void readPixels(int x,int y,int width,int height,
                        GLenum pixelFormat,GLenum type);
            

        /** Read the contents of the current bound texture, handling compressed pixelFormats if present.
          * Create memory for storage if required, reuse existing pixel coords if possible.
        */
        void readImageFromCurrentTexture(unsigned int contextID, bool copyMipMapsIfAvailable);


        /** Scale image to specified size. */
        void scaleImage(int s,int t,int r) { scaleImage(s,t,r, getDataType()); }

        /** Scale image to specified size and with specified data type. */
        void scaleImage(int s,int t,int r, GLenum newDataType);

        /** Copy a source Image into a subpart of this Image at specified position.
          * Typically used to copy to an already allocated image, such as creating
          * a 3D image from a stack 2D images.
          * If this Image is empty then image data is created to
          * accomodate the source image in its offset position.
          * If source is NULL then no operation happens, this Image is left unchanged.
        */
        void copySubImage(int s_offset,int t_offset,int r_offset,osg::Image* source);

        /** Width of image. */
        inline int s() const { return _s; }

        /** Height of image. */
        inline int t() const { return _t; }
        
        /** Depth of image. */
        inline int r() const { return _r; }
        
        void setInternalTextureFormat(GLint internalFormat);
        inline GLint getInternalTextureFormat() const { return _internalTextureFormat; }
        
        void setPixelFormat(GLenum pixelFormat);
        inline GLenum getPixelFormat() const { return _pixelFormat; }
        
        inline GLenum getDataType() const { return _dataType; }        
        
        inline unsigned int getPacking() const { return _packing; }
        
        /** Return the number of bits required for each pixel. */
        inline unsigned int getPixelSizeInBits() const { return computePixelSizeInBits(_pixelFormat,_dataType); }

        /** Return the number of bytes each row of pixels occupies once it has been packed. */
        inline unsigned int getRowSizeInBytes() const { return computeRowWidthInBytes(_s,_pixelFormat,_dataType,_packing); }

        /** Return the number of bytes each image (_s*_t) of pixels occupies. */
        inline unsigned int getImageSizeInBytes() const { return getRowSizeInBytes()*_t; }
        
        /** Return the number of bytes the whole row/image/volume of pixels occupies. */
        inline unsigned int getTotalSizeInBytes() const { return getImageSizeInBytes()*_r; }

        /** Return the number of bytes the whole row/image/volume of pixels occupies, including all mip maps if included. */
        unsigned int getTotalSizeInBytesIncludingMipmaps() const;

        /** Return true if the Image represent a valid and usable imagery.*/
        bool valid() const { return _s!=0 && _t!=0 && _r!=0 && _data!=0 && _dataType!=0; }

        /** Raw image data. */
        inline unsigned char* data() { return _data; }
        
        /** Raw const image data. */
        inline const unsigned char* data() const { return _data; }


        inline unsigned char* data(int column, int row=0,int image=0)
        {
            if (!_data) return NULL;
            return _data+(column*getPixelSizeInBits())/8+row*getRowSizeInBytes()+image*getImageSizeInBytes();
        }
        
        inline const unsigned char* data(int column, int row=0,int image=0) const
        {
            if (!_data) return NULL;
            return _data+(column*getPixelSizeInBits())/8+row*getRowSizeInBytes()+image*getImageSizeInBytes();
        }


        /** Flip the image horizontally. */
        void flipHorizontal();
        
        /** Flip the image vertically. */
        void flipVertical();


        /** Ensure image dimensions are a power of two.
          * Mipmapped textures require the image dimensions to be
          * power of two and are within the maxiumum texture size for
          * the host machine.
        */
        void ensureValidSizeForTexturing(GLint maxTextureSize);
      
        /** Dirty the image, which increments the modified count, to force osg::Texture to reload the image. */
        inline void dirty() { ++_modifiedCount; }      
      
        /** Set the modified count value. Used by osg::Texture when using texture subloading. */
        inline void setModifiedCount(unsigned int value) { _modifiedCount=value; }

        /** Get modified count value. Used by osg::Texture when using texture subloading. */
        inline unsigned int getModifiedCount() const { return _modifiedCount; }


        static bool isPackedType(GLenum type);
        static unsigned int computeNumComponents(GLenum pixelFormat);
        static unsigned int computePixelSizeInBits(GLenum pixelFormat,GLenum type);
        static unsigned int computeRowWidthInBytes(int width,GLenum pixelFormat,GLenum type,int packing);
        static int computeNearestPowerOfTwo(int s,float bias=0.5f);
                
        /** Precomputed mipmaps stuff. */
        typedef std::vector< unsigned int > MipmapDataType;

        inline bool isMipmap() const {return !_mipmapData.empty();};

        unsigned int getNumMipmapLevels() const
        {
            return _mipmapData.size()+1;
        };

        /** Send offsets into data. It is assumed that first mipmap offset (index 0) is 0.*/
        inline void setMipmapLevels(const MipmapDataType& mipmapDataVector) { _mipmapData = mipmapDataVector; }
        
        inline const MipmapDataType& getMipmapLevels() const { return _mipmapData; }

        inline unsigned int getMipmapOffset(unsigned int mipmapLevel) const
        {
            if(mipmapLevel == 0)
                return 0;
            else if (mipmapLevel < getNumMipmapLevels())
               return _mipmapData[mipmapLevel-1];
            return 0;
        };
        
        inline unsigned char* getMipmapData(unsigned int mipmapLevel)
        {
           return _data+getMipmapOffset(mipmapLevel);
        }

        inline const unsigned char* getMipmapData(unsigned int mipmapLevel) const
        {
           return _data+getMipmapOffset(mipmapLevel);
        }
        
        /** Return true if this image is translucent - i.e. it has alpha values that are less 1.0 (when normalized). */
        bool isImageTranslucent() const;

        /** Set the optional PixelBufferObject used to map the image memory efficiently to graphics memory. */ 
        void setPixelBufferObject(PixelBufferObject* buffer) { _bufferObject = buffer; if (_bufferObject.valid()) _bufferObject->setImage(this); }

        /** Get the PixelBufferObject.*/ 
        PixelBufferObject* getPixelBufferObject() { return _bufferObject.get(); }

        /** Get the const PixelBufferObject.*/ 
        const PixelBufferObject* getPixelBufferObject() const { return _bufferObject.get(); }
        
    protected :

        virtual ~Image();

        Image& operator = (const Image&) { return *this; }

        std::string _fileName;

        int _s, _t, _r;
        GLint _internalTextureFormat;
        GLenum _pixelFormat;
        GLenum _dataType;
        unsigned int _packing;

        AllocationMode _allocationMode;
        unsigned char* _data;
        
        void deallocateData();
        
        void setData(unsigned char* data,AllocationMode allocationMode);

        unsigned int _modifiedCount;

        MipmapDataType _mipmapData;
        
        ref_ptr<PixelBufferObject> _bufferObject;
};

class Geode;

/** Convenience function to be used by image loaders to generate a valid geode
  * to return for readNode().
  * Use the image's s and t values to scale the dimensions of the image.
*/
extern OSG_EXPORT Geode* createGeodeForImage(Image* image);
/** Convenience function to be used by image loaders to generate a valid geode
  * to return for readNode().
  * Use the specified s and t values to scale the dimensions of the image.
*/
extern OSG_EXPORT Geode* createGeodeForImage(Image* image,float s,float t);

}

#endif                                            // __SG_IMAGE_H
