/* -*-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_STATE
#define OSG_STATE 1

#include <osg/Export>
#include <osg/StateSet>
#include <osg/Matrix>

#include <osg/FrameStamp>
#include <osg/DisplaySettings>
#include <osg/Polytope>
#include <osg/Viewport>

#include <vector>
#include <map>

namespace osg {

#ifndef GL_TEXTURE0
#define GL_TEXTURE0 0x84C0
#endif

#ifndef GL_FOG_COORDINATE_ARRAY
    #ifdef GL_FOG_COORDINATE_ARRAY_EXT
        #define GL_FOG_COORDINATE_ARRAY GL_FOG_COORDINATE_ARRAY_EXT
    #else
         #define GL_FOG_COORDINATE_ARRAY 0x8457
    #endif
#endif

#ifndef GL_SECONDARY_COLOR_ARRAY
    #ifdef GL_SECONDARY_COLOR_ARRAY_EXT
        #define GL_SECONDARY_COLOR_ARRAY GL_SECONDARY_COLOR_ARRAY_EXT
    #else
         #define GL_SECONDARY_COLOR_ARRAY 0x845E
    #endif
#endif

/** macro for use with osg::StateAttrbiute::apply methods for detected and 
  * reporting OpenGL error messages.*/
#define OSG_GL_DEBUG(message) \
    if (state.getFineGrainedErrorDetection()) \
    { \
        GLenum errorNo = glGetError(); \
        if (errorNo!=GL_NO_ERROR) \
        { \
            osg::notify(WARN)<<"Warning: detected OpenGL error '"<<gluErrorString(errorNo)<<" "<<message<<endl; \
        }\
    }

/** State class for managing a state stack.
  * Lazy state updating is used to minimize state changes.
  */
class SG_EXPORT State : public Referenced
{
    public :
    
        State();

        /** push stateset onto state stack.*/
        void pushStateSet(const StateSet* dstate);

        /** pop stateset off state stack.*/
        void popStateSet();
       
        /** pop all stateset's off state stack, ensuring its empty ready for the next frame.
          * note, to return OpenGL to default state, one should do any state.popAllStatSets(); state.apply().*/
        void popAllStateSets();

        /** copy the modes and attributes which captures the current state.*/
        void captureCurrentState(StateSet& stateset) const;

        /** reset the state object to an empty stack.*/
        void reset();
        
        inline const Viewport* getCurrentViewport() const
        {
            return static_cast<const Viewport*>(getLastAppliedAttribute(osg::StateAttribute::VIEWPORT));
        }
        

        void setInitialViewMatrix(const osg::RefMatrix* matrix);

        inline const osg::Matrix& getInitialViewMatrix() const { return *_initialViewMatrix; }
        inline const osg::Matrix& getInitialInverseViewMatrix() const { return _initialInverseViewMatrix; }

        inline void applyProjectionMatrix(const osg::RefMatrix* matrix)
        {
            if (_projection!=matrix)
            {
                glMatrixMode( GL_PROJECTION );
                if (matrix) 
                {
                    _projection=matrix;
                    glLoadMatrix(matrix->ptr());
                }
                else
                {
                    _projection=_identity;
                    glLoadIdentity();
                }
                glMatrixMode( GL_MODELVIEW );
            }
        }
        
        inline const osg::Matrix& getProjectionMatrix() const
        {
            return *_projection;
        }

        inline void applyModelViewMatrix(const osg::RefMatrix* matrix)
        {
            if (_modelView!=matrix)
            {
                if (matrix)
                {
                    _modelView=matrix;
                    glLoadMatrix(matrix->ptr());
                }
                else 
                {
                    _modelView=_identity;
                    glLoadIdentity();
                }
            }
        }

        const osg::Matrix& getModelViewMatrix() const
        {
            return *_modelView;
        }


        Polytope getViewFrustum() const;


        /** Apply stateset.*/
        void apply(const StateSet* dstate);

        /** Apply the state.*/
        void apply();


        /** Apply an OpenGL mode if required. */
        inline bool applyMode(StateAttribute::GLMode mode,bool enabled)
        {
            ModeStack& ms = _modeMap[mode];
            ms.changed = true;    
            return applyMode(mode,enabled,ms);
        }

        inline bool applyTextureMode(unsigned int unit, StateAttribute::GLMode mode,bool enabled)
        {
            if (setActiveTextureUnit(unit))
            {
                ModeMap& modeMap = getOrCreateTextureModeMap(unit);
                ModeStack& ms = modeMap[mode];
                ms.changed = true;
                return applyMode(mode,enabled,ms);
            }
            else
                return false;
        }

        /** Apply an attribute if required. */
        inline bool applyAttribute(const StateAttribute* attribute)
        {
            AttributeStack& as = _attributeMap[attribute->getType()];
            as.changed = true;
            return applyAttribute(attribute,as);
        }

