/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 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_TEXTURE
#define OSG_TEXTURE 1

#include <osg/GL>
#include <osg/Image>
#include <osg/StateAttribute>
#include <osg/ref_ptr>
#include <osg/Vec4>
#include <osg/buffered_value>

#include <list>
#include <map>

// if not defined by gl.h use the definition found in:
// http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_filter_anisotropic.txt
#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
#endif

#ifndef GL_ARB_texture_compression
#define GL_COMPRESSED_ALPHA_ARB                 0x84E9
#define GL_COMPRESSED_LUMINANCE_ARB             0x84EA
#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB       0x84EB
#define GL_COMPRESSED_INTENSITY_ARB             0x84EC
#define GL_COMPRESSED_RGB_ARB                   0x84ED
#define GL_COMPRESSED_RGBA_ARB                  0x84EE
#define GL_TEXTURE_COMPRESSION_HINT_ARB         0x84EF
#define GL_TEXTURE_COMPRESSED_ARB               0x86A1
#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB   0x86A2
#define GL_COMPRESSED_TEXTURE_FORMATS_ARB       0x86A3
#endif

#ifndef GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB
#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB    0x86A0
#endif

#ifndef GL_EXT_texture_compression_s3tc
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT         0x83F0
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT        0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT        0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT        0x83F3
#endif

#ifndef GL_MIRRORED_REPEAT_IBM
#define GL_MIRRORED_REPEAT_IBM            0x8370
#endif

#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE                  0x812F
#endif

#ifndef GL_CLAMP_TO_BORDER_ARB
#define GL_CLAMP_TO_BORDER_ARB            0x812D
#endif

#ifndef GL_GENERATE_MIPMAP_SGIS
#define GL_GENERATE_MIPMAP_SGIS           0x8191
#define GL_GENERATE_MIPMAP_HINT_SGIS      0x8192
#endif

#ifndef GL_TEXTURE_3D
#define GL_TEXTURE_3D                     0x806F
#endif



namespace osg {

/** Texture base class which encapsulates OpenGl texture functionality which common betweent the various types of OpenGL textures.*/
class SG_EXPORT Texture : public osg::StateAttribute
{

    public :
    
    
        Texture();

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

        virtual osg::Object* cloneType() const = 0;
        virtual osg::Object* clone(const CopyOp& copyop) const = 0;
        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const Texture *>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "Texture"; }
        virtual Type getType() const { return TEXTURE; }

        virtual bool isTextureAttribute() const { return true; }

        enum WrapParameter {
            WRAP_S,
            WRAP_T,
            WRAP_R
        };

        enum WrapMode {
            CLAMP  = GL_CLAMP,
            CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE,
            CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER_ARB,
            REPEAT = GL_REPEAT,
            MIRROR = GL_MIRRORED_REPEAT_IBM
        };

        /** Set the texture wrap mode.*/
        void setWrap(WrapParameter which, WrapMode wrap);
        /** Get the texture wrap mode.*/
        WrapMode getWrap(WrapParameter which) const;


        /** Sets the border color for this texture. Makes difference only if
          * wrap mode is CLAMP_TO_BORDER */
        void setBorderColor(const Vec4& color) { _borderColor = color; dirtyTextureParameters(); }
        
        const Vec4& getBorderColor() const { return _borderColor; }
        

        enum FilterParameter {
            MIN_FILTER,
            MAG_FILTER
        };

        enum FilterMode {
            LINEAR                    = GL_LINEAR,
            LINEAR_MIPMAP_LINEAR      = GL_LINEAR_MIPMAP_LINEAR,
            LINEAR_MIPMAP_NEAREST     = GL_LINEAR_MIPMAP_NEAREST,
            NEAREST                   = GL_NEAREST,
            NEAREST_MIPMAP_LINEAR     = GL_NEAREST_MIPMAP_LINEAR,
            NEAREST_MIPMAP_NEAREST    = GL_NEAREST_MIPMAP_NEAREST
        };


        /** Set the texture filter mode.*/
        void setFilter(FilterParameter which, FilterMode filter);

        /** Get the texture filter mode.*/
        FilterMode getFilter(FilterParameter which) const;

        /** Set the maximum anisotropy value, default value is 1.0 for
          * no anisotropic filtering.  If hardware does not support anisotropic
          * filtering then normal filtering is used, equivilant to a max anisotropy value of 1.0.
          * valid range is 1.0f upwards.  The maximum value depends on the graphics
          * system being used.*/
        void setMaxAnisotropy(float anis);
        
        /** Get the maximum anisotropy value.*/
        inline float getMaxAnisotropy() const { return _maxAnisotropy; }

