/* -*-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 OSGUTIL_OPTIMIZER
#define OSGUTIL_OPTIMIZER

#include <osg/NodeVisitor>
#include <osg/Matrix>
#include <osg/Geometry>
#include <osg/Transform>

#include <osgUtil/Export>

#include <set>

namespace osgUtil {

/** Traverses scene graph to improve efficiency. See OptimizationOptions.
  * For example of usage see examples/osgimpostor or osgviewer.
  */
  
class OSGUTIL_EXPORT Optimizer
{

    public:

        Optimizer() {}
        virtual ~Optimizer() {}

        enum OptimizationOptions
        {
            FLATTEN_STATIC_TRANSFORMS = 0x001,
            REMOVE_REDUNDANT_NODES =    0x002,
            COMBINE_ADJACENT_LODS =     0x004,
            SHARE_DUPLICATE_STATE =     0x008,
            MERGE_GEOMETRY =            0x010,
            CHECK_GEOMETRY =            0x020,
            SPATIALIZE_GROUPS =         0x040,
            COPY_SHARED_NODES =         0x080,
            TRISTRIP_GEOMETRY =         0x100,
            TESSELATE_GEOMETRY =        0x200,
            OPTIMIZE_TEXTURE_SETTINGS = 0x400,
            DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS |
                                REMOVE_REDUNDANT_NODES |
                                COMBINE_ADJACENT_LODS |
                                SHARE_DUPLICATE_STATE |
                                MERGE_GEOMETRY |
                                CHECK_GEOMETRY |
                                OPTIMIZE_TEXTURE_SETTINGS,
            ALL_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS |
                                REMOVE_REDUNDANT_NODES |
                                COMBINE_ADJACENT_LODS |
                                SHARE_DUPLICATE_STATE |
                                MERGE_GEOMETRY |
                                CHECK_GEOMETRY |
                                SPATIALIZE_GROUPS |
                                COPY_SHARED_NODES |
                                TRISTRIP_GEOMETRY |
                                OPTIMIZE_TEXTURE_SETTINGS
        };

        /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/
        void reset();
        
        /** Traverse the node and its subgraph with a series of optimization
          * visitors, specified by the OptimizationOptions.*/
        void optimize(osg::Node* node);

        /** Traverse the node and its subgraph with a series of optimization
          * visitors, specified by the OptimizationOptions.*/
        virtual void optimize(osg::Node* node, unsigned int options);


        inline void setPermissibleOptimizationsForObject(const osg::Object* object, unsigned int options)
        {
            _permissibleOptimizationsMap[object] = options;
        }
        
        inline unsigned int getPermissibleOptimizationsForObject(const osg::Object* object) const
        {
            PermissibleOptimizationsMap::const_iterator itr = _permissibleOptimizationsMap.find(object);
            if (itr!=_permissibleOptimizationsMap.end()) return itr->second;
            else return 0xffffffff;
        }
        
        inline bool isOperationPermissibleForObject(const osg::Object* object,unsigned int option) const
        {
            return (option & getPermissibleOptimizationsForObject(object))!=0; 
        }
        
        typedef std::map<const osg::Object*,unsigned int> PermissibleOptimizationsMap;

        PermissibleOptimizationsMap& getPermissibleOptionsMap() { return _permissibleOptimizationsMap; }
        const PermissibleOptimizationsMap& getPermissibleOptionsMap() const { return _permissibleOptimizationsMap; }
        
        
    protected:
    
        PermissibleOptimizationsMap _permissibleOptimizationsMap;


    public:


        /** Flatten Static Transform nodes by applying their transform to the
          * geometry on the leaves of the scene graph, then removing the 
          * now redundant transforms.*/        
        class OSGUTIL_EXPORT FlattenStaticTransformsVisitor : public osg::NodeVisitor
        {
            public:



                FlattenStaticTransformsVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::Node& geode);
                virtual void apply(osg::Geode& geode);
                virtual void apply(osg::Billboard& geode);
                virtual void apply(osg::Transform& transform);

