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

#include <osgEarth/TerrainTileModel>
#include <osgEarth/TerrainOptions>
#include <osgEarth/TerrainEngineRequirements>
#include <osgEarth/ImageLayer>
#include <osgEarth/Progress>
#include <osgEarth/Threading>
#include <osgEarth/ElevationPool>
#include <osgEarth/TileMesher>

namespace osgEarth
{
    class ElevationLayerVector;
    class Map;

    /**
     * Set of layers that the TerrainTileModelFactory can use
     * to determine what data to create for a tile. If the manifest
     * is empty, that represents that the factory should create
     * data for ALL available layers. But if it contains at least
     * one entry, the creation is limited to those entries.
     */
    class OSGEARTH_EXPORT CreateTileManifest
    {
    public:
        //! Request data for a layer
        void insert(const Layer* layer);

        //! Does the manifest exclude this layer?
        bool excludes(const Layer* layer) const;

        //! Sets whether to apply the update progressively (in LOD order)
        void setProgressive(bool value);
        const optional<bool>& progressive() const { return _progressive; }

        //! Is the manifest empty (meaning ALL available layers should be loaded)?
        bool empty() const;

        //! Are the layers in the manifest up to date with the layers in the map?
        bool inSyncWith(const Map*) const;

        //! Update all the manifest layers with the latest layer revisions from the map
        void updateRevisions(const Map*);

        bool includes(UID uid) const;

        bool includes(const Layer* layer) const;

        bool includesElevation() const;

        bool includesConstraints() const;

        bool includesLandCover() const;

    private:
        typedef vector_map<UID, int> LayerTable;
        LayerTable _layers;
        bool _includesElevation = false;
        bool _includesConstraints = false;
        bool _includesLandCover = false;
        optional<bool> _progressive = false;
    };

    /**
     * Builds a TerrainTileModel from a map frame.
     */
    class OSGEARTH_EXPORT TerrainTileModelFactory : public osg::Referenced
    {
    public:
        /** Constructor */
        TerrainTileModelFactory(const TerrainOptions& options);

        /**
         * Creates a tile model and populates it with data from the map.
         *
         * @param map          Map frame from which to read source data
         * @param key          Tile key for which to create the model
         * @param manifest     Set of layers for which to fetch data (empty => all layers)
         * @param requirements Hints that tell the factory what types of data to add
         * @param progress     Progress tracking callback
         */
        virtual TerrainTileModel* createTileModel(
            const Map*                       map,
            const TileKey&                   key,
            const CreateTileManifest&        manifest,
            const TerrainEngineRequirements& requirements,
            ProgressCallback*                progress);

        //! Same as createTileModel, except that this method will create "fallback"
        //! data for each layer that doesn't have real data at the TileKey's LOD.
        //! The texture matrix will each layer will provide a scale and bias for
        //! sampling the corresponding texture.
        virtual TerrainTileModel* createStandaloneTileModel(
            const Map*                       map,
            const TileKey&                   key,
            const CreateTileManifest&        manifest,
            const TerrainEngineRequirements& requirements,
            ProgressCallback*                progress);

    protected:

        virtual void addColorLayers(
            TerrainTileModel*                model,
            const Map*                       map,
            const TerrainEngineRequirements& reqs,
            const TileKey&                   key,
            const CreateTileManifest&        manifest,
            ProgressCallback*                progress,
            bool                             standalone);

        virtual bool addImageLayer(
            TerrainTileModel* model,
            ImageLayer* layer,
            const TileKey& key,
            const TerrainEngineRequirements& reqs,
            ProgressCallback* progress);

        virtual void addStandaloneImageLayer(
            TerrainTileModel* model,
            ImageLayer* layer,
            const TileKey& key,
            const TerrainEngineRequirements& reqs,
            ProgressCallback* progress);

        virtual void addElevation(
            TerrainTileModel*            model,
            const Map*                   map,
            const TileKey&               key,
            const CreateTileManifest&    manifest,
            unsigned                     border,
            ProgressCallback*            progress);

        virtual void addStandaloneElevation(
            TerrainTileModel*            model,
            const Map*                   map,
            const TileKey&               key,
            const CreateTileManifest&    manifest,
            unsigned                     border,
            ProgressCallback*            progress);

        virtual bool addLandCover(
            TerrainTileModel*            model,
            const Map*                   map,
            const TileKey&               key,
            const TerrainEngineRequirements& requirements,
            const CreateTileManifest&    manifest,
            ProgressCallback*            progress);

        virtual void addStandaloneLandCover(
            TerrainTileModel*            model,
            const Map*                   map,
            const TileKey&               key,
            const TerrainEngineRequirements& requirements,
            const CreateTileManifest&    manifest,
            ProgressCallback*            progress);

        virtual void addMesh(
            TerrainTileModel*            model,
            const Map*                   map,
            const TileKey&               key,
            const TerrainEngineRequirements& requirements,
            const CreateTileManifest&    manifest,
            ProgressCallback*            progress);

    protected:

        Texture::Ptr createImageTexture(
            const osg::Image* image,
            const ImageLayer* layer) const;

        Texture::Ptr createCoverageTexture(
            const osg::Image* image) const;

        TerrainOptions _options;
        ElevationPool::WorkingSet _workingSet;
    };
}
