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

#include <osgEarth/Common>
#include <osgEarth/Caching>
#include <osgEarth/Profile>
#include <osgEarth/MapOptions>
#include <osgEarth/ImageLayer>
#include <osgEarth/ElevationLayer>
#include <osgEarth/ModelLayer>
#include <osgEarth/MaskLayer>
#include <osgEarth/Revisioning>
#include <osgEarth/ThreadingUtils>
#include <osgDB/ReaderWriter>
#include <osg/TransferFunction>

namespace osgEarth
{    
    class MapInfo;

    /**
     * Conveys atomic change actions to the map data model.
     */
    struct MapModelChange
    {
        enum ActionType {
            ADD_IMAGE_LAYER,
            REMOVE_IMAGE_LAYER,
            MOVE_IMAGE_LAYER,
            ADD_ELEVATION_LAYER,
            REMOVE_ELEVATION_LAYER,
            MOVE_ELEVATION_LAYER,
            ADD_MODEL_LAYER,
            REMOVE_MODEL_LAYER,
            MOVE_MODEL_LAYER,
            ADD_MASK_LAYER,
            REMOVE_MASK_LAYER,
            UNSPECIFIED
        };

        MapModelChange( ActionType action, Revision mapModeRev, Layer* layer, int firstIndex =-1, int secondIndex =-1 ) 
            : _action(action), _layer(layer), _modelRevision(mapModeRev), _firstIndex(firstIndex), _secondIndex(secondIndex) { }

        const ActionType& getAction() const { return _action; }
        const Revision& getRevision() const { return _modelRevision; }
        int getFirstIndex() const { return _firstIndex; }
        int getSecondIndex() const { return _secondIndex; }
        Layer* getLayer() const { return _layer; }
        ImageLayer* getImageLayer() const { return static_cast<ImageLayer*>(_layer.get()); }
        ElevationLayer* getElevationLayer() const { return static_cast<ElevationLayer*>(_layer.get()); }
        ModelLayer* getModelLayer() const { return static_cast<ModelLayer*>(_layer.get()); }
        MaskLayer* getMaskLayer() const { return static_cast<MaskLayer*>(_layer.get()); }

    private:
        ActionType _action;
        osg::ref_ptr<Layer> _layer;
        Revision _modelRevision;
        int _firstIndex, _secondIndex;
    };

    /**
     * Callback that the Map object uses to notify listeners of map data model changes.
     */
    struct OSGEARTH_EXPORT MapCallback : public osg::Referenced
    {
        virtual void onMapInfoEstablished( const MapInfo& mapInfo ) { } 

        virtual void onMapModelChanged( const MapModelChange& change );

        virtual void onImageLayerAdded( ImageLayer* layer, unsigned int index ) { }
        virtual void onImageLayerRemoved( ImageLayer* layer, unsigned int index ) { }
        virtual void onImageLayerMoved( ImageLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }

        virtual void onElevationLayerAdded( ElevationLayer* layer, unsigned int index ) { }
        virtual void onElevationLayerRemoved( ElevationLayer* layer, unsigned int index ) { }
        virtual void onElevationLayerMoved( ElevationLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }

        virtual void onModelLayerAdded( ModelLayer* layer, unsigned int index ) { }
        virtual void onModelLayerRemoved( ModelLayer* layer ) { }
        virtual void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex ) { }