        inline bool applyTextureAttribute(unsigned int unit, const StateAttribute* attribute)
        {
            if (setActiveTextureUnit(unit))
            {
                AttributeMap& attributeMap = getOrCreateTextureAttributeMap(unit);
                AttributeStack& as = attributeMap[attribute->getType()];
                as.changed = true;
                return applyAttribute(attribute,as);
            }
            else
                return false;
        }
       
        /** Mode has been set externally, update state to reflect this setting.*/
        void haveAppliedMode(StateAttribute::GLMode mode,StateAttribute::GLModeValue value);
        
        /** Mode has been set externally, therefore dirty the associated mode in osg::State
          * so it is applied on next call to osg::State::apply(..)*/
        void haveAppliedMode(StateAttribute::GLMode mode);

        /** Attribute has been applied externally, update state to reflect this setting.*/
        void haveAppliedAttribute(const StateAttribute* attribute);

        /** Attribute has been applied externally, 
          * and therefore this attribute type has been dirtied 
          * and will need to be re-appplied on next osg::State.apply(..).
          * note, if you have an osg::StateAttribute which you have applied externally
          * then use the have_applied(attribute) method as this will the osg::State to
          * track the current state more accuratly and enable lazy state updating such
          * that only changed state will be applied.*/
        void haveAppliedAttribute(StateAttribute::Type type);

        /** Get whether the current specified mode is enabled (true) or disabled (false).*/ 
        bool getLastAppliedMode(StateAttribute::GLMode mode) const;
        
        /** Get the current specified attribute, return NULL is one has not yet been applied.*/ 
        const StateAttribute* getLastAppliedAttribute(StateAttribute::Type type) const;
        


        /** texture Mode has been set externally, update state to reflect this setting.*/
        void haveAppliedTextureMode(unsigned int unit, StateAttribute::GLMode mode,StateAttribute::GLModeValue value);
        
        /** texture Mode has been set externally, therefore dirty the associated mode in osg::State
          * so it is applied on next call to osg::State::apply(..)*/
        void haveAppliedTextureMode(unsigned int unit, StateAttribute::GLMode mode);

        /** texture Attribute has been applied externally, update state to reflect this setting.*/
        void haveAppliedTextureAttribute(unsigned int unit, const StateAttribute* attribute);

        /** texture Attribute has been applied externally, 
          * and therefore this attribute type has been dirtied 
          * and will need to be re-appplied on next osg::State.apply(..).
          * note, if you have an osg::StateAttribute which you have applied externally
          * then use the have_applied(attribute) method as this will the osg::State to
          * track the current state more accuratly and enable lazy state updating such
          * that only changed state will be applied.*/
        void haveAppliedTextureAttribute(unsigned int unit, StateAttribute::Type type);

        /** Get whether the current specified texture mode is enabled (true) or disabled (false).*/ 
        bool getLastAppliedTextureMode(unsigned int unit, StateAttribute::GLMode mode) const;
        
        /** Get the current specified texture attribute, return NULL is one has not yet been applied.*/ 
        const StateAttribute* getLastAppliedTextureAttribute(unsigned int unit, StateAttribute::Type type) const;


        /** Dirty the modes previously applied in osg::State.*/
        void dirtyAllModes();

        /** Dirty the modes attributes previously applied in osg::State.*/
        void dirtyAllAttributes();

        /** disable the vertex, normal, color, tex coords, secenday color, fog coord and index arrays.*/
        void disableAllVertexArrays();

        /** dirty the vertex, normal, color, tex coords, secenday color, fog coord and index arrays.*/
        void dirtyAllVertexArrays();


        /** Wrapper around glInterleavedArrays(..).
          * also resets the internal array points and modes within osg::State to keep the other
          * vertex array operations consistent. */
        void setInterleavedArrays( GLenum format, GLsizei stride, const GLvoid* pointer);


        /** wrapper around glEnableClientState(GL_VERTEX_ARRAY);glVertexPointer(..);
          * note, only updates values that change.*/
        inline void setVertexPointer( GLint size, GLenum type,
                                      GLsizei stride, const GLvoid *ptr )
        {
            if (!_vertexArray._enabled || _vertexArray._dirty)
            {
                _vertexArray._enabled = true;
                glEnableClientState(GL_VERTEX_ARRAY);
            }
            //if (_vertexArray._pointer!=ptr || _vertexArray._dirty)
            {
                _vertexArray._pointer=ptr;
                glVertexPointer( size, type, stride, ptr );
            }
            _vertexArray._dirty = false;
        }
        
        /** wrapper glDisableClientState(GL_VERTEX_ARRAY).
          * note, only updates values that change.*/
        inline void disableVertexPointer()
        {
            if (_vertexArray._enabled || _vertexArray._dirty)
            {
                _vertexArray._enabled = false;
                _vertexArray._dirty = false;
                glDisableClientState(GL_VERTEX_ARRAY);
            }
        }

        inline void dirtyVertexPointer()
        {
            _vertexArray._pointer = 0; 
            _vertexArray._dirty = true; 
        }

        /** wrapper around glEnableClientState(GL_NORMAL_ARRAY);glNormalPointer(..);
          * note, only updates values that change.*/
        inline void setNormalPointer( GLenum type, GLsizei stride,
                                      const GLvoid *ptr )
        {
            if (!_normalArray._enabled || _normalArray._dirty)
            {
                _normalArray._enabled = true;
                glEnableClientState(GL_NORMAL_ARRAY);
            }
            //if (_normalArray._pointer!=ptr || _normalArray._dirty)
            {
                _normalArray._pointer=ptr;
                glNormalPointer( type, stride, ptr );
            }
            _normalArray._dirty = false;
        }

        /** wrapper around glDisableClientState(GL_NORMAL_ARRAY);
          * note, only updates values that change.*/
        inline void disableNormalPointer()
        {
            if (_normalArray._enabled || _normalArray._dirty)
            {
                _normalArray._enabled = false;
                _normalArray._dirty = false;
                glDisableClientState(GL_NORMAL_ARRAY);
            }
        }

        inline void dirtyNormalPointer()
        {
            _normalArray._pointer = 0;
            _normalArray._dirty = true;
        }

        /** wrapper around glEnableClientState(GL_COLOR_ARRAY);glColorPointer(..);
          * note, only updates values that change.*/
        inline void setColorPointer( GLint size, GLenum type,
                                     GLsizei stride, const GLvoid *ptr )
        {
            if (!_colorArray._enabled || _colorArray._dirty)
            {
                _colorArray._enabled = true;
                glEnableClientState(GL_COLOR_ARRAY);
            }
            //if (_colorArray._pointer!=ptr || _colorArray._dirty)
            {
                _colorArray._pointer=ptr;
                glColorPointer( size, type, stride, ptr );
            }
            _colorArray._dirty = false;
        }

        /** wrapper around glDisableClientState(GL_COLOR_ARRAY);
          * note, only updates values that change.*/
        inline void disableColorPointer()
        {
            if (_colorArray._enabled || _colorArray._dirty)
            {
                _colorArray._enabled = false;
                _colorArray._dirty = false;
                glDisableClientState(GL_COLOR_ARRAY);
            }
        }

        inline void dirtyColorPointer()
        { 
            _colorArray._pointer = 0;
            _colorArray._dirty = true;
        }


        inline bool isSecondaryColorSupported() const { return _isSecondaryColorSupportResolved?_isSecondaryColorSupported:computeSecondaryColorSupported(); }

