/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2013 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_ENGINE_MP_ENGINE_NODE_H
#define OSGEARTH_ENGINE_MP_ENGINE_NODE_H 1

#include <osgEarth/TerrainEngineNode>
#include <osgEarth/TextureCompositor>
#include <osgEarth/Map>
#include <osgEarth/Revisioning>
#include <osgEarth/ThreadingUtils>

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

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

using namespace osgEarth;

namespace osgEarth_engine_mp
{
    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);

    public: // internal TerrainEngineNode

        virtual void preInitialize( const Map* map, const TerrainOptions& options );
        virtual void postInitialize( const Map* map, const TerrainOptions& options );
        virtual void validateTerrainOptions( TerrainOptions& options );
        virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
        virtual osg::BoundingSphere computeBound() const;

    public: // osg::Node

        void traverse(osg::NodeVisitor& nv);

    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 unregisterEngine( UID uid );
        static void getEngineByUID( UID uid, osg::ref_ptr<MPTerrainEngineNode>& output );

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

           virtual void onVisibleChanged( TerrainLayer* layer );

            MPTerrainEngineNode* _terrain;
            friend class MPTerrainEngineNode;
        };

    protected:
        // override from TerrainEngineNode
        virtual void updateTextureCombining() { updateShaders(); }

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

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

        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( unsigned int oldIndex, unsigned int newIndex );
        
        void updateShaders(); 

    private:
        osgEarth::Drivers::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               _shaderUpdateRequired;
        bool               _rootTilesRegistered;
        Threading::Mutex   _rootTilesRegisteredMutex;

        osg::ref_ptr< ElevationChangedCallback > _elevationCallback;

        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.

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

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

        osg::Uniform* _verticalScaleUniform;

        osg::ref_ptr< TileModelFactory > _tileModelFactory;

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

} // namespace osgEarth_engine_mp

#endif // OSGEARTH_ENGINE_MP_ENGINE_NODE_H