                bool removeTransforms(osg::Node* nodeWeCannotRemove);

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,FLATTEN_STATIC_TRANSFORMS) :  true; 
                }

            
            protected:

                typedef std::vector<osg::Transform*>                TransformStack;
                typedef std::set<osg::Drawable*>                    DrawableSet;
                typedef std::set<osg::Billboard*>                   BillboardSet;
                typedef std::set<osg::Node* >                       NodeSet;
                typedef std::set<osg::Transform*>                   TransformSet;
                
                Optimizer*      _optimizer;
                TransformStack  _transformStack;
                NodeSet         _excludedNodeSet;
                DrawableSet     _drawableSet;
                BillboardSet    _billboardSet;
                TransformSet    _transformSet;
        };


        /** Combine Static Transform nodes that sit above one another.*/        
        class OSGUTIL_EXPORT CombineStaticTransformsVisitor : public osg::NodeVisitor
        {
            public:

                CombineStaticTransformsVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::MatrixTransform& transform);

                bool removeTransforms(osg::Node* nodeWeCannotRemove);

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,FLATTEN_STATIC_TRANSFORMS) :  true; 
                }

            protected:

                typedef std::set<osg::MatrixTransform*> TransformSet;
                Optimizer*      _optimizer;
                TransformSet  _transformSet;
        };

        /** Remove rendundant nodes, such as groups with one single child.*/
        class OSGUTIL_EXPORT RemoveEmptyNodesVisitor : public osg::NodeVisitor
        {
            public:


                typedef std::set<osg::Node*> NodeList;
                NodeList                     _redundantNodeList;

                RemoveEmptyNodesVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::Geode& geode);
                virtual void apply(osg::Group& group);
                
                void removeEmptyNodes();

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,REMOVE_REDUNDANT_NODES) :  true; 
                }

                Optimizer*      _optimizer;
        };

        /** Remove rendundant nodes, such as groups with one single child.*/
        class OSGUTIL_EXPORT RemoveRedundantNodesVisitor : public osg::NodeVisitor
        {
            public:

                typedef std::set<osg::Node*> NodeList;
                NodeList                     _redundantNodeList;

                RemoveRedundantNodesVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}
                
                virtual void apply(osg::Group& group);
                virtual void apply(osg::Transform& transform);
                
                void removeRedundantNodes();

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,REMOVE_REDUNDANT_NODES) :  true; 
                }

                Optimizer*      _optimizer;
        };

        /** Tesselate all geodes, to remove POLYGONS.*/
        class OSGUTIL_EXPORT TesselateVisitor : public osg::NodeVisitor
        {
            public:

                typedef std::set<osg::Group*>  GroupList;
                GroupList                      _groupList;

                TesselateVisitor():osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}

                virtual void apply(osg::Geode& geode);

        };

        /** Optimize the LOD groups, by combining adjacent LOD's which have
          * complementary ranges.*/
        class OSGUTIL_EXPORT CombineLODsVisitor : public osg::NodeVisitor
        {
            public:

                typedef std::set<osg::Group*>  GroupList;
                GroupList                      _groupList;

                CombineLODsVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::LOD& lod);

                void combineLODs();

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,COMBINE_ADJACENT_LODS) :  true; 
                }

                Optimizer*      _optimizer;
        };
 
        /** Optimize State in the scene graph by removing duplicate state,
          * replacing it with shared instances, both for StateAttributes,
          * and whole StateSets.*/
        class OSGUTIL_EXPORT StateVisitor : public osg::NodeVisitor
        {
            public:

                /// default to traversing all children.
                StateVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                /** empty visitor, make it ready for next traversal.*/        
                virtual void reset();

                virtual void apply(osg::Node& node);

                virtual void apply(osg::Geode& geode);

                void optimize();

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,SHARE_DUPLICATE_STATE) :  true; 
                }

            protected:

                void addStateSet(osg::StateSet* stateset,osg::Object* obj);

                typedef std::set<osg::Object*>              ObjectSet;
                typedef std::map<osg::StateSet*,ObjectSet>  StateSetMap;

                Optimizer*      _optimizer;
                StateSetMap _statesets;

        };
        
        class OSGUTIL_EXPORT CheckGeometryVisitor : public osg::NodeVisitor
        {
            public:

                /// default to traversing all children.
                CheckGeometryVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::Geode& geode) { checkGeode(geode); }

                void checkGeode(osg::Geode& geode);
                
                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,CHECK_GEOMETRY) :  true; 
                }

                Optimizer*      _optimizer;

        };
        
        class OSGUTIL_EXPORT MergeGeometryVisitor : public osg::NodeVisitor
        {
            public:

                /// default to traversing all children.
                MergeGeometryVisitor(Optimizer* optimizer=0) :
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}

                virtual void apply(osg::Geode& geode) { mergeGeode(geode); }
                virtual void apply(osg::Billboard&) { /* don't do anything*/ }

                bool mergeGeode(osg::Geode& geode);

                static bool geometryContainsSharedArrays(osg::Geometry& geom);

                static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs);

                static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs);
                static bool mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs);
                static bool mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs);
                static bool mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs);
                static bool mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs);

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,MERGE_GEOMETRY) :  true; 
                }

                Optimizer*      _optimizer;
        };

        /** Spatialize scene into a balanced quad/oct tree.*/
        class OSGUTIL_EXPORT SpatializeGroupsVisitor : public osg::NodeVisitor
        {
            public:

                SpatializeGroupsVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}
                
                virtual void apply(osg::Group& group);
                
                bool divide(unsigned int maxNumTreesPerCell=8);
                
                bool divide(osg::Group* group, unsigned int maxNumTreesPerCell);
                
                typedef std::set<osg::Group*> GroupsToDivideList;
                GroupsToDivideList _groupsToDivideList;

                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,SPATIALIZE_GROUPS) :  true; 
                }

                Optimizer*      _optimizer;

        };

        /** Copy any shared subgraphs, enabling flattening of static transforms.*/
        class OSGUTIL_EXPORT CopySharedSubgraphsVisitor : public osg::NodeVisitor
        {
            public:

                CopySharedSubgraphsVisitor(Optimizer* optimizer=0):
                    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                    _optimizer(optimizer) {}
                
                virtual void apply(osg::Node& node);

                void copySharedNodes();
                
                typedef std::set<osg::Node*> SharedNodeList;
                SharedNodeList _sharedNodeList;
                
                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,COPY_SHARED_NODES) :  true; 
                }

                Optimizer*      _optimizer;

        };


        /** For all textures apply settings.*/
        class OSGUTIL_EXPORT TextureVisitor : public osg::NodeVisitor
        {
            public:

                TextureVisitor(bool changeAutoUnRef, bool valueAutoUnRef,
                               bool changeClientImageStorage, bool valueClientImageStorage,
                               bool changeAnisotropy, float valueAnisotropy,
                               Optimizer* optimizer=0):
                        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
                        _optimizer(optimizer),
                        _changeAutoUnRef(changeAutoUnRef), _valueAutoUnRef(valueAutoUnRef),
                        _changeClientImageStorage(changeClientImageStorage), _valueClientImageStorage(valueClientImageStorage),
                        _changeAnisotropy(changeAnisotropy), _valueAnisotropy(valueAnisotropy) {}
                
                virtual void apply(osg::Geode& node);
                virtual void apply(osg::Node& node);

                void apply(osg::StateSet& stateset);
                void apply(osg::Texture& texture);
                
                inline bool isOperationPermissibleForObject(const osg::Object* object) const
                {
                    return _optimizer ? _optimizer->isOperationPermissibleForObject(object,OPTIMIZE_TEXTURE_SETTINGS) :  true; 
                }

                Optimizer*      _optimizer;
                bool            _changeAutoUnRef, _valueAutoUnRef;
                bool            _changeClientImageStorage, _valueClientImageStorage;
                bool            _changeAnisotropy;
                float           _valueAnisotropy;
                                           
                

        };

};

}

#endif