        /** wrapper around glEnableClientState(GL_SECONDARY_COLOR_ARRAY);glSecondayColorPointer(..);
          * note, only updates values that change.*/
        void setSecondaryColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *ptr );

        /** wrapper around glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
          * note, only updates values that change.*/
        inline void disableSecondaryColorPointer()
        {
            if (_secondaryColorArray._enabled || _secondaryColorArray._dirty)
            {
                _secondaryColorArray._enabled = false;
                _secondaryColorArray._dirty = false;
                if (isSecondaryColorSupported()) glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
            }
        }

        inline void dirtySecondaryColorPointer()
        {
            _secondaryColorArray._pointer = 0;
            _secondaryColorArray._dirty = true;
        }

        /** wrapper around glEnableClientState(GL_INDEX_ARRAY);glIndexPointer(..);
          * note, only updates values that change.*/
        inline void setIndexPointer( GLenum type, GLsizei stride,
                              const GLvoid *ptr )
        {
            if (!_indexArray._enabled || _indexArray._dirty)
            {
                _indexArray._enabled = true;
                glEnableClientState(GL_INDEX_ARRAY);
            }
            //if (_indexArray._pointer!=ptr || _indexArray._dirty)
            {
                _indexArray._pointer=ptr;
                glIndexPointer( type, stride, ptr );
            }
            _indexArray._dirty = false;
        }

        /** wrapper around glDisableClientState(GL_INDEX_ARRAY);
          * note, only updates values that change.*/
        inline void disableIndexPointer()
        {
            if (_indexArray._enabled || _indexArray._dirty)
            {
                _indexArray._enabled = false;
                _indexArray._dirty = false;
                glDisableClientState(GL_INDEX_ARRAY);
            }
        }

        inline void dirtyIndexPointer()
        {
            _indexArray._pointer = 0;
            _indexArray._dirty = true;
        }


        inline bool isFogCoordSupported() const { return _isFogCoordSupportResolved?_isFogCoordSupported:computeFogCoordSupported(); }

        /** wrapper around glEnableClientState(GL_FOG_COORDINATE_ARRAY);glFogCoordPointer(..);
          * note, only updates values that change.*/
        void setFogCoordPointer( GLenum type, GLsizei stride, const GLvoid *ptr );

        /** wrapper around glDisableClientState(GL_FOG_COORDINATE_ARRAY);
          * note, only updates values that change.*/
        inline void disableFogCoordPointer()
        {
            if (_fogArray._enabled || _fogArray._dirty)
            {
                _fogArray._enabled = false;
                _fogArray._dirty = false;
                if (isFogCoordSupported()) glDisableClientState(GL_FOG_COORDINATE_ARRAY);
            }
        }

        inline void dirtyFogCoordPointer()
        {
            _fogArray._pointer = 0;
            _fogArray._dirty = true;
        }


        /** wrapper around glEnableClientState(GL_TEXTURE_COORD_ARRAY);glTexCoordPointer(..);
          * note, only updates values that change.*/
        inline void setTexCoordPointer( unsigned int unit,
                                        GLint size, GLenum type,
                                        GLsizei stride, const GLvoid *ptr )
        {
            if (setClientActiveTextureUnit(unit))
            {
                if ( unit >= _texCoordArrayList.size()) _texCoordArrayList.resize(unit+1);
                EnabledArrayPair& eap = _texCoordArrayList[unit];

                if (!eap._enabled || eap._dirty)
                {
                    eap._enabled = true;
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                }
                //if (eap._pointer!=ptr || eap._dirty)
                {
                    glTexCoordPointer( size, type, stride, ptr );
                    eap._pointer = ptr;
                }
                eap._dirty = false;
            }
        }

        /** wrapper around glDisableClientState(GL_TEXTURE_COORD_ARRAY);
          * note, only updates values that change.*/
        inline void disableTexCoordPointer( unsigned int unit )
        {
            if (setClientActiveTextureUnit(unit))
            {
                if ( unit >= _texCoordArrayList.size()) _texCoordArrayList.resize(unit+1);
                EnabledArrayPair& eap = _texCoordArrayList[unit];

                if (eap._enabled || eap._dirty)
                {
                    eap._enabled = false;
                    eap._dirty = false;
                    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                }
            }
        }

        inline void dirtyTexCoordPointer( unsigned int unit )
        {
            if ( unit >= _texCoordArrayList.size()) return; // _texCoordArrayList.resize(unit+1);
            EnabledArrayPair& eap = _texCoordArrayList[unit];
            eap._pointer = 0;
            eap._dirty = true;
        }


        inline void disableTexCoordPointersAboveAndIncluding( unsigned int unit )
        {
            while (unit<_texCoordArrayList.size())
            {
                EnabledArrayPair& eap = _texCoordArrayList[unit];
                if (eap._enabled || eap._dirty)
                {
                    if (setClientActiveTextureUnit(unit))
                    {
                        eap._enabled = false;
                        eap._dirty = false;
                        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                    }
                }
                ++unit;
            }
        }

        inline void dirtyTexCoordPointersAboveAndIncluding( unsigned int unit )
        {
            while (unit<_texCoordArrayList.size())
            {
                EnabledArrayPair& eap = _texCoordArrayList[unit];
                eap._pointer = 0;
                eap._dirty = true;
                ++unit;
            }
        }

        /** set the current tex coord array texture unit, return true if selected, false if selection failed such as when multitexturing is not supported.
          * note, only updates values that change.*/
        bool setClientActiveTextureUnit( unsigned int unit );


        /** set the current texture unit, return true if selected, false if selection failed such as when multitexturing is not supported.
          * note, only updates values that change.*/
        bool setActiveTextureUnit( unsigned int unit );
        
        /** wrapper around glEnableVertexAttribArrayARB(index);glVertexAttribPointerARB(..);
          * note, only updates values that change.*/
        void setVertexAttribPointer( unsigned int index,
                                     GLint size, GLenum type, GLboolean normalized, 
                                     GLsizei stride, const GLvoid *ptr );
        
        /** wrapper around DisableVertexAttribArrayARB(index);
          * note, only updates values that change.*/
        void disableVertexAttribPointer( unsigned int index );
      
        void disableVertexAttribPointersAboveAndIncluding( unsigned int index );
        
        inline void dirtyVertexAttribPointersAboveAndIncluding( unsigned int index )
        {
            while (index<_vertexAttribArrayList.size())
            {
                EnabledArrayPair& eap = _vertexAttribArrayList[index];
                eap._pointer = 0;
                eap._dirty = true;
                ++index;
            }
        }

        bool isVertexBufferObjectSupported() const { return _isVertexBufferObjectSupportResolved?_isVertexBufferObjectSupported:computeVertexBufferObjectSupported(); }


        /** Set the current OpenGL context uniqueID.
            Note, it is the application developers responsibility to
            set up unique ID for each OpenGL context.  This value is
            then used by osg::StateAttribure's and osg::Drawable's to
            help manage OpenGL display list and texture binds appropriate
            for each context, the contextID simply acts as an index local
            arrays that they maintain for the purpose.
            Typical settings for contextID are 0,1,2,3... up to the maximum
            number of graphics contexts you have setup.
            By default contextID is 0.*/ 
        inline void setContextID(unsigned int contextID) { _contextID=contextID; }

        /** Get the current OpenGL context unique ID.*/ 
        inline unsigned int getContextID() const { return _contextID; }
        
        
        /** Set the frame stamp for the current frame.*/
        inline void setFrameStamp(FrameStamp* fs) { _frameStamp = fs; }

        /** Set the frame stamp for the current frame.*/
        inline const FrameStamp* getFrameStamp() const { return _frameStamp.get(); }
        
                
        /** Set the DisplaySettings. Note, nothing is applied, the visual settings are just used
          * used in the State object to pass the current visual settings to Drawables
          * during rendering. */
        inline void setDisplaySettings(DisplaySettings* vs) { _displaySettings = vs; }
        
        /** Get the DisplaySettings */
        inline const DisplaySettings* getDisplaySettings() const { return _displaySettings.get(); }

        typedef std::pair<const StateAttribute*,StateAttribute::OverrideValue>  AttributePair;
        typedef std::vector<AttributePair>                                      AttributeVec;
        typedef std::vector<StateAttribute::GLModeValue>                        ValueVec;
        
        
        /** Set flag for early termination of the draw traversal.*/
        void setAbortRenderingPtr(bool* abortPtr) { _abortRenderingPtr = abortPtr; }

        /** Get flag for early termination of the draw traversal, 
          * if true steps should be taken to complete rendering early.*/
        bool getAbortRendering() const { return _abortRenderingPtr!=0?(*_abortRenderingPtr):false; }

        void setReportGLErrors(bool flag) { _reportGLErrors = flag; }
        bool getReportGLErrors() const { return _reportGLErrors; }

        bool checkGLErrors(const char* str) const;
        bool checkGLErrors(StateAttribute::GLMode mode) const;
        bool checkGLErrors(const StateAttribute* attribute) const;

    protected:

        virtual ~State();

        unsigned int                _contextID;
        ref_ptr<FrameStamp>         _frameStamp;
        
        ref_ptr<const RefMatrix>    _identity;
        ref_ptr<const RefMatrix>    _initialViewMatrix;
        ref_ptr<const RefMatrix>    _projection;
        ref_ptr<const RefMatrix>    _modelView;
        
        Matrix                      _initialInverseViewMatrix;

        ref_ptr<DisplaySettings>    _displaySettings;
        
        bool*                       _abortRenderingPtr;
        bool                        _reportGLErrors;

        struct ModeStack
        {
            ModeStack()
            {
                changed = false;
                last_applied_value = false;
                global_default_value = false;
            }
        
            bool        changed;
            bool        last_applied_value;
            bool        global_default_value;
            ValueVec    valueVec;
        };



        struct AttributeStack
        {
            AttributeStack()
            {
                changed = false;
                last_applied_attribute = 0L;
                global_default_attribute = 0L;
            }

            /** apply an attribute if required, passing in attribute and appropriate attribute stack */
            bool                    changed;
            const StateAttribute*   last_applied_attribute;
            ref_ptr<StateAttribute> global_default_attribute;
            AttributeVec            attributeVec;
        };
        

        /** apply an OpenGL mode if required, passing in mode, enable flag and appropriate mode stack */
        inline bool applyMode(StateAttribute::GLMode mode,bool enabled,ModeStack& ms)
        {
            if (ms.last_applied_value != enabled)
            {
                ms.last_applied_value = enabled;

                if (enabled) glEnable(mode);
                else glDisable(mode);

                if (_reportGLErrors) checkGLErrors(mode);

                return true;
            }
            else
                return false;
        }


        /** apply an attribute if required, passing in attribute and appropriate attribute stack */
        inline bool applyAttribute(const StateAttribute* attribute,AttributeStack& as)
        {
            if (as.last_applied_attribute != attribute)
            {
                if (!as.global_default_attribute.valid()) as.global_default_attribute = dynamic_cast<StateAttribute*>(attribute->cloneType());

                as.last_applied_attribute = attribute;
                attribute->apply(*this);
                
                if (_reportGLErrors) checkGLErrors(attribute);
                
                return true;
            }
            else
                return false;
        }

        inline bool applyGlobalDefaultAttribute(AttributeStack& as)
        {
            if (as.last_applied_attribute != as.global_default_attribute.get())
            {
                as.last_applied_attribute = as.global_default_attribute.get();
                if (as.global_default_attribute.valid())
                {
                    as.global_default_attribute->apply(*this);
                    if (_reportGLErrors) checkGLErrors(as.global_default_attribute.get());
                }
                return true;
            }
            else
                return false;
        }
        

        typedef std::map<StateAttribute::GLMode,ModeStack>      ModeMap;
        typedef std::vector<ModeMap>                            TextureModeMapList;

        typedef std::map<StateAttribute::Type,AttributeStack>   AttributeMap;
        typedef std::vector<AttributeMap>                       TextureAttributeMapList;

        typedef std::vector<const StateSet*>                    StateSetStack;
        typedef std::vector<ref_ptr<const Matrix> >             MatrixStack;

        ModeMap                                                 _modeMap;
        AttributeMap                                            _attributeMap;

        TextureModeMapList                                      _textureModeMapList;
        TextureAttributeMapList                                 _textureAttributeMapList;
       
        StateSetStack                                           _drawStateStack;
        
        struct EnabledArrayPair
        {
            EnabledArrayPair():_dirty(true),_enabled(false),_normalized(0),_pointer(0) {}
            EnabledArrayPair(const EnabledArrayPair& eap):_dirty(eap._dirty), _enabled(eap._enabled),_normalized(eap._normalized),_pointer(eap._pointer) {}
            EnabledArrayPair& operator = (const EnabledArrayPair& eap) { _dirty=eap._dirty; _enabled=eap._enabled; _normalized=eap._normalized;_pointer=eap._pointer; return *this; }
            
            bool            _dirty;
            bool            _enabled;
            GLboolean       _normalized;
            const GLvoid*   _pointer;
        };
        
        typedef std::vector<EnabledArrayPair>                   EnabledTexCoordArrayList;
        typedef std::vector<EnabledArrayPair>                   EnabledVertexAttribArrayList;

        EnabledArrayPair                _vertexArray;
        EnabledArrayPair                _normalArray;
        EnabledArrayPair                _colorArray;
        EnabledArrayPair                _secondaryColorArray;
        EnabledArrayPair                _indexArray;
        EnabledArrayPair                _fogArray;
        EnabledTexCoordArrayList        _texCoordArrayList;
        EnabledVertexAttribArrayList    _vertexAttribArrayList;

        unsigned int                    _currentActiveTextureUnit;
        unsigned int                    _currentClientActiveTextureUnit;
        
        inline ModeMap& getOrCreateTextureModeMap(unsigned int unit)
        {        
            if (unit>=_textureModeMapList.size()) _textureModeMapList.resize(unit+1);
            return _textureModeMapList[unit];
        }


        inline AttributeMap& getOrCreateTextureAttributeMap(unsigned int unit)
        {        
            if (unit>=_textureAttributeMapList.size()) _textureAttributeMapList.resize(unit+1);
            return _textureAttributeMapList[unit];
        }
        
        inline void pushModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void pushAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);
        
        inline void popModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void popAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);

        inline void applyModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void applyAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);
        
        inline void applyModeMap(ModeMap& modeMap);
        inline void applyAttributeMap(AttributeMap& attributeMap);

        void haveAppliedMode(ModeMap& modeMap,StateAttribute::GLMode mode,StateAttribute::GLModeValue value);
        void haveAppliedMode(ModeMap& modeMap,StateAttribute::GLMode mode);
        void haveAppliedAttribute(AttributeMap& attributeMap,const StateAttribute* attribute);
        void haveAppliedAttribute(AttributeMap& attributeMap,StateAttribute::Type type);
        bool getLastAppliedMode(const ModeMap& modeMap,StateAttribute::GLMode mode) const;
        const StateAttribute* getLastAppliedAttribute(const AttributeMap& attributeMap,StateAttribute::Type type) const;


        mutable bool _isSecondaryColorSupportResolved;
        mutable bool _isSecondaryColorSupported;
        bool computeSecondaryColorSupported() const;

        mutable bool _isFogCoordSupportResolved;
        mutable bool _isFogCoordSupported;
        bool computeFogCoordSupported() const;

        mutable bool _isVertexBufferObjectSupportResolved;
        mutable bool _isVertexBufferObjectSupported;
        bool computeVertexBufferObjectSupported() const;

};

