//C++ header - Open Scene Graph - Copyright (C) 1998-2001 Robert Osfield
//Distributed under the terms of the GNU Library General Public License (LGPL)
//as published by the Free Software Foundation.

// -*-c++-*-

#ifndef OSG_TEXTURE
#define OSG_TEXTURE 1

#include <osg/GL>
#include <osg/Types>
#include <osg/Image>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/ref_ptr>

#include <vector>
#include <map>
#include <set>

// 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_GENERATE_MIPMAP_SGIS
#define GL_GENERATE_MIPMAP_SGIS           0x8191
#define GL_GENERATE_MIPMAP_HINT_SGIS      0x8192
#endif


namespace osg {

/** Texture state class which encapsulates OpenGl texture functionality.*/
class SG_EXPORT Texture : public StateAttribute
{

    public :
        
        Texture();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Texture(const Texture& text,const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            StateAttribute(text,copyop),
            _handleList(text._handleList),
            _modifiedTag(text._modifiedTag),
            _textureObjectSize(text._textureObjectSize),
            _image(copyop(text._image.get())),
            _textureUnit(text._textureUnit),
            _wrap_s(text._wrap_s),
            _wrap_t(text._wrap_t),
            _wrap_r(text._wrap_r),
            _min_filter(text._min_filter),
            _mag_filter(text._mag_filter),
            _internalFormatMode(text._internalFormatMode),
            _internalFormatValue(text._internalFormatValue),
            _textureWidth(text._textureWidth),
            _textureHeight(text._textureHeight),
            _subloadMode(text._subloadMode),
            _subloadOffsX(text._subloadOffsX),
            _subloadOffsY(text._subloadOffsY) {}

        META_StateAttribute(Texture,(Type)(TEXTURE_0+_textureUnit));
        
        /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
        virtual int compare(const StateAttribute& rhs) const;

        virtual void setStateSetModes(StateSet& ds,const GLModeValue value) const
        {
            ds.setMode(GL_TEXTURE_2D,value);
        }

        /** Set the texture image. */
        void setImage(Image* image);

        /** Get the texture image. */
        Image* getImage() { return _image.get(); }

        /** Get the const texture image. */
        inline const Image* getImage() const { return _image.get(); }

        /** Copy pixels into a 2D texture image.As per glCopyTexImage2D.
          * Creates an OpenGL texture object from the current OpenGL background
          * framebuffer contents at pos \a x, \a y with width \a width and
          * height \a height. \a width and \a height must be a power of two.
          */
	void copyTexImage2D(State& state, int x, int y, int width, int height );

        /** Copy a two-dimensional texture subimage. As per glCopyTexSubImage2D.
          * Updates portion of an existing OpenGL texture object from the current OpenGL background
          * framebuffer contents at pos \a x, \a y with width \a width and
          * height \a height. \a width and \a height must be a power of two,
          * and writing into the texture with offset \a xoffset and \a yoffset.
          */
	void copyTexSubImage2D(State& state, int xoffset, int yoffset, int x, int y, int width, int height );

        /** Set the texture unit.
          * Valid values are 0,1,2,3.
          * Default value of texture unit is 0.
          * note, multi-texturing not fully implemented yet... April 2001.
          */        
        inline void setTextureUnit(const unsigned int textureUnit) { _textureUnit = textureUnit; }
        
        /** get the texture unit.*/
        inline const unsigned int getTextureUnit() const { return _textureUnit; }


        enum WrapParameter {
            WRAP_S,
            WRAP_T,
            WRAP_R
        };

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

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


        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,
	    ANISOTROPIC               = GL_TEXTURE_MAX_ANISOTROPY_EXT
        };

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


        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 _internalFormatValue.
          */
        inline void setInternalFormatMode(const InternalFormatMode mode) { _internalFormatMode = mode; }

        /** Get the internal format mode.*/
        inline const 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 setInternalFormatValue(const int internalFormat)
        {
            _internalFormatMode = USE_USER_DEFINED_FORMAT;
            _internalFormatValue = internalFormat;
        }

        /** Get the internal format to use when creating OpenGL textures.*/
        inline const int getInternalFormatValue() const { return _internalFormatValue; }
        
        /** return the OpenGL texture object for specified context.*/
        inline const uint getTextureObject(const uint contextID) const { if (contextID<_handleList.size()) return _handleList[contextID]; else return 0;}

        /** return the memory size of texture object.
          * Texture object size can be used for estimating the cost of
          * uploading the texture to graphics hardware, which in turn can
          * be used for setting texture residence priorities. */
        inline const uint getTextureObjectSize() const { return _textureObjectSize; }

        enum SubloadMode {
            OFF,
            AUTO,
            IF_DIRTY
        };

        /** Set the texture subload mode. */
        inline void setSubloadMode(const SubloadMode mode) { _subloadMode = mode; }

        /** Get the texture subload mode. */
        inline const SubloadMode getSubloadMode() const { return _subloadMode; }

        /** Set the texture subload offsets. */
        inline void setSubloadOffset(const int x, const int y) {
            _subloadOffsX = x;
            _subloadOffsY = y;
        }

        /** Get the texture subload offsets. */
        inline void getSubloadOffset(int& x, int& y) const {
            x = _subloadOffsX;
            y = _subloadOffsY;
        }
        
        /** Get the handle to the texture object for the current context.*/
        inline GLuint& getHandle(const uint contextID) const
        {
            // pad out handle list if required.
            while (_handleList.size()<=contextID)
                _handleList.push_back(0);

            // get the globj for the current contextID.
            return _handleList[contextID];
        }

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

        /** On first apply (unless already compiled), create the minmapped 
          * texture and bind it, subsequent apply will simple bind to texture.*/
        virtual void apply(State& state) const;
        
        /** Compile the texture mip maps.  Implemented by simply calling apply().*/
        virtual void compile(State& state) const;


        /** Method which does the creation of the texture itself, and
          * does not set or use texture binding. */
        virtual void applyImmediateMode(State& state) const;


        /** 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);
        

    protected :

        virtual ~Texture();

        typedef std::vector<GLuint> TextureNameList;
        mutable TextureNameList _handleList;

        typedef std::vector<uint> ImageModifiedTag;
        mutable ImageModifiedTag _modifiedTag;

        // size of the created OpenGL texture object, may be estimated. 
        mutable uint _textureObjectSize;

        // not ideal that _image is mutable, but its required since
        // Image::ensureDimensionsArePowerOfTwo() can only be called
        // in a valid OpenGL context, a therefore within an Texture::apply
        // which is const...
        mutable ref_ptr<Image> _image;

        unsigned int _textureUnit;

        WrapMode _wrap_s;
        WrapMode _wrap_t;
        WrapMode _wrap_r;

        FilterMode _min_filter;
        FilterMode _mag_filter;

        InternalFormatMode  _internalFormatMode;
        int                 _internalFormatValue;

        // subloaded images can have different texture and image sizes.
        mutable unsigned int _textureWidth, _textureHeight;

        SubloadMode _subloadMode;
        unsigned int _subloadOffsX, _subloadOffsY;

        // static cache of deleted display lists which can only 
        // by completely deleted once the appropriate OpenGL context
        // is set.
        typedef std::map<uint,std::set<uint> > DeletedTextureObjectCache;
        static DeletedTextureObjectCache s_deletedTextureObjectCache;

};

}

#endif
