//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.

#ifndef OSG_STATE
#define OSG_STATE 1

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

#include <osg/FrameStamp>
#include <osg/Camera>
#include <osg/DisplaySettings>

#include <vector>
#include <map>

namespace osg {

/** 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();
        
        virtual ~State();

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

        /** pop drawstate off state stack.*/
        void popStateSet();
       
        /** 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();


        /** apply an OpenGL mode if required. */
        inline const bool apply_mode(const StateAttribute::GLMode mode,const bool enabled)
        {
            return apply_mode(mode,enabled,_modeMap[mode]);
        }


        /** apply an attribute if required. */
        inline const bool apply_attribute(const StateAttribute* attribute)
        {
            return apply_attribute(attribute,_attributeMap[attribute->getType()]);
        }


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

        /** apply the state.*/
        void apply();
       
        /** mode has been set externally, update state to reflect this setting.*/
        void have_applied(const StateAttribute::GLMode mode,const StateAttribute::GLModeValue value);
        
        /** attribute has been applied externally, update state to reflect this setting.*/
        void have_applied(const StateAttribute* attribute);

        
        /** 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.*/ 
        inline void setContextID(unsigned int contextID) { _contextID=contextID; }

        /** Get the current OpenGL context unique ID.*/ 
        inline const 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 camera. Note, nothing is applied, the camera is just used
          * used in the State object to pass the current camera to Drawables
          * during rendering. */
        inline void setCamera(Camera* camera) { _camera = camera; }
        
        /** Get the camera */
        inline const Camera* getCamera() const { return _camera.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(); }

    private:
    
        unsigned int                _contextID;
        ref_ptr<FrameStamp>         _frameStamp;
        ref_ptr<Camera>             _camera;
        ref_ptr<DisplaySettings>    _displaySettings;
        
        typedef std::vector<StateAttribute::GLModeValue>  ValueVec;

        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;
        };


        typedef std::pair<const StateAttribute*,StateAttribute::OverrideValue> AttributePair;
        typedef std::vector<AttributePair>                                     AttributeVec;

        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 const bool apply_mode(const StateAttribute::GLMode mode,const bool enabled,ModeStack& ms)
        {
            if (ms.last_applied_value != enabled)
            {
                ms.last_applied_value = enabled;

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

                return true;
            }
            else
                return false;
        }

        /** apply an attribute if required, passing in attribute and appropriate attribute stack */
        inline const bool apply_attribute(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);
                return true;
            }
            else
                return false;
        }

        inline const bool apply_global_default_attribute(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);
                return true;
            }
            else
                return false;
        }

        typedef std::map<StateAttribute::GLMode,ModeStack>      ModeMap;
        typedef std::map<StateAttribute::Type,AttributeStack>   AttributeMap;
        typedef std::vector<ref_ptr<const StateSet> >           StateSetStack;
        typedef std::vector<ref_ptr<const Matrix> >             MatrixStack;

        ModeMap         _modeMap;
        AttributeMap    _attributeMap;
        StateSetStack   _drawStateStack;
        
};

}

#endif
