/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2015 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

#ifndef OSGEARTH_UTIL_HTM_H
#define OSGEARTH_UTIL_HTM_H 1

#include <osgEarthUtil/Common>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Polytope>
#include <vector>

namespace osgEarth { namespace Util
{
    class HTMNode;

    /**
     * Hierarchical Triangular Mesh group
     * http://www.geog.ucsb.edu/~hu/papers/spatialIndex.pdf
     *
     * An osg::Group that automatically organizes its contents spatially
     * in order to improve culling performance and proximity search.
     *
     * Note! This group will improve performance when you have a huge
     * number of entities and you zoom in to a smaller area. It will NOT
     * improve the performance when viewing the entire set from a far
     * range.
     */
    class OSGEARTHUTIL_EXPORT HTMGroup : public osg::Group
    {
    public:
        HTMGroup();

        /** total number of objects in the group. */
        unsigned dataCount() const { return _dataCount; }

        /** The maximum number of objects to hold in an index cell before
            splitting it up into smaller parts. */
        unsigned getSplitThreshold() const { return _splitThreshold; }

        /** The minimum number of objects across a set of child cells before
            merging them back into a single cell. */
        unsigned getMergeThreshold() const { return _mergeThreshold; }

        /** enable or disable clustering (experimental) */
        void setCluster( bool value ) { _cluster = value; }
        bool getCluster() const       { return _cluster; }

        /** activate debugging mode */
        void setDebug() { _debug = true; }
        bool getDebug() const { return _debug; }

        /** check a node to see whether we need to move it. */
        bool refresh(osg::Node* node);

        /** removes a node from the group. */
        bool remove(osg::Node* node);

    public: // osg::Group

        /** Add a node to the group. */
        virtual bool addChild(osg::Node* child);

        /** Add a node to the group. Ignores the "index". */
        virtual bool insertChild(unsigned index, osg::Node* child);


    public: // osg::Group (internal)

        /** These methods are derived from Group but are NOOPs for the HTMGroup. */
        virtual bool removeChildren(unsigned pos, unsigned numChildrenToRemove);
        virtual bool replaceChild(osg::Node* origChild, osg::Node* newChild);
        virtual bool setChild(unsigned index, osg::Node* node);

        /** custom traversal */
        virtual void traverse(osg::NodeVisitor& nv);

    protected:
        virtual ~HTMGroup() { }

        bool insert(osg::Node* node);

        unsigned _dataCount;
        bool     _debug;
        bool     _cluster;
        unsigned _splitThreshold;
        unsigned _mergeThreshold;

        typedef std::map<osg::Node*, HTMNode*> NodeMap;
        NodeMap _nodeTable;
    };


    /**
     * Internal index cell for the HTMGroup (do not use directly).
     */
    class HTMNode : public osg::Group
    {
    public:
        HTMNode(HTMGroup* root, const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2);

    public: // osg::Group

        virtual void traverse(osg::NodeVisitor& nv);

    protected:
        virtual ~HTMNode() { }

        HTMNode* insert(osg::Node* node);

        bool remove(osg::Node* node);

        bool refresh(osg::Node* node);

        void split();

        void merge();

        bool isLeaf() const {
            return getNumChildren() == 0;
        }

        bool contains(const osg::Vec3d& p) const {
            return _tri.contains(p);
        }

        unsigned dataCount() const {
            return _dataCount;
        }

        HTMNode* findLeaf(osg::Node*);

        // test whether the node's triangle lies entirely withing a frustum
        bool entirelyWithin(const osg::Polytope& tope) const;
        
        // test whether the node's triangle intersects a frustum
        bool intersects(const osg::Polytope& tope) const;

        osg::BoundingSphere computeBound() const;

    private:

        struct PolytopeDP : public osg::Polytope
        {
            bool contains(const osg::Vec3d& p) const;
            bool containsAnyOf(const std::vector<osg::Vec3d>& p) const;
        };

        struct Triangle
        {
            std::vector<osg::Vec3d> _v;
            PolytopeDP              _tope;

            void set(const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2);

            void getMidpoints(osg::Vec3d* w) const;

            bool contains(const osg::Vec3d& p) const {
                return _tope.contains(p);
            }
        };

        typedef std::list<osg::ref_ptr<osg::Node> > NodeList;

        Triangle      _tri;
        NodeList      _data;
        unsigned      _dataCount;
        HTMGroup*     _root;
        osg::ref_ptr<osg::Geode> _debugGeode;
        osg::ref_ptr<osg::Node>  _clusterNode;
        osg::BoundingSphere      _bs;

        friend class HTMGroup;
    };

} } // namesapce osgEarth::Util


#endif // OSGEARTH_UTIL_HTM_H