/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2008-2014 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 OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_NODE
#define OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_NODE 1

#include "Common"
#include "RenderBindings"
#include "Loader"
#include "TileRenderModel"
#include "LoadTileData"

#include <osgEarth/TerrainTileModel>
#include <osgEarth/TerrainTileNode>
#include <osgEarth/TerrainTileModelFactory>
#include <osgEarth/Threading>
#include <vector>

namespace osg {
    class CullStack;
}
namespace osgUtil {
    class CullVisitor;
}
namespace osgEarth {
    class TerrainOptions;
}

namespace osgEarth { namespace REX
{
    class LoadTileOperation;
    class EngineContext;
    class SurfaceNode;
    class SelectionInfo;
    class TerrainCuller;

    /**
     * TileNode represents a single tile. TileNode has 5 children:
     * one SurfaceNode that renders the actual tile content under a MatrixTransform;
     * and four TileNodes representing the LOD+1 quadtree tiles under this tile.
     */
    class TileNode : public osg::Group,
                     public osgEarth::TerrainTile
    {
    public:
        TileNode(
            const TileKey& key,
            TileNode* parentTile,
            EngineContext* context,
            Cancelable* progress);

        virtual ~TileNode();
        virtual const char* className() const { return "Rex::TileNode"; }

        /** TileKey of the key representing the data in this node. */
        const TileKey& getKey() const { return _key; }

        /** Indicates that this tile should never be unloaded. */
        void setDoNotExpire(bool value);
        bool getDoNotExpire() const { return _doNotExpire; }

        /** Tells this tile to load all its layers. */
        void refreshAllLayers();

        /** Tells this tile to request data for the data in the manifest */
        void refreshLayers(const CreateTileManifest& manifest);

        /** Install new geometry in this tile */
        void createGeometry(Cancelable*);

        /** Initial inheritance of tile data from the parent node. */
        void initializeData();

        /** Whether the tile is expired; i.e. has not been visited in some time. */
        bool isDormant() const;

        /** Whether all the subtiles are this tile are dormant (have not been visited recently) */
        bool areSubTilesDormant() const;

        /** Whether all 3 quadtree siblings of this tile are dormant */
        bool areSiblingsDormant() const;

        /** Removed any sub tiles from the scene graph. Please call from a safe thread only (update) */
        void removeSubTiles();

        /** Notifies this tile that another tile has come into existence. */
        void notifyOfArrival(TileNode* that);

        /** Returns the tile's parent; convenience function */
        inline TileNode* getParentTile() { return _parentTile.get(); } //.get();

        inline const TileNode* getParentTile() const { return _parentTile.get(); } //.get();

        inline bool getParentTile(osg::ref_ptr<TileNode>& parent) { return _parentTile.lock(parent); }

        /** Returns the SurfaceNode under this node. */
        SurfaceNode* getSurfaceNode() { return _surface.get(); }

        /** Elevation data for this node along with its scale/bias matrix; needed for bounding box */
        void setElevationRaster(const osg::Image* image, const osg::Matrixf& matrix);
        void updateElevationRaster();
        const osg::Image* getElevationRaster() const;
        const osg::Matrixf& getElevationMatrix() const;

        // access to subtiles
        TileNode* getSubTile(unsigned i) { return static_cast<TileNode*>(_children[i].get()); }
        const TileNode* getSubTile(unsigned i) const { return static_cast<TileNode*>(_children[i].get()); }

        /** Merge new Tile model data into this tile's rendering data model. */
        void merge(
            const TerrainTileModel* dataModel,
            const CreateTileManifest& manifest);

        /** Access the rendering model for this tile */
        TileRenderModel& renderModel() { return _renderModel; }

        const osg::Vec4f& getTileKeyValue() const { return _tileKeyValue; }

        const osg::Vec2f& getMorphConstants() const { return _morphConstants; }

        void loadSync();

        void refreshSharedSamplers(const RenderBindings& bindings);

        int getRevision() const { return _revision; }

        bool isEmpty() const { return _empty; }

        float getLoadPriority() const { return _loadPriority; }

        // whether the TileNodeRegistry should update-traverse this node
        bool updateRequired() const {
            return _imageUpdatesActive;
        }

        // update-traverse this node, updating any images that require
        // and update-traverse
        void update(osg::NodeVisitor& nv);

        int getLastTraversalFrame() const {
            return _lastTraversalFrame;
        }

        double getLastTraversalTime() const {
            return _lastTraversalTime;
        }

        float getLastTraversalRange() const {
            return _lastTraversalRange;
        }

        void resetTraversalRange() {
            _lastTraversalRange = FLT_MAX;
        }

    public: // osg::Node

        osg::BoundingSphere computeBound() const;

        void traverse(osg::NodeVisitor& nv);

        void resizeGLObjectBuffers(unsigned maxSize);

        void releaseGLObjects(osg::State* state) const;

    protected:

        TileKey _key;
        osg::observer_ptr<TileNode> _parentTile;
        osg::ref_ptr<SurfaceNode>          _surface;
        osg::observer_ptr<EngineContext>   _context;
        Threading::Mutex                   _mutex;
        std::atomic<int>                   _lastTraversalFrame;
        double                             _lastTraversalTime;
        float                              _lastTraversalRange;
        bool                               _childrenReady;
        mutable osg::Vec4f                 _tileKeyValue;
        osg::Vec2f                         _morphConstants;
        TileRenderModel                    _renderModel;
        bool                               _empty;
        bool                               _imageUpdatesActive;
        TileKey                            _subdivideTestKey;
        bool                               _doNotExpire;
        int                                _revision;
        bool _createChildAsync;
        std::atomic<float> _loadPriority;

        using CreateChildResult = osg::ref_ptr<TileNode>;
        std::vector<Future<CreateChildResult>> _createChildResults;

        using LoadQueue = std::queue<LoadTileDataOperationPtr>;
        Mutexed<LoadQueue> _loadQueue;
        unsigned _loadsInQueue;
        const CreateTileManifest* _nextLoadManifestPtr;

        bool dirty() const {
            return _loadsInQueue > 0;
        }

        bool nextLoadIsProgressive() const;

        osg::observer_ptr<TileNode> _eastNeighbor;
        osg::observer_ptr<TileNode> _southNeighbor;

    private:

        void updateNormalMap();

        bool createChildren();

        TileNode* createChild(
            const TileKey& key,
            Cancelable* cancelable);

        // Returns false if the Surface node fails visiblity test
        bool cull(TerrainCuller*);

        bool cull_spy(TerrainCuller*);

        bool shouldSubDivide(TerrainCuller*, const SelectionInfo&);

        // whether this tile should render the given pass
        bool passInLegalRange(const RenderingPass&) const;

        /** Load (or continue loading) content for the tiles in this quad. */
        void load(TerrainCuller*);

        /** Ensure that inherited data from the parent node is up to date. */
        void refreshInheritedData(TileNode* parent, const RenderBindings& bindings);

        // Inherit one shared sampler from parent tile if possible
        void inheritSharedSampler(int binding);

        //const TerrainOptions& options() const;
    };

} } // namespace osgEarth::REX

#endif // OSGEARTH_DRIVERS_REX_TERRAIN_ENGINE_TILE_NODE
