/* osgEarth
* Copyright 2008-2014 Pelican Mapping
* MIT License
*/
#pragma once

#include "Common"
#include "RenderBindings"
#include "Loader"
#include "TileRenderModel"
#include "LoadTileData"
#include <osgEarth/Threading>
#include <array>

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

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(Texture::Ptr image, const osg::Matrixf& matrix);
        void updateElevationRaster();
        Texture::Ptr getElevationRaster() const;
        const osg::Matrixf& getElevationMatrix() const;

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

        /** 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 override;
        void traverse(osg::NodeVisitor& nv) override;
        void resizeGLObjectBuffers(unsigned maxSize) override;
        void releaseGLObjects(osg::State* state) const override;

    protected:

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

        // for each job creating one child at a time:
        using CreateChildResult = osg::ref_ptr<TileNode>;
        std::array<Future<CreateChildResult>, 4> _createChildResults;

        // for each job creating all children at once:
        using CreateChildrenResult = std::array<osg::ref_ptr<TileNode>, 4>;
        Future<CreateChildrenResult> _createChildrenFutureResult;

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

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

} } // namespace osgEarth::REX
