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

#include <osg/Matrix>
#include <osg/Drawable>
#include <osg/StateSet>
#include <osg/State>
#include <osg/Light>

#include <osgUtil/RenderLeaf>

#include <set>
#include <vector>
#include <algorithm>

namespace osgUtil {

struct LessDepthSortFunctor
{
    bool operator() (const osg::ref_ptr<RenderLeaf>& lhs,const osg::ref_ptr<RenderLeaf>& rhs)
    {
        return (lhs->_depth < rhs->_depth);
    }
};

/** StateGraph - contained in a renderBin, defines the scene to be drawn.
  */
class OSGUTIL_EXPORT StateGraph : public osg::Object
{
    public:


        typedef std::map< const osg::StateSet*, osg::ref_ptr<StateGraph> >  ChildList;
        typedef std::vector< osg::ref_ptr<RenderLeaf> >                     LeafList;

        StateGraph*                         _parent;

#ifdef OSGUTIL_RENDERBACKEND_USE_REF_PTR
        osg::ref_ptr<const osg::StateSet> _stateset;
#else
        const osg::StateSet* _stateset;
#endif

        int                                 _depth;
        ChildList                           _children;
        LeafList                            _leaves;

        mutable float                       _averageDistance;
        mutable float                       _minimumDistance;

        osg::ref_ptr<osg::Referenced>       _userData;

        bool                                _dynamic;

        StateGraph():
            _parent(NULL),
            _stateset(NULL),
            _depth(0),
            _averageDistance(0),
            _minimumDistance(0),
            _userData(NULL),
            _dynamic(false)
        {
        }

        StateGraph(StateGraph* parent,const osg::StateSet* stateset):
            _parent(parent),
            _stateset(stateset),
            _depth(0),
            _averageDistance(0),
            _minimumDistance(0),
            _userData(NULL),
            _dynamic(false)
        {
            if (_parent) _depth = _parent->_depth + 1;

            if (_parent && _parent->_dynamic) _dynamic = true;
            else _dynamic = stateset->getDataVariance()==osg::Object::DYNAMIC;
        }

        ~StateGraph() {}


        virtual osg::Object* cloneType() const { return new StateGraph(); }
        virtual StateGraph* cloneStateGraph() const { return new StateGraph(); }
        virtual osg::Object* clone(const osg::CopyOp&) const { return new StateGraph(); }
        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const StateGraph*>(obj)!=0L; }
        virtual const char* libraryName() const { return "osgUtil"; }
        virtual const char* className() const { return "StateGraph"; }

        void setUserData(osg::Referenced* obj) { _userData = obj; }
        osg::Referenced* getUserData() { return _userData.get(); }
        const osg::Referenced* getUserData() const { return _userData.get(); }

        void setStateSet(const osg::StateSet* stateset) { _stateset = stateset; }

#ifdef OSGUTIL_RENDERBACKEND_USE_REF_PTR
        const osg::StateSet* getStateSet() const { return _stateset.get(); }
#else
        const osg::StateSet* getStateSet() const { return _stateset; }
#endif

        /** return true if all of drawables, lights and children are empty.*/
        inline bool empty() const
        {
            return _leaves.empty() && _children.empty();
        }

        inline bool leaves_empty() const
        {
            return _leaves.empty();
        }


        inline float getAverageDistance() const
        {
            if (_averageDistance==FLT_MAX && !_leaves.empty())
            {
                _averageDistance = 0.0f;
                for(LeafList::const_iterator itr=_leaves.begin();
                    itr!=_leaves.end();
                    ++itr)
                {
                   _averageDistance += (*itr)->_depth;
                }
                _averageDistance /= (float)_leaves.size();

            }
            return _averageDistance;
        }

        inline float getMinimumDistance() const
        {
            if (_minimumDistance==FLT_MAX && !_leaves.empty())
            {
                LeafList::const_iterator itr=_leaves.begin();
                _minimumDistance = (*itr)->_depth;
                ++itr;
                for(;
                    itr!=_leaves.end();
                    ++itr)
                {
                   if ((*itr)->_depth<_minimumDistance) _minimumDistance=(*itr)->_depth;
                }

            }
            return _minimumDistance;
        }

        inline void sortFrontToBack()
        {
            std::sort(_leaves.begin(),_leaves.end(),LessDepthSortFunctor());
        }

        /** Reset the internal contents of a StateGraph, including deleting all children.*/
        void reset();

        /** Recursively clean the StateGraph of all its drawables, lights and depths.
          * Leaves children intact, and ready to be populated again.*/
        void clean();

        /** Recursively prune the StateGraph of empty children.*/
        void prune();


