// clang-format off
/* -*-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.
*/

/* Modified for OpenMW */

#ifndef OPENMW_OSGUTIL_OPTIMIZER
#define OPENMW_OSGUTIL_OPTIMIZER

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

//#include <osgUtil/Export>

#include <set>
#include <mutex>

namespace osgDB
{
    class SharedStateManager;
}

//namespace osgUtil {
namespace SceneUtil {

// forward declare
class Optimizer;

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

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

        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 Optimizer
{

    public:

        Optimizer() : _mergeAlphaBlending(false), _sharedStateManager(nullptr), _sharedStateMutex(nullptr) {}
        virtual ~Optimizer() {}

        enum OptimizationOptions
        {
            FLATTEN_STATIC_TRANSFORMS = (1 << 0),
            REMOVE_REDUNDANT_NODES =    (1 << 1),
            REMOVE_LOADED_PROXY_NODES = (1 << 2),
            COMBINE_ADJACENT_LODS =     (1 << 3),
            SHARE_DUPLICATE_STATE =     (1 << 4),
            MERGE_GEOMETRY =            (1 << 5),
            CHECK_GEOMETRY =            (1 << 6), // deprecated, currently no-op
            MAKE_FAST_GEOMETRY =        (1 << 7),
            SPATIALIZE_GROUPS =         (1 << 8),
            COPY_SHARED_NODES =         (1 << 9),
            TRISTRIP_GEOMETRY =         (1 << 10),
            TESSELLATE_GEOMETRY =       (1 << 11),
            OPTIMIZE_TEXTURE_SETTINGS = (1 << 12),
            MERGE_GEODES =              (1 << 13),
            FLATTEN_BILLBOARDS =        (1 << 14),
            TEXTURE_ATLAS_BUILDER =     (1 << 15),
            STATIC_OBJECT_DETECTION =   (1 << 16),
            FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS = (1 << 17),
            INDEX_MESH =                (1 << 18),
            VERTEX_POSTTRANSFORM =      (1 << 19),
            VERTEX_PRETRANSFORM =       (1 << 20),
            DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS |
                                REMOVE_REDUNDANT_NODES |
                                REMOVE_LOADED_PROXY_NODES |
                                COMBINE_ADJACENT_LODS |
                                SHARE_DUPLICATE_STATE |
                                MERGE_GEOMETRY |
                                MAKE_FAST_GEOMETRY |
                                CHECK_GEOMETRY |
                                OPTIMIZE_TEXTURE_SETTINGS |
                                STATIC_OBJECT_DETECTION,
            ALL_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS |
                                REMOVE_REDUNDANT_NODES |
                                REMOVE_LOADED_PROXY_NODES |
                                COMBINE_ADJACENT_LODS |
                                SHARE_DUPLICATE_STATE |
                                MERGE_GEODES |
                                MERGE_GEOMETRY |
                                MAKE_FAST_GEOMETRY |
                                CHECK_GEOMETRY |
                                SPATIALIZE_GROUPS |
                                COPY_SHARED_NODES |
                                TRISTRIP_GEOMETRY |
                                OPTIMIZE_TEXTURE_SETTINGS |
                                TEXTURE_ATLAS_BUILDER |
                                STATIC_OBJECT_DETECTION
        };

        void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; }
        void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; }

        void setSharedStateManager(osgDB::SharedStateManager* sharedStateManager, std::mutex* sharedStateMutex) { _sharedStateMutex = sharedStateMutex; _sharedStateManager = sharedStateManager; }

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

        osg::Vec3f _viewPoint;
        bool _mergeAlphaBlending;

        osgDB::SharedStateManager* _sharedStateManager;
        mutable std::mutex* _sharedStateMutex;

    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.  Static transformed subgraphs that have multiple
          * parental paths above them are not flattened, if you require this then
          * the subgraphs have to be duplicated - for this use the
          * FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor. */
        class FlattenStaticTransformsVisitor : public BaseOptimizerVisitor
        {
            public:

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

                void apply(osg::Node& node) override;
                void apply(osg::Geometry& geometry) override;
                void apply(osg::Drawable& drawable) override;
                void apply(osg::Billboard& billboard) override;
                void apply(osg::Transform& transform) override final;
                void apply(osg::MatrixTransform& transform) override;

                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 CombineStaticTransformsVisitor : public BaseOptimizerVisitor
        {
            public:

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

                void apply(osg::MatrixTransform& transform) override;
                void apply(osg::Geometry&) override { }

                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 RemoveEmptyNodesVisitor : public BaseOptimizerVisitor
        {
            public:


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

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

                void apply(osg::Group& group) override;
                void apply(osg::Geometry&) override { }

                void removeEmptyNodes();

        };

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

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

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

                void apply(osg::Group& group) override;
                void apply(osg::Transform& transform) override;
                void apply(osg::LOD& lod) override;
                void apply(osg::Switch& switchNode) override;
                void apply(osg::Sequence& sequenceNode) override;
                void apply(osg::Geometry&) override { }

                bool isOperationPermissible(osg::Node& node);

                void removeRedundantNodes();

        };

        /** Merge adjacent Groups that have the same StateSet. */
        class MergeGroupsVisitor : public SceneUtil::BaseOptimizerVisitor
        {
        public:
            MergeGroupsVisitor(SceneUtil::Optimizer* optimizer)
                : BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES)
            {
            }

            bool isOperationPermissible(osg::Group& node);

            void apply(osg::Geometry&) override { }
            void apply(osg::Group& group) override;
            void apply(osg::LOD& lod) override;
            void apply(osg::Switch& switchNode) override;
            void apply(osg::Sequence& sequenceNode) override;
        };

        struct GeometryArraySizes
        {
            unsigned mVertex = 0;
            unsigned mNormal = 0;
            unsigned mColor = 0;
            unsigned mSecondaryColor = 0;
            unsigned mFogCoord = 0;
            std::vector<unsigned> mTexCoord;
            std::vector<unsigned> mVertexAttrib;
        };

        class MergeGeometryVisitor : public BaseOptimizerVisitor
        {
            public:

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

                void setMergeAlphaBlending(bool merge)
                {
                    _mergeAlphaBlending = merge;
                }
                void setViewPoint(const osg::Vec3f& viewPoint)
                {
                    _viewPoint = viewPoint;
                }

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

                unsigned int getTargetMaximumNumberOfVertices() const
                {
                    return _targetMaximumNumberOfVertices;
                }

                bool pushStateSet(osg::StateSet* stateSet);
                void popStateSet();
                void checkAlphaBlendingActive();
                void apply(osg::Geometry&) override { }
                void apply(osg::Group& group) override;
                void apply(osg::Billboard&) override { /* don't do anything*/ }

                bool mergeGroup(osg::Group& group);

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

                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;
                std::vector<osg::StateSet*> _stateSetStack;
                bool _alphaBlendingActive;
                bool _mergeAlphaBlending;
                osg::Vec3f _viewPoint;
        };

};

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
// clang-format on