inline void State::pushModeList(ModeMap& modeMap,const StateSet::ModeList& modeList)
{
    for(StateSet::ModeList::const_iterator mitr=modeList.begin();
        mitr!=modeList.end();
        ++mitr)
    {
        // get the mode stack for incomming GLmode {mitr->first}.
        ModeStack& ms = modeMap[mitr->first];
        if (ms.valueVec.empty())
        {
            // first pair so simply push incomming pair to back.
            ms.valueVec.push_back(mitr->second);
        }
        else if ((ms.valueVec.back() & StateAttribute::OVERRIDE) && !(mitr->second & StateAttribute::PROTECTED)) // check the existing override flag 
        {
            // push existing back since override keeps the previoius value.
            ms.valueVec.push_back(ms.valueVec.back());
        }
        else 
        {
            // no override on so simply push incomming pair to back.
            ms.valueVec.push_back(mitr->second);
        }
        ms.changed = true;
    }
}

inline void State::pushAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList)
{
    for(StateSet::AttributeList::const_iterator aitr=attributeList.begin();
        aitr!=attributeList.end();
        ++aitr)
    {
        // get the attribute stack for incomming type {aitr->first}.
        AttributeStack& as = attributeMap[aitr->first];
        if (as.attributeVec.empty())
        {
            // first pair so simply push incomming pair to back.
            as.attributeVec.push_back(
                AttributePair(aitr->second.first.get(),aitr->second.second));
        }
        else if ((as.attributeVec.back().second & StateAttribute::OVERRIDE) && !(aitr->second.second & StateAttribute::PROTECTED)) // check the existing override flag 
        {
            // push existing back since override keeps the previoius value.
            as.attributeVec.push_back(as.attributeVec.back());
        }
        else 
        {
            // no override on so simply push incomming pair to back.
            as.attributeVec.push_back(
                AttributePair(aitr->second.first.get(),aitr->second.second));
        }
        as.changed = true;
    }
}