        /** Set the hint of whether to use hardware mip map generation where available.*/
        inline void setUseHardwareMipMapGeneration(bool useHardwareMipMapGeneration) { _useHardwareMipMapGeneration = useHardwareMipMapGeneration; }

        /** Get the hint of whether to use hardware mip map generation where available.*/
        inline bool getUseHardwareMipMapGeneration() const { return _useHardwareMipMapGeneration; }

        /** Set the automatic unreference of image data after the texture has been set up in apply, on (true) or off (false).
          * If the image data is only referened by this Texture then the image data will be autoamtically deleted.*/
        inline void setUnRefImageDataAfterApply(bool flag) { _unrefImageDataAfterApply = flag; }
        
        /** Get the automatic unreference of image data after the texture has been set up in apply.*/
        inline bool getUnRefImageDataAfterApply() const { return _unrefImageDataAfterApply; }


        enum InternalFormatMode {
            USE_IMAGE_DATA_FORMAT,
            USE_USER_DEFINED_FORMAT,
            USE_ARB_COMPRESSION,
            USE_S3TC_DXT1_COMPRESSION,
            USE_S3TC_DXT3_COMPRESSION,
            USE_S3TC_DXT5_COMPRESSION
        };

        /** Set the internal format mode.
          * Note, If the mode is set USE_IMAGE_DATA_FORMAT, USE_ARB_COMPRESSION,
          * USE_S3TC_COMPRESSION the internalFormat is automatically selected,
          * and will overwrite the previous _internalFormat.
          */
        inline void setInternalFormatMode(InternalFormatMode mode) { _internalFormatMode = mode; }

        /** Get the internal format mode.*/
        inline InternalFormatMode getInternalFormatMode() const { return _internalFormatMode; }

        /** Set the internal format to use when creating OpenGL textures.
          * Also sets the internalFormatMode to USE_USER_DEFINED_FORMAT.
          */
        inline void setInternalFormat(GLint internalFormat)
        {
            _internalFormatMode = USE_USER_DEFINED_FORMAT;
            _internalFormat = internalFormat;
        }

        /** Get the internal format to use when creating OpenGL textures.*/
        inline GLint getInternalFormat() const { if (_internalFormat==0) computeInternalFormat(); return _internalFormat; }
        
        bool isCompressedInternalFormat() const;


        class TextureObject;

        /** Get the handle to the texture object for the current context.*/
        inline TextureObject* getTextureObject(unsigned int contextID) const
        {
            return _textureObjectBuffer[contextID].get();
        }

        /** Force a recompile on next apply() of associated OpenGL texture objects.*/
        void dirtyTextureObject();

        /** return true if the texture objects for all the required graphics contexts are loaded.*/
        bool areAllTextureObjectsLoaded() const;


        /** get the dirty flag for the current contextID.*/
        inline unsigned int& getTextureParameterDirty(unsigned int contextID) const
        {
            return _texParametersDirtyList[contextID];
        }


        /** Force a resetting on next apply() of associated OpenGL texture parameters.*/
        void dirtyTextureParameters();



        
        /** Texture is pure virtual base class, apply must be overriden. */
        virtual void apply(State& state) const = 0;

        /** Calls apply(state) to compile the texture. */
        virtual void compile(State& state) const;

        /** Extensions class which encapsulates the querring of extensions and
          * associated function pointers, and provide convinience wrappers to 
          * check for the extensions or use the associated functions.*/        
        class SG_EXPORT Extensions : public osg::Referenced
        {
            public:
                Extensions();

                Extensions(const Extensions& rhs);
                
                void lowestCommonDenominator(const Extensions& rhs);
                
                void setupGLExtenions();

                void setMultiTexturingSupported(bool flag) { _isMultiTexturingSupported=flag; }
                bool isMultiTexturingSupported() const { return _isMultiTexturingSupported; }

                void setTextureFilterAnisotropicSupported(bool flag) { _isTextureFilterAnisotropicSupported=flag; }
                bool isTextureFilterAnisotropicSupported() const { return _isTextureFilterAnisotropicSupported; }
                
                void setTextureCompressionARBSupported(bool flag) { _isTextureCompressionARBSupported=flag; }
                bool isTextureCompressionARBSupported() const { return _isTextureCompressionARBSupported; }

                void setTextureCompressionS3TCSupported(bool flag) { _isTextureCompressionS3TCSupported=flag; }
                bool isTextureCompressionS3TCSupported() const { return _isTextureCompressionS3TCSupported; }

                void setTextureMirroredRepeatSupported(bool flag) { _isTextureMirroredRepeatSupported=flag; }
                bool isTextureMirroredRepeatSupported() const { return _isTextureMirroredRepeatSupported; }

