/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2018 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.
 *
 * This program 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
 * GNU Lesser General Public License for more details.
 *
 * 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 OSGEARTHUTIL_SPATIAL_DATA
#define OSGEARTHUTIL_SPATIAL_DATA

#include <osgEarthUtil/Common>
#include <osgEarth/GeoData>
#include <osg/observer_ptr>
#include <osg/Geode>
#include <osg/Plane>
#include <osg/LOD>
#include <map>

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    class GeoObject;
    //typedef std::vector< osg::ref_ptr<GeoObject> > GeoObjectVector;
    typedef std::pair< float, osg::ref_ptr<GeoObject> > GeoObjectPair;
    typedef std::multimap< float, osg::ref_ptr<GeoObject> > GeoObjectCollection;

    /**
     * A group corresponding to a specific geospatial cell.
     * @deprecated (remove after 2.10)
     */
    class OSGEARTHUTIL_EXPORT GeoCell : public osg::LOD
    {
    public:
        GeoCell(
            const GeoExtent& extent,
            float    maxRange,
            unsigned maxOjbects,
            unsigned splitDim,
            float    splitRangeFactor,
            unsigned depth );

        /** dtor */
        virtual ~GeoCell() { }

        /** Adds an object to this geocell graph. */
        virtual bool insertObject( GeoObject* object );

        /** Removes an object from this geocell graph. */
        bool removeObject( GeoObject* object );

        /** Re-index an object within the geocell graph based on a new position. */
        bool reindexObject( GeoObject* object );

        /** The number of rows and columns into which this cell will split */
        unsigned getSplitDimension() const { return _splitDim; }

        /** The maximum number of objects this cell can hold before splitting */
        unsigned getMaxObjects() const { return _maxObjects; }

        /** Child cells have a maximum LOD range of this cell's max range times this factor. */
        float getSplitRangeFactor() const { return _splitRangeFactor; }

        /** the extent of this cell */
        const GeoExtent& getExtent() const { return _extent; }

        /** gets the frame number of the last time this cell passed cull */
        unsigned getLastCullFrame() const { return _frameStamp; }

    public: // osg::LOD overrides

        virtual osg::BoundingSphere computeBound() const;

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

    protected:
        GeoExtent _extent;
        unsigned  _splitDim;
        unsigned  _maxObjects;  // maximum # of objects to render in this cell before splitting.
        unsigned  _minObjects;  // minimum # of objects to drop below before pulling up
        float     _maxRange;    // maximum visibility range of this cell.
        float     _splitRangeFactor; // child range = cell range * factor
        unsigned  _count;       // # of objects in this cell (including all children)
        unsigned  _depth;
        unsigned  _frameStamp;  // last fram this cell was visited by CULL

        void split();
        void merge();
        void adjustCount( int delta );
        void generateBoundaries();
        void generateBoundaryGeometry();

        std::vector<osg::Vec3d> _boundaryPoints;

        // priority-order collection of objects
        GeoObjectCollection _objects;

        osg::ref_ptr<osg::Geode> _clusterGeode;
        osg::ref_ptr<osg::Geode> _boundaryGeode;
        osg::Vec4Array* _boundaryColor;

        friend class GeoCellVisitor;
    };

    /**
     * @deprecated (remove after 2.10)
     */
    class OSGEARTHUTIL_EXPORT GeoGraph : public GeoCell
    {
    public:
        GeoGraph(
            const GeoExtent& extent,
            float            maxRange,
            unsigned         maxObjects       =500,            
            unsigned         splitDim         =2,
            float            splitRangeFactor =0.5f,
            unsigned         rootWidth        =2,
            unsigned         rootHeight       =2 );

        /** dtor */
        virtual ~GeoGraph() { }

        bool insertObject( GeoObject* object );

    private:
        unsigned _rootWidth, _rootHeight;
    };
    
    /**
     * @deprecated (remove after 2.10)
     */
    class GeoCellVisitor : public osg::NodeVisitor
    {
    public:
        GeoCellVisitor() : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ) { }
        virtual ~GeoCellVisitor() { }

        virtual void operator()( const GeoCell* cell, const GeoObjectCollection& objects ) =0;

        void apply( osg::LOD& node ) {
            const GeoCell* cell = static_cast<const GeoCell*>(&node);
            if ( cell->_depth > 0 )
                this->operator()( cell, cell->_objects );
            traverse( node );
        }
    };

    /**
     * Base class for an object indexed by a GeoCell hierarchy.
     * @deprecated (remove after 2.10)
     */
    class OSGEARTHUTIL_EXPORT GeoObject : public osg::Referenced
    {
    public:
        /**
         * Outputs the location (for spatial indexing), which must be in the same
         * SRS as the GeoGraph.
         */
        virtual bool getLocation( osg::Vec3d& output ) const =0;

        /** The priority of the object relative to other objects */
        virtual float getPriority() const { return 0.0; }

        /** Gets the node to render for this object. */
        virtual osg::Node* getNode() const =0;

        /** The cell currently holding this object. */
        GeoCell* getGeoCell() const { return _cell.get(); }

    protected:
        GeoObject();
        virtual ~GeoObject() { }
        osg::observer_ptr<GeoCell> _cell;
        float _priority;
        friend class GeoCell;
    };    

} } // namespace osgEarth::Util

#endif // OSGEARTHUTIL_SPATIAL_DATA