inline void State::popModeList(ModeMap& modeMap,const StateSet::ModeList& modeList)
{
    for(StateSet::ModeList::const_iterator mitr=modeList.begin();
        mitr!=modeList.end();
        ++mitr)
    {
        // get the mode stack for incomming GLmode {mitr->first}.
        ModeStack& ms = modeMap[mitr->first];
        if (!ms.valueVec.empty())
        {
            ms.valueVec.pop_back();
        }
        ms.changed = true;
    }
}

inline void State::popAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList)
{
    for(StateSet::AttributeList::const_iterator aitr=attributeList.begin();
        aitr!=attributeList.end();
        ++aitr)
    {
        // get the attribute stack for incomming type {aitr->first}.
        AttributeStack& as = attributeMap[aitr->first];
        if (!as.attributeVec.empty())
        {
            as.attributeVec.pop_back();
        }
        as.changed = true;
    }
}

inline void State::applyModeList(ModeMap& modeMap,const StateSet::ModeList& modeList)
{
    StateSet::ModeList::const_iterator ds_mitr = modeList.begin();
    ModeMap::iterator this_mitr=modeMap.begin();

    while (this_mitr!=modeMap.end() && ds_mitr!=modeList.end())
    {
        if (this_mitr->first<ds_mitr->first)
        {

            // note GLMode = this_mitr->first
            ModeStack& ms = this_mitr->second;
            if (ms.changed)
            {
                ms.changed = false;
                if (!ms.valueVec.empty())
                {
                    bool new_value = ms.valueVec.back() & StateAttribute::ON;
                    applyMode(this_mitr->first,new_value,ms);
                }
                else
                {
                    // assume default of disabled.
                    applyMode(this_mitr->first,ms.global_default_value,ms);

                }

            }

            ++this_mitr;

        }
        else if (ds_mitr->first<this_mitr->first)
        {

            // ds_mitr->first is a new mode, therefore 
            // need to insert a new mode entry for ds_mistr->first.
            ModeStack& ms = modeMap[ds_mitr->first];

            bool new_value = ds_mitr->second & StateAttribute::ON;
            applyMode(ds_mitr->first,new_value,ms);

            // will need to disable this mode on next apply so set it to changed.
            ms.changed = true;

            ++ds_mitr;

        }
        else
        {
            // this_mitr & ds_mitr refer to the same mode, check the overide
            // if any otherwise just apply the incomming mode.

            ModeStack& ms = this_mitr->second;

            if (!ms.valueVec.empty() && (ms.valueVec.back() & StateAttribute::OVERRIDE) && !(ds_mitr->second & StateAttribute::PROTECTED))
            {
                // override is on, there just treat as a normal apply on modes.

                if (ms.changed)
                {
                    ms.changed = false;
                    bool new_value = ms.valueVec.back() & StateAttribute::ON;
                    applyMode(this_mitr->first,new_value,ms);

                }
            }
            else
            {
                // no override on or no previous entry, therefore consider incomming mode.
                bool new_value = ds_mitr->second & StateAttribute::ON;
                if (applyMode(ds_mitr->first,new_value,ms))
                {
                    ms.changed = true;
                }
            }

            ++this_mitr;
            ++ds_mitr;
        }
    }

    // iterator over the remaining state modes to apply any previous changes.
    for(;
        this_mitr!=modeMap.end();
        ++this_mitr)
    {
        // note GLMode = this_mitr->first
        ModeStack& ms = this_mitr->second;
        if (ms.changed)
        {
            ms.changed = false;
            if (!ms.valueVec.empty())
            {
                bool new_value = ms.valueVec.back() & StateAttribute::ON;
                applyMode(this_mitr->first,new_value,ms);
            }
            else
            {
                // assume default of disabled.
                applyMode(this_mitr->first,ms.global_default_value,ms);

            }

        }
    }        

    // iterator over the remaining incomming modes to apply any new mode.
    for(;
        ds_mitr!=modeList.end();
        ++ds_mitr)
    {
        ModeStack& ms = modeMap[ds_mitr->first];

        bool new_value = ds_mitr->second & StateAttribute::ON;
        applyMode(ds_mitr->first,new_value,ms);

        // will need to disable this mode on next apply so set it to changed.
        ms.changed = true;
    }
}

inline void State::applyAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList)
{
    StateSet::AttributeList::const_iterator ds_aitr=attributeList.begin();

    AttributeMap::iterator this_aitr=attributeMap.begin();

    while (this_aitr!=attributeMap.end() && ds_aitr!=attributeList.end())
    {
        if (this_aitr->first<ds_aitr->first)
        {

            // note attribute type = this_aitr->first
            AttributeStack& as = this_aitr->second;
            if (as.changed)
            {
                as.changed = false;
                if (!as.attributeVec.empty())
                {
                    const StateAttribute* new_attr = as.attributeVec.back().first;
                    applyAttribute(new_attr,as);
                }
                else
                {
                    applyGlobalDefaultAttribute(as);
                }
            }

            ++this_aitr;

        }
        else if (ds_aitr->first<this_aitr->first)
        {

            // ds_mitr->first is a new attribute, therefore 
            // need to insert a new attribute entry for ds_aistr->first.
            AttributeStack& as = attributeMap[ds_aitr->first];

            const StateAttribute* new_attr = ds_aitr->second.first.get();
            applyAttribute(new_attr,as);

            // will need to disable this mode on next apply so set it to changed.
            as.changed = true;

            ++ds_aitr;

        }
        else
        {
            // this_mitr & ds_mitr refer to the same attribute, check the overide
            // if any otherwise just apply the incomming attribute

            AttributeStack& as = this_aitr->second;

            if (!as.attributeVec.empty() && (as.attributeVec.back().second & StateAttribute::OVERRIDE) && !(ds_aitr->second.second & StateAttribute::PROTECTED))
            {
                // override is on, there just treat as a normal apply on modes.

                if (as.changed)
                {
                    as.changed = false;
                    const StateAttribute* new_attr = as.attributeVec.back().first;
                    applyAttribute(new_attr,as);
                }
            }
            else
            {
                // no override on or no previous entry, therefore consider incomming mode.
                const StateAttribute* new_attr = ds_aitr->second.first.get();
                if (applyAttribute(new_attr,as))
                {
                    as.changed = true;
                }
            }

            ++this_aitr;
            ++ds_aitr;
        }
    }

    // iterator over the remaining state modes to apply any previous changes.
    for(;
        this_aitr!=attributeMap.end();
        ++this_aitr)
    {
        // note attribute type = this_aitr->first
        AttributeStack& as = this_aitr->second;
        if (as.changed)
        {
            as.changed = false;
            if (!as.attributeVec.empty())
            {
                const StateAttribute* new_attr = as.attributeVec.back().first;
                applyAttribute(new_attr,as);
            }
            else
            {
                applyGlobalDefaultAttribute(as);
            }
        }
    }        

    // iterator over the remaining incomming modes to apply any new mode.
    for(;
        ds_aitr!=attributeList.end();
        ++ds_aitr)
    {
        // ds_mitr->first is a new attribute, therefore 
        // need to insert a new attribute entry for ds_aistr->first.
        AttributeStack& as = attributeMap[ds_aitr->first];

        const StateAttribute* new_attr = ds_aitr->second.first.get();
        applyAttribute(new_attr,as);

        // will need to update this attribute on next apply so set it to changed.
        as.changed = true;
    }

}

inline void State::applyModeMap(ModeMap& modeMap)
{
    for(ModeMap::iterator mitr=modeMap.begin();
        mitr!=modeMap.end();
        ++mitr)
    {
        // note GLMode = mitr->first
        ModeStack& ms = mitr->second;
        if (ms.changed)
        {
            ms.changed = false;
            if (!ms.valueVec.empty())
            {
                bool new_value = ms.valueVec.back() & StateAttribute::ON;
                applyMode(mitr->first,new_value,ms);
            }
            else
            {
                // assume default of disabled.
                applyMode(mitr->first,ms.global_default_value,ms);
            }
            
        }
    }        
}

inline void State::applyAttributeMap(AttributeMap& attributeMap)
{
    for(AttributeMap::iterator aitr=attributeMap.begin();
        aitr!=attributeMap.end();
        ++aitr)
    {
        AttributeStack& as = aitr->second;
        if (as.changed)
        {
            as.changed = false;
            if (!as.attributeVec.empty())
            {
                const StateAttribute* new_attr = as.attributeVec.back().first;
                applyAttribute(new_attr,as);
            }
            else
            {
                applyGlobalDefaultAttribute(as);
            }
            
        }
    }        
}

}

#endif