                void setTextureEdgeClampSupported(bool flag) { _isTextureEdgeClampSupported=flag; }
                bool isTextureEdgeClampSupported() const { return _isTextureEdgeClampSupported; }

                void setTextureBorderClampSupported(bool flag) { _isTextureBorderClampSupported=flag; }
                bool isTextureBorderClampSupported() const { return _isTextureBorderClampSupported; }

                void setGenerateMipMapSupported(bool flag) { _isGenerateMipMapSupported=flag; }
                bool isGenerateMipMapSupported() const { return _isGenerateMipMapSupported; }

                void setMaxTextureSize(GLint maxsize) { _maxTextureSize=maxsize; }
                GLint maxTextureSize() const { return _maxTextureSize; }

                bool isCompressedTexImage2DSupported() const { return _glCompressedTexImage2D!=0; }

                void setCompressedTexImage2DProc(void* ptr) { _glCompressedTexImage2D = ptr; }
                void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) const;

                void setCompressedTexSubImage2DProc(void* ptr) { _glCompressedTexSubImage2D = ptr; }
                void glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei type, const GLvoid *data) const;

                void setGetCompressedTexImageProc(void* ptr) { _glGetCompressedTexImage = ptr; }
                void glGetCompressedTexImage(GLenum target, GLint level, GLvoid *data) const;

            protected:

                ~Extensions() {}
                
                bool    _isMultiTexturingSupported;
                bool    _isTextureFilterAnisotropicSupported;
                bool    _isTextureCompressionARBSupported;
                bool    _isTextureCompressionS3TCSupported;
                bool    _isTextureMirroredRepeatSupported;
                bool    _isTextureEdgeClampSupported;
                bool    _isTextureBorderClampSupported;
                bool    _isGenerateMipMapSupported;
                
                GLint   _maxTextureSize;

                void*   _glCompressedTexImage2D;
                void*   _glCompressedTexSubImage2D;
                void*   _glGetCompressedTexImage;

        };
        
        /** Function to call to get the extension of a specified context.
          * If the Exentsion object for that context has not yet been created then 
          * and the 'createIfNotInitalized' flag been set to false then returns NULL.
          * If 'createIfNotInitalized' is true then the Extensions object is 
          * automatically created.  However, in this case the extension object 
          * only be created with the graphics context associated with ContextID..*/
        static Extensions* getExtensions(unsigned int contextID,bool createIfNotInitalized);

        /** setExtensions allows users to override the extensions across graphics contexts.
          * typically used when you have different extensions supported across graphics pipes
          * but need to ensure that they all use the same low common denominator extensions.*/
        static void setExtensions(unsigned int contextID,Extensions* extensions);


        /** Helper method which does the creation of the texture itself, but does not set or use texture binding. 
          * Note, do not call this method directly unless you are implementing your own Subload callback*/
        void applyTexImage2D_load(State& state, GLenum target, const Image* image, GLsizei width, GLsizei height,GLsizei numMipmapLevels) const;
        
        /** Helper method which subloads images to the texture itself, but does not set or use texture binding. 
          * Note, do not call this method directly unless you are implementing your own Subload callback*/
        void applyTexImage2D_subload(State& state, GLenum target, const Image* image, GLsizei width, GLsizei height,GLsizei numMipmapLevels) const;

    protected :

        virtual ~Texture();

        virtual void computeInternalFormat() const = 0;
        
        void computeInternalFormatWithImage(const osg::Image& image) const;

        void computeRequiredTextureDimensions(State& state, const osg::Image& image,GLsizei& width, GLsizei& height,GLsizei& numMipmapLevels) const;

        bool isCompressedInternalFormat(GLint internalFormat) const;

        /** Helper method which does setting of texture paramters. */
        void applyTexParameters(GLenum target, State& state) const;
        


        /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
        int compareTexture(const Texture& rhs) const;

        typedef buffered_value<unsigned int> TexParameterDirtyList;
        mutable TexParameterDirtyList _texParametersDirtyList;

        WrapMode _wrap_s;
        WrapMode _wrap_t;
        WrapMode _wrap_r;

        FilterMode      _min_filter;
        FilterMode      _mag_filter;
        float           _maxAnisotropy;
        bool            _useHardwareMipMapGeneration;
        bool            _unrefImageDataAfterApply;

        Vec4 _borderColor;

        InternalFormatMode  _internalFormatMode;
        mutable GLint       _internalFormat;
        
        
    public:
    
        class TextureObject : public osg::Referenced
        {
        public:
        
            inline TextureObject(GLuint    id,GLenum target):
                _id(id),
                _target(target),
                _numMipmapLevels(0),
                _internalFormat(0),
                _width(0),
                _height(0),
                _depth(0),
                _border(0),
                _allocated(false),
                _timeStamp(0) {}
                
            inline TextureObject(GLuint    id,
                          GLenum    target,
                          GLint     numMipmapLevels,
                          GLenum    internalFormat,
                          GLsizei   width,
                          GLsizei   height,
                          GLsizei   depth,
                          GLint     border):
                _id(id),
                _target(target),
                _numMipmapLevels(numMipmapLevels),
                _internalFormat(internalFormat),
                _width(width),
                _height(height),
                _depth(depth),
                _border(border),
                _allocated(false),
                _timeStamp(0) {}
                          
            inline bool match(GLenum    target,
                       GLint     numMipmapLevels,
                       GLenum    internalFormat,
                       GLsizei   width,
                       GLsizei   height,
                       GLsizei   depth,
                       GLint     border)
            {
                return isReusable() &&
                       (_target == target) &&
                       (_numMipmapLevels == numMipmapLevels) &&
                       (_internalFormat == internalFormat) &&
                       (_width == width) &&
                       (_height == height) &&
                       (_depth == depth) &&
                       (_border == border);
            }
            
            
            inline void bind()
            {
                glBindTexture( _target, _id);
            }


            inline void setAllocated(bool allocated=true) { _allocated = allocated; }
            
            inline void setAllocated(GLint     numMipmapLevels,
                              GLenum    internalFormat,
                              GLsizei   width,
                              GLsizei   height,
                              GLsizei   depth,
                              GLint     border)
            {
                _allocated=true;
                _numMipmapLevels = numMipmapLevels;
                _internalFormat = internalFormat;
                _width = width;
                _height = height;
                _depth = depth;
                _border = border;
            }
        
            inline bool isAllocated() const { return _allocated; }

            inline bool isReusable() const { return _allocated && _width!=0; }

            GLuint      _id;
            GLenum      _target;
            GLint       _numMipmapLevels;
            GLenum      _internalFormat;
            GLsizei     _width;
            GLsizei     _height;
            GLsizei     _depth;
            GLint       _border;

            bool        _allocated;
            double      _timeStamp;
        };
        
        typedef std::list< ref_ptr<TextureObject> > TextureObjectList;
        typedef std::map<unsigned int, TextureObjectList > TextureObjectListMap; 

        /** take the active texture objects from the Texture and place them in the specified TextureObjectListMap.*/
        void takeTextureObjects(TextureObjectListMap& toblm);
        
        typedef buffered_object< ref_ptr<TextureObject> >  TextureObjectBuffer;
        mutable TextureObjectBuffer _textureObjectBuffer;


        class SG_EXPORT TextureObjectManager : public osg::Referenced
        {
        public:        

            virtual TextureObject* generateTextureObject(unsigned int contextID,GLenum target);

            virtual TextureObject* generateTextureObject(unsigned int contextID,
                                                         GLenum    target,
                                                         GLint     numMipmapLevels,
                                                         GLenum    internalFormat,
                                                         GLsizei   width,
                                                         GLsizei   height,
                                                         GLsizei   depth,
                                                         GLint     border);

            virtual TextureObject* reuseTextureObject(unsigned int contextID,
                                                      GLenum    target,
                                                      GLint     numMipmapLevels,
                                                      GLenum    internalFormat,
                                                      GLsizei   width,
                                                      GLsizei   height,
                                                      GLsizei   depth,
                                                      GLint     border);
            
            inline TextureObject* reuseOrGenerateTextureObject(unsigned int contextID,
                                                      GLenum    target,
                                                      GLint     numMipmapLevels,
                                                      GLenum    internalFormat,
                                                      GLsizei   width,
                                                      GLsizei   height,
                                                      GLsizei   depth,
                                                      GLint     border)
            {
                TextureObject* to = reuseTextureObject(contextID,target,numMipmapLevels,internalFormat,width,height,depth,border);
                if (to) return to;
                else return generateTextureObject(contextID,target,numMipmapLevels,internalFormat,width,height,depth,border);
            }                                                      

            virtual void addTextureObjects(TextureObjectListMap& toblm);
            
            virtual void addTextureObjectsFrom(Texture& texture);
            
            virtual void flushTextureObjects(unsigned int contextID,double currentTime, double& availableTime);
            

            void setExpiryDelay(double expiryDelay) { _expiryDelay = expiryDelay; }

            double getExpiryDelay() const { return _expiryDelay; }
            
            // how long to keep unsed texture objects around for before deleting.
            double                  _expiryDelay;
            
            TextureObjectListMap    _textureObjectListMap;
            
        };
        
        
        static void setTextureObjectManager(TextureObjectManager* tom);
        static TextureObjectManager* getTextureObjectManager();
        static void flushTextureObjects(unsigned int contextID,double currentTime, double& availableTime);
        
        

};

}

#endif
