/* -*-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/Types>
#include <osg/Image>
#include <osg/StateAttribute>
#include <osg/ref_ptr>
#include <osg/Vec4>
#include <osg/buffered_value>


// 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_IMAGE_SIZE_ARB         0x86A0
#define GL_TEXTURE_COMPRESSED_ARB         0x86A1
#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2
#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3
#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; }

        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;


        /** Get the handle to the texture object for the current context.*/
        /** return the OpenGL texture object for specified context.*/
        inline GLuint& getTextureObject(uint contextID) const
        {
            // get the globj for the current contextID.
            return _handleList[contextID];
        }

        inline uint& getModifiedTag(uint contextID) const
        {
            // get the modified tag for the current contextID.
            return _modifiedTag[contextID];
        }

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

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

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


        /** use deleteTextureObject instead of glDeleteTextures to allow
          * OpenGL texture objects to cached until they can be deleted
          * by the OpenGL context in which they were created, specified
          * by contextID.*/
        static void deleteTextureObject(uint contextID,GLuint handle);

        /** flush all the cached display list which need to be deleted
          * in the OpenGL context related to contextID.*/
        static void flushDeletedTextureObjects(uint contextID);
        
        /** 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 Extensions : public osg::Referenced
        {
            public:
                Extensions();

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

                bool isTextureFilterAnisotropicSupported() const { return _isTextureFilterAnisotropicSupported; }
                bool isTextureCompressionARBSupported() const { return _isTextureCompressionARBSupported; }
                bool isTextureCompressionS3TCSupported() const { return _isTextureCompressionS3TCSupported; }
                bool isTextureMirroredRepeatSupported() const { return _isTextureMirroredRepeatSupported; }
                bool isTextureEdgeClampSupported() const { return _isTextureEdgeClampSupported; }
                bool isTextureBorderClampSupported() const { return _isTextureBorderClampSupported; }

                GLint maxTextureSize() const { return _maxTextureSize; }

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

                void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) const;

            protected:

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

                void*   _glCompressedTexImage2D;

        };
        
        /** 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 const Extensions* getExtensions(uint 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(uint contextID,Extensions* extensions);

    protected :

        virtual ~Texture();

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

        bool isCompressedInternalFormat(GLint internalFormat) const;

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

        /** Helper method which does the creation of the texture itself, and
          * does not set or use texture binding. */
        void applyTexImage2D(GLenum target, Image* image, State& state, GLsizei& width, GLsizei& height,GLsizei& numMimpmapLevels) const;

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

        typedef buffered_value<GLuint> TextureNameList;
        mutable TextureNameList _handleList;

        typedef buffered_value<uint> ImageModifiedTag;
        mutable ImageModifiedTag _modifiedTag;

        typedef buffered_value<uint> TexParameterDirtyList;
        mutable TexParameterDirtyList _texParametersDirtyList;

        WrapMode _wrap_s;
        WrapMode _wrap_t;
        WrapMode _wrap_r;

        FilterMode      _min_filter;
        FilterMode      _mag_filter;
        float           _maxAnisotropy;

        Vec4 _borderColor;

        InternalFormatMode  _internalFormatMode;
        mutable GLint       _internalFormat;

};

}

#endif