        virtual void onMaskLayerAdded( MaskLayer* mask ) { }
        virtual void onMaskLayerRemoved( MaskLayer* mask ) { }
    };

    typedef std::list< osg::ref_ptr<MapCallback> > MapCallbackList;

    /**
     * Map is the main data model that the MapNode will render. It is a
     * container for all Layer objects (that contain the actual data) and
     * the rendering options.
     */
    class OSGEARTH_EXPORT Map : public osg::Referenced
    {
    public:
        /**
         * Constructs a new, empty map.
         */
        Map( const MapOptions& options =MapOptions() );

    public:
        /**
         * Gets the optons with which this map was created.
         */
        const MapOptions& getMapOptions() const { return _mapOptions; }

        /**
         * Gets the map's master profile. This value may not be available until 
         * after autoCalculateProfile has been called.
         */
        const Profile* getProfile() const;

        /**
         * Copies references of the map image layers into the output list.
         * This method is thread safe. It returns the map revision that was
         * in effect when the data was copied.
         */
        int getImageLayers( ImageLayerVector& out_layers, bool validLayersOnly =false ) const;

        /**
         * Gets the number of image layers in the map.
         */
        int getNumImageLayers() const;

        /**
         * Gets an image layer by name.
         */
        ImageLayer* getImageLayerByName( const std::string& name ) const;

        /**
         * Gets an image layer by its unique ID.
         */
        ImageLayer* getImageLayerByUID( UID layerUID ) const;

        /**
         * Gets an image layer at the specified index.
         */
        ImageLayer* getImageLayerAt( int index ) const;

        /**
         * Copies references of the elevation layers into the output list.
         * This method is thread safe. It returns the map revision that was
         * in effect when the data was copied.
         */
        int getElevationLayers( ElevationLayerVector& out_layers, bool validLayersOnly =false ) const;

        /**
         * Gets the number of elevation layers in the map.
         */
        int getNumElevationLayers() const;

        /**
         * Gets an elevation layer by name.
         */
        ElevationLayer* getElevationLayerByName( const std::string& name ) const;

        /**
         * Gets an elevation layer by its unique ID.
         */
        ElevationLayer* getElevationLayerByUID( UID layerUID ) const;

        /**
         * Gets an elevation layer at the specified index.
         */
        ElevationLayer* getElevationLayerAt( int index ) const;

        /** 
         * Copies references of the model layers into the output list.
         * This method is thread safe. It returns the map revision that was
         * in effect when the data was copied.
         */
        int getModelLayers( ModelLayerVector& out_layers, bool validLayersOnly =false ) const;

        /**
         * Gets the number of model layers in the map.
         */
        int getNumModelLayers() const;

        /**
         * Gets a model layer by name.
         */
        ModelLayer* getModelLayerByName( const std::string& name ) const;

        /**
         * Gets a model layer by its unique ID.
         */
        ModelLayer* getModelLayerByUID( UID layerUID ) const;

        /**
         * Gets the model layer at the specified index.
         */
        ModelLayer* getModelLayerAt( int index ) const;

        /**
         * Gets the model layer to draw as a terrain mask.
         */
        MaskLayer* getTerrainMaskLayer() const;

        /**
         * Adds a map layer callback to this map. This will be notified whenever layers are
         * added, removed, or re-ordered.
         */
        void addMapCallback( MapCallback* callback ) const;

        /**
         * Adds an image layer to the map.
         */
        void addImageLayer( ImageLayer* layer );

        /**
        * Adds an image layer to the map at the specified index.
        */
        void insertImageLayer( ImageLayer* layer, unsigned int index );

        /**
         * Removes an image layer from the map.
         */
        void removeImageLayer( ImageLayer* layer );

        /**
         * Moves (re-orders) an image layer to another index position in its list.
         */
        void moveImageLayer( ImageLayer* layer, unsigned int newIndex );

        /**
         * Adds an elevation layer to the map.
         */
        void addElevationLayer( ElevationLayer* layer );

        /**
         * Removes an elevation layer from the map.
         */
        void removeElevationLayer( ElevationLayer* layer );

        /**
         * Moves (re-orders) an elevation layer to another index position in its list.
         */
        void moveElevationLayer( ElevationLayer* layer, unsigned int newIndex );

        /**
         * Adds a new model layer to the map.
         */
        void addModelLayer( ModelLayer* layer );

        /**
         * Adds a new model layer to the map at the specified index.
         */
        void insertModelLayer( ModelLayer* layer, unsigned int index );

        /**
         * Removes a model layer from the map.
         */
        void removeModelLayer( ModelLayer* layer );

        /**
         * Moves (re-orders) an image layer to another index position in its list.
         */
        void moveModelLayer( ModelLayer* layer, unsigned int newIndex );

        /**
         * Set a layer to use as a terrain mask. The map engine will draw
         * this layer using the stencil buffer.
         */
        void setTerrainMaskLayer( MaskLayer* layer );

        /**
         * Removed a terrain mask layer that was set with setTerrainMaskLayer().
         */
        void removeTerrainMaskLayer();

    public:
        /**
         * Gets the user-provided options structure stored in this map.
         */
        const osgDB::ReaderWriter::Options* getGlobalOptions() const;

        void setGlobalOptions( const osgDB::ReaderWriter::Options* options );

        /**
         * Sets the readable name of this map.
         */
        void setName( const std::string& name );

        /** 
         * Gets the readable name of this map.
         */
        const std::string& getName() const { return _name; }

        /**
         * Creates a heightfield for the region covered by the given TileKey, falling back on
         * lower resolutions if necessary. 
         *
         * @param key
         *      Tile key defining the region (and ideal LOD) for which to return a heightfield
         * @param samplePolicy
         *      See enum SamplePolicy in this class.
         */
        bool getHeightField(
            const TileKey& key,
            bool fallback,
            osg::ref_ptr<osg::HeightField>& out_hf,
            ElevationInterpolation interpolation =INTERP_AVERAGE,
            ElevationSamplePolicy samplePolicy =SAMPLE_FIRST_VALID,
            ProgressCallback* progress = 0) const;

		/**
		 * Sets the Cache for this Map. Set to NULL for no cache.
		 */
		void setCache( Cache* cache );

		/**
		 * Gets the Cache for this Map
		 */
		Cache* getCache() const;

        /**
         * Gets the revision # of the map. The revision # changes every time
         * you add, remove, or move layers. You can use this to track changes
         * in the map model (as a alternative to installing a MapCallback).
         */
        Revision getDataModelRevision() const;

        /**
         * Convenience function that returns TRUE if the map cs type is
         * geocentric.
         */
        bool isGeocentric() const;

        /**
         * Synronizes a map frame to the current revision of the map's data model.
         * Returns true if new Map model data was available and a sync occurred;
         * returns false if nothing changed.
         */    
        bool sync( class MapFrame& frame ) const;    

        enum ModelParts {
            IMAGE_LAYERS     = 1 << 0,
            ELEVATION_LAYERS = 1 << 1,
            MODEL_LAYERS     = 1 << 2,
            TERRAIN_LAYERS   = IMAGE_LAYERS | ELEVATION_LAYERS,
            ENTIRE_MODEL     = 0xff
        };

    protected:

        ~Map() { }

    private:

        MapOptions _mapOptions;
        std::string _name;
        ImageLayerVector _imageLayers;
        ElevationLayerVector _elevationLayers;
        ModelLayerVector _modelLayers;
        osg::ref_ptr<MaskLayer> _terrainMaskLayer;
        MapCallbackList _mapCallbacks;
        osg::ref_ptr<const osgDB::ReaderWriter::Options> _globalOptions;
        Threading::ReadWriteMutex _mapDataMutex;
        osg::ref_ptr<const Profile> _profile;
		osg::ref_ptr<Cache> _cache;
        Revision _dataModelRevision;

    private:
        void calculateProfile();
    };


    /**
     * A convenience class that combines a general geospatial profile and additional 
     * information about the map itself.
     */
    class MapInfo
    {
    public:
        MapInfo( const Map* map )
            : _profile( map->getProfile() ),
              _isGeocentric( map->isGeocentric() ),
              _isCube( map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE ) { }
        
        const Profile* getProfile() const { return _profile; }

        bool isGeocentric() const { return _isGeocentric; }
        bool isCube() const { return _isCube; }
        bool isPlateCarre() const { return !_isGeocentric && isGeographicSRS(); }

        bool isProjectedSRS() const { return !isProjectedSRS(); }
        bool isGeographicSRS() const { return _profile->getSRS()->isGeographic(); }        

    private:
        osg::ref_ptr<const Profile> _profile;
        bool _isGeocentric, _isCube;
    };


    /**
     * A "snapshot in time" of a Map model revision. Use this class to get a safe "copy" of
     * the map model lists that you can use without worrying about the model changing underneath
     * you from another thread.
     */
    class OSGEARTH_EXPORT MapFrame
    {
    public:
        /**
         * Constructs a new map frame.
         */
        MapFrame( const Map* map, Map::ModelParts parts =Map::TERRAIN_LAYERS, const std::string& name ="" );

        /**
         * Constructs a map frame that only makes a copy of "valid" data items.
         * Warning: if you ask to copyValidDataOnly, the indexOf() methods may not return
         * values that correspond to the source data model!
         */
        MapFrame( const Map* map, bool copyValidDataOnly, Map::ModelParts parts =Map::TERRAIN_LAYERS, const std::string& name ="" );

        /**
         * A copy constructor with a new name (no sync happens)
         */
        MapFrame( const MapFrame& frame, const std::string& name ="" );

        /**
         * Synchronizes this frame with the source map model (only if necessary). Returns
         * true is new data was synced; false if nothing changed.
         */
        bool sync();

        /** Accesses the profile/rendering info about the source map. */
        const MapInfo& getMapInfo() const { return _mapInfo; }

        /** Convenience method to access the map's profile */
        const Profile* getProfile() const { return _mapInfo.getProfile(); }

        /** The image layer stack snapshot */
        const ImageLayerVector& imageLayers() const { return _imageLayers; }
        ImageLayer* getImageLayerAt( int index ) const { return _imageLayers[index].get(); }
        ImageLayer* getImageLayerByUID( UID uid ) const;
        ImageLayer* getImageLayerByName( const std::string& name ) const;

        /** The elevation layer stack snapshot */
        const ElevationLayerVector& elevationLayers() const { return _elevationLayers; }
        ElevationLayer* getElevationLayerAt( int index ) const { return _elevationLayers[index].get(); }
        ElevationLayer* getElevationLayerByUID( UID uid ) const;
        ElevationLayer* getElevationLayerByName( const std::string& name ) const;

        /** The model layer set snapshot */
        const ModelLayerVector& modelLayers() const { return _modelLayers; }
        ModelLayer* getModelLayerAt(int index) const { return _modelLayers[index].get(); }

        /** Gets the index of the layer in the layer stack snapshot. */
        int indexOf( ImageLayer* layer ) const;
        int indexOf( ElevationLayer* layer ) const;
        int indexOf( ModelLayer* layer ) const;

        /** Gets the map data model revision with which this frame is currently sync'd */
        Revision getRevision() const { return _mapDataModelRevision; }

        /**
         * Equivalent to the Map::createHeightField() method, but operates on the elevation stack
         * snapshot in this MapFrame.
         */
        bool getHeightField(
            const TileKey& key,
            bool fallback,
            osg::ref_ptr<osg::HeightField>& out_hf,
            ElevationInterpolation interpolation =INTERP_AVERAGE,
            ElevationSamplePolicy samplePolicy   =SAMPLE_FIRST_VALID,
            ProgressCallback* progress = 0) const; 

    private:
        bool _initialized;
        osg::ref_ptr<const Map> _map;
        std::string _name;
        MapInfo _mapInfo;
        Map::ModelParts _parts;
        bool _copyValidDataOnly;
        Revision _mapDataModelRevision;
        ImageLayerVector _imageLayers;
        ElevationLayerVector _elevationLayers;
        ModelLayerVector _modelLayers;
        friend class Map;
    };

}

#endif // OSGEARTH_MAP_H