        void resizeGLObjectBuffers(unsigned int maxSize)
        {
            for(ChildList::iterator itr = _children.begin();
                itr != _children.end();
                ++itr)
            {
                (itr->second)->resizeGLObjectBuffers(maxSize);
            }

            for(LeafList::iterator itr = _leaves.begin();
                itr != _leaves.end();
                ++itr)
            {
                (*itr)->resizeGLObjectBuffers(maxSize);
            }
        }

        void releaseGLObjects(osg::State* state=0) const
        {
            if (_stateset) _stateset->releaseGLObjects(state);

            for(ChildList::const_iterator itr = _children.begin();
                itr != _children.end();
                ++itr)
            {
                (itr->second)->releaseGLObjects(state);
            }

            for(LeafList::const_iterator itr = _leaves.begin();
                itr != _leaves.end();
                ++itr)
            {
                (*itr)->releaseGLObjects(state);
            }
        }

        inline StateGraph* find_or_insert(const osg::StateSet* stateset)
        {
            // search for the appropriate state group, return it if found.
            ChildList::iterator itr = _children.find(stateset);
            if (itr!=_children.end()) return itr->second.get();

            // create a state group and insert it into the children list
            // then return the state group.
            StateGraph* sg = new StateGraph(this,stateset);
            _children[stateset] = sg;
            return sg;
        }

        /** add a render leaf.*/
        inline void addLeaf(RenderLeaf* leaf)
        {
            if (leaf)
            {
                _averageDistance = FLT_MAX; // signify dirty.
                _minimumDistance = FLT_MAX; // signify dirty.
                _leaves.push_back(leaf);
                leaf->_parent = this;
                if (_dynamic) leaf->_dynamic = true;
            }
        }

        static inline void moveStateGraph(osg::State& state,StateGraph* sg_curr,StateGraph* sg_new)
        {
            if (sg_new==sg_curr || sg_new==NULL) return;

            if (sg_curr==NULL)
            {

                // use return path to trace back steps to sg_new.
                std::vector<StateGraph*> return_path;
                return_path.reserve(sg_new->_depth+1);

                // need to pop back root render graph.
                do
                {
                    return_path.push_back(sg_new);
                    sg_new = sg_new->_parent;
                } while (sg_new);

                for(std::vector<StateGraph*>::reverse_iterator itr=return_path.rbegin();
                    itr!=return_path.rend();
                    ++itr)
                {
                    StateGraph* rg = (*itr);
                    if (rg->getStateSet()) state.pushStateSet(rg->getStateSet());
                }
                return;
            }


            // first handle the typical case which is two state groups
            // are neighbours.
            if (sg_curr->_parent==sg_new->_parent)
            {

                // state has changed so need to pop old state.
                if (sg_curr->getStateSet()) state.popStateSet();
                // and push new state.
                if (sg_new->getStateSet()) state.pushStateSet(sg_new->getStateSet());
                return;
            }


            // need to pop back up to the same depth as the new state group.
            while (sg_curr->_depth>sg_new->_depth)
            {
                if (sg_curr->getStateSet()) state.popStateSet();
                sg_curr = sg_curr->_parent;
            }

            // use return path to trace back steps to sg_new.
            std::vector<StateGraph*> return_path;
            return_path.reserve(sg_new->_depth+1);

            // need to pop back up to the same depth as the curr state group.
            while (sg_new->_depth>sg_curr->_depth)
            {
                return_path.push_back(sg_new);
                sg_new = sg_new->_parent;
            }

            // now pop back up both parent paths until they agree.

            // DRT - 10/22/02
            // should be this to conform with above case where two StateGraph
            // nodes have the same parent
            while (sg_curr != sg_new)
            {
                if (sg_curr->getStateSet()) state.popStateSet();
                sg_curr = sg_curr->_parent;

                return_path.push_back(sg_new);
                sg_new = sg_new->_parent;
            }

            for(std::vector<StateGraph*>::reverse_iterator itr=return_path.rbegin();
                itr!=return_path.rend();
                ++itr)
            {
                StateGraph* rg = (*itr);
                if (rg->getStateSet()) state.pushStateSet(rg->getStateSet());
            }

        }

        inline static void moveToRootStateGraph(osg::State& state,StateGraph* sg_curr)
        {
            // need to pop back all statesets and matrices.
            while (sg_curr)
            {
                if (sg_curr->getStateSet()) state.popStateSet();
                sg_curr = sg_curr->_parent;
            }

        }

        inline static int numToPop(StateGraph* sg_curr)
        {
            int numToPop = 0;
            // need to pop back all statesets and matrices.
            while (sg_curr)
            {
                if (sg_curr->getStateSet()) ++numToPop;
                sg_curr = sg_curr->_parent;
            }

            return numToPop;
        }

    private:

        /// disallow copy construction.
        StateGraph(const StateGraph&) : osg::Object() {}
        /// disallow copy operator.
        StateGraph& operator = (const StateGraph&) { return *this; }

};

}

#endif