/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2005 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 {

// forward declare
class Optimizer;

/** Helper base class for implementing Optimizer techniques.*/
class OSGUTIL_EXPORT BaseOptimizerVisitor : public osg::NodeVisitor
{
    public:

        BaseOptimizerVisitor(Optimizer* optimizer, unsigned int operation):
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
            _optimizer(optimizer),
            _operationType(operation) {}

        inline bool isOperationPermissibleForObject(const osg::StateSet* object) const;
        inline bool isOperationPermissibleForObject(const osg::StateAttribute* object) const;
        inline bool isOperationPermissibleForObject(const osg::Drawable* object) const;        
        inline bool isOperationPermissibleForObject(const osg::Node* object) const;

    protected:

        Optimizer*      _optimizer;
        unsigned int _operationType;
};

/** 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,
            REMOVE_LOADED_PROXY_NODES = 0x004,
            COMBINE_ADJACENT_LODS =     0x008,
            SHARE_DUPLICATE_STATE =     0x010,
            MERGE_GEOMETRY =            0x020,
            CHECK_GEOMETRY =            0x040,
            SPATIALIZE_GROUPS =         0x080,
            COPY_SHARED_NODES =         0x100,
            TRISTRIP_GEOMETRY =         0x200,
            TESSELATE_GEOMETRY =        0x400,
            OPTIMIZE_TEXTURE_SETTINGS = 0x800,
            DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS |
                                REMOVE_REDUNDANT_NODES |
                                REMOVE_LOADED_PROXY_NODES |
                                COMBINE_ADJACENT_LODS |
                                SHARE_DUPLICATE_STATE |
                                MERGE_GEOMETRY |
                                CHECK_GEOMETRY |
                                OPTIMIZE_TEXTURE_SETTINGS,
            ALL_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS |
                                REMOVE_REDUNDANT_NODES |
                                REMOVE_LOADED_PROXY_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);


        /** Callback for customizing what operations are permitted on objects in the scene graph.*/        
        struct IsOperationPermissibleForObjectCallback : public osg::Referenced
        {
            virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateSet* stateset,unsigned int option) const
            {
                return optimizer->isOperationPermissibleForObjectImplementation(stateset,option);
            }
            
            virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateAttribute* attribute,unsigned int option) const
            {
                return optimizer->isOperationPermissibleForObjectImplementation(attribute,option);
            }
            
            virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Drawable* drawable,unsigned int option) const
            {
                return optimizer->isOperationPermissibleForObjectImplementation(drawable,option);
            }
            
            virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Node* node,unsigned int option) const
            {
                return optimizer->isOperationPermissibleForObjectImplementation(node,option);
            }
            
        };
        
        /** Set the callback for customizing what operations are permitted on objects in the scene graph.*/        
        void setIsOperationPermissibleForObjectCallback(IsOperationPermissibleForObjectCallback* callback) { _isOperationPermissibleForObjectCallback=callback; }

        /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/        
        IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() { return _isOperationPermissibleForObjectCallback.get(); }

        /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/        
        const IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() const { return _isOperationPermissibleForObjectCallback.get(); }


        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::StateSet* object, unsigned int option) const
        {
            if (_isOperationPermissibleForObjectCallback.valid())
                return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option);
            else 
                return isOperationPermissibleForObjectImplementation(object,option); 
        }

        inline bool isOperationPermissibleForObject(const osg::StateAttribute* object, unsigned int option) const
        {
            if (_isOperationPermissibleForObjectCallback.valid())
                return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option);
            else 
                return isOperationPermissibleForObjectImplementation(object,option); 
        }

        inline bool isOperationPermissibleForObject(const osg::Drawable* object, unsigned int option) const
        {
            if (_isOperationPermissibleForObjectCallback.valid())
                return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option);
            else 
                return isOperationPermissibleForObjectImplementation(object,option); 
        }

        inline bool isOperationPermissibleForObject(const osg::Node* object, unsigned int option) const
        {
            if (_isOperationPermissibleForObjectCallback.valid())
                return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option);
            else 
                return isOperationPermissibleForObjectImplementation(object,option); 
        }

        bool isOperationPermissibleForObjectImplementation(const osg::StateSet* stateset, unsigned int option) const
        {
            return (option & getPermissibleOptimizationsForObject(stateset))!=0;
        }
        
        bool isOperationPermissibleForObjectImplementation(const osg::StateAttribute* attribute, unsigned int option) const
        {
            return (option & getPermissibleOptimizationsForObject(attribute))!=0;
        }

        bool isOperationPermissibleForObjectImplementation(const osg::Drawable* drawable, unsigned int option) const
        {
            if (option & (REMOVE_REDUNDANT_NODES|MERGE_GEOMETRY))
            {
                if (drawable->getUserData()) return false;
                if (drawable->getUpdateCallback()) return false;
                if (drawable->getEventCallback()) return false;
                if (drawable->getCullCallback()) return false;
            }
            return (option & getPermissibleOptimizationsForObject(drawable))!=0;
        }

        bool isOperationPermissibleForObjectImplementation(const osg::Node* node, unsigned int option) const
        {
            if (option & (REMOVE_REDUNDANT_NODES|COMBINE_ADJACENT_LODS|FLATTEN_STATIC_TRANSFORMS))
            {
                if (node->getUserData()) return false;
                if (node->getUpdateCallback()) return false;
                if (node->getEventCallback()) return false;
                if (node->getCullCallback()) return false;
                if (node->getNumDescriptions()>0) return false;
                if (node->getStateSet()) return false;
                if (node->getNodeMask()!=0xffffffff) return false;
                // if (!node->getName().empty()) return false;
            }

            return (option & getPermissibleOptimizationsForObject(node))!=0;
        }
        
    protected:

        osg::ref_ptr<IsOperationPermissibleForObjectCallback> _isOperationPermissibleForObjectCallback;

        typedef std::map<const osg::Object*,unsigned int> PermissibleOptimizationsMap;
        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 BaseOptimizerVisitor
        {
            public:

                FlattenStaticTransformsVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {}

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

                bool removeTransforms(osg::Node* nodeWeCannotRemove);

            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;
                
                TransformStack  _transformStack;
                NodeSet         _excludedNodeSet;
                DrawableSet     _drawableSet;
                BillboardSet    _billboardSet;
                TransformSet    _transformSet;
        };


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

                CombineStaticTransformsVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {}

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

                bool removeTransforms(osg::Node* nodeWeCannotRemove);

            protected:

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

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


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

                RemoveEmptyNodesVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {}

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

        };

        /** Remove redundant nodes, such as groups with one single child.*/
        class OSGUTIL_EXPORT RemoveRedundantNodesVisitor : public BaseOptimizerVisitor
        {
            public:

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

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

        };

        /** Remove loaded proxy nodes.*/
        class OSGUTIL_EXPORT RemoveLoadedProxyNodesVisitor : public BaseOptimizerVisitor
        {
            public:

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

                RemoveLoadedProxyNodesVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, REMOVE_LOADED_PROXY_NODES) {}
                
                virtual void apply(osg::ProxyNode& group);
                
                void removeRedundantNodes();

        };

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

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

                TesselateVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, TESSELATE_GEOMETRY) {}

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

        };

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

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

                CombineLODsVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, COMBINE_ADJACENT_LODS) {}

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

                void combineLODs();

        };
 
        /** 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 BaseOptimizerVisitor
        {
            public:

                /// default to traversing all children.
                StateVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, SHARE_DUPLICATE_STATE) {}

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

            protected:

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

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

                StateSetMap _statesets;

        };
        
        class OSGUTIL_EXPORT CheckGeometryVisitor : public BaseOptimizerVisitor
        {
            public:

                /// default to traversing all children.
                CheckGeometryVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, CHECK_GEOMETRY) {}

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

                void checkGeode(osg::Geode& geode);
                
        };
        
        class OSGUTIL_EXPORT MergeGeometryVisitor : public BaseOptimizerVisitor
        {
            public:

                /// default to traversing all children.
                MergeGeometryVisitor(Optimizer* optimizer=0) :
                    BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY),
                    _targetMaximumNumberOfVertices(10000) {}

                void setTargetMaximumNumberOfVertices(unsigned int num)
                {
                    _targetMaximumNumberOfVertices = num;
                }
                
                unsigned int getTargetMaximumNumberOfVertices() const
                {
                    return _targetMaximumNumberOfVertices;
                }

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

            protected:

                unsigned int _targetMaximumNumberOfVertices;

        };

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

                SpatializeGroupsVisitor(Optimizer* optimizer=0):
                    BaseOptimizerVisitor(optimizer, SPATIALIZE_GROUPS) {}
                
                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;

        };

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

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

                void copySharedNodes();
                
                typedef std::set<osg::Node*> SharedNodeList;
                SharedNodeList _sharedNodeList;
                
        };


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

                TextureVisitor(bool changeAutoUnRef, bool valueAutoUnRef,
                               bool changeClientImageStorage, bool valueClientImageStorage,
                               bool changeAnisotropy, float valueAnisotropy,
                               Optimizer* optimizer=0):
                        BaseOptimizerVisitor(optimizer, OPTIMIZE_TEXTURE_SETTINGS),
                        _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);

                bool            _changeAutoUnRef, _valueAutoUnRef;
                bool            _changeClientImageStorage, _valueClientImageStorage;
                bool            _changeAnisotropy;
                float           _valueAnisotropy;

        };
        
};

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

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

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

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

}

#endif
