/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2018 Pelican Mapping
 * MIT License
 */
#ifndef OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H
#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H 1

#include <osgEarth/TerrainEngineNode>
#include <osgEarth/TerrainResources>
#include <osgEarth/Map>
#include <osgEarth/Revisioning>
#include <osgEarth/Threading>
#include <osgEarth/Containers>
#include <osgEarth/ResourceReleaser>

#include "MPTerrainEngineOptions"
#include "KeyNodeFactory"
#include "TileModelFactory"
#include "TileModelCompiler"
#include "TileNodeRegistry"

#include <osg/Geode>
#include <osg/NodeCallback>
#include <osg/Uniform>
#include <osgUtil/RenderBin>

using namespace osgEarth;

namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
{
    class MPTerrainEngineNode : public TerrainEngineNode
    {
    public:
        MPTerrainEngineNode();
        META_Node(osgEarth,MPTerrainEngineNode);
        virtual ~MPTerrainEngineNode();

    public:
        /** Creates a tile node that is set up to page in subtiles. */
        osg::Node* createNode(const TileKey& key, ProgressCallback* progress =0L);

        /** Creates a tile node with no support for paging in subtiles. */
        osg::Node* createStandaloneNode(const TileKey& key, ProgressCallback* progress =0L);

    public: // TerrainEngineNode

        // for standalone tile creation outside of a terrain
        osg::Node* createTile(const TileKey& key);
        
        // when incremental update is enabled, forces regeneration of tiles
        // in the given region.
        void invalidateRegion(
            const GeoExtent& extent,
            unsigned         minLevel,
            unsigned         maxLevel);

        /** Access the stateset used to render the terrain. */
        osg::StateSet* getTerrainStateSet();

        /** Access the stateset used to render payload data. */
        osg::StateSet* getPayloadStateSet();

        /** Install and MP-specific shader library. */
        bool includeShaderLibrary(VirtualProgram* vp);

    public: // internal TerrainEngineNode

        void setMap(const Map* map, const TerrainOptions& options);
        const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }

    public: // osg::Node

        void traverse(osg::NodeVisitor& nv);
        osg::BoundingSphere computeBound() const;

    public: // MapCallback adapter functions

        void onMapInfoEstablished( const MapInfo& mapInfo ); // not virtual!
        void onMapModelChanged( const MapModelChange& change ); // not virtual!

        UID getUID() const;

    public: // statics    
        static void registerEngine( MPTerrainEngineNode* engineNode );
        static void getEngineByUID( UID uid, osg::ref_ptr<MPTerrainEngineNode>& output );

    public:
        class ElevationChangedCallback : public VisibleLayerCallback
        {
        public:
            ElevationChangedCallback( MPTerrainEngineNode* terrain );

            void onVisibleChanged(VisibleLayer* layer);

            MPTerrainEngineNode* _terrain;
            friend class MPTerrainEngineNode;
        };

    protected:
        // override from TerrainEngineNode
        virtual void updateTextureCombining() { updateState(); }
        
        virtual void notifyExistingNodes(TerrainEngine::NodeCallback* cb);

    private:
        void init();
        void syncMapModel();

        // Reloads all the tiles in the terrain due to a data model change
        void refresh(bool force =false);
        virtual void dirtyTerrain();

        void addImageLayer( ImageLayer* layer );
        void addElevationLayer( ElevationLayer* layer );

        void removeImageLayer( ImageLayer* layerRemoved );
        void removeElevationLayer( ElevationLayer* layerRemoved );
        void toggleElevationLayer( ElevationLayer* layer );

        void moveImageLayer( unsigned int oldIndex, unsigned int newIndex );
        void moveElevationLayer( ElevationLayer* layer );
        
        void updateState(); 


    private:
        MPTerrainEngineOptions _terrainOptions;

        class TerrainNode* _terrain;
        UID                _uid;
        Revision           _mapModelRev;  // tracks revision changes int the map model
        Revision           _terrainRev;   // the revisinon of the rendered terrain (slightly different)
        Revision           _shaderLibRev;
        bool               _batchUpdateInProgress;
        bool               _refreshRequired;
        bool               _stateUpdateRequired;

        osg::ref_ptr< ElevationChangedCallback > _elevationCallback;

        osg::ref_ptr<TerrainEngine::NodeCallback> _normalMapInstaller;

        MapFrame* _update_mapf; // map frame for the main/update traversal thread

        // node registry is shared across all threads.
        osg::ref_ptr<TileNodeRegistry> _liveTiles;      // tiles in the scene graph.
        //osg::ref_ptr<TileNodeRegistry> _deadTiles;        // tiles that used to be in the scene graph.
        osg::ref_ptr<ResourceReleaser> _releaser;

        PerThread< osg::ref_ptr<KeyNodeFactory> > _perThreadKeyNodeFactories;
        KeyNodeFactory* getKeyNodeFactory();

        osg::Timer _timer;
        unsigned   _tileCount;
        double     _tileCreationTime;
        int        _primaryUnit;
        int        _secondaryUnit;
        int        _elevationTextureUnit;
        int        _normalMapUnit;

        osg::ref_ptr< TileModelFactory > _tileModelFactory;

        Threading::Mutex _renderBinMutex;
        osg::ref_ptr<osgUtil::RenderBin> _terrainRenderBinPrototype;
        osg::ref_ptr<osgUtil::RenderBin> _payloadRenderBinPrototype;

        MPTerrainEngineNode( const MPTerrainEngineNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) { }
    };

} } } // namespace osgEarth::Drivers::MPTerrainEngine

#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_ENGINE_NODE_H
