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

#include <osgEarthProcedural/Export>
#include <osgEarthProcedural/Biome>
#include <osgEarthProcedural/BiomeManager>
#include <osgEarth/ImageLayer>
#include <osgEarth/LayerReference>
#include <osgEarth/FeatureSource>
#include <osgEarth/CoverageLayer>

namespace osgEarth
{
    namespace Procedural
    {
        using namespace osgEarth;

        struct OSGEARTHPROCEDURAL_EXPORT LandCoverSample
        {
            OE_OPTION(std::string, biomeid, {});
            OE_OPTION(float, dense, 0.0f);
            OE_OPTION(float, lush, 0.0f);
            OE_OPTION(float, rugged, 0.0f);
            OE_OPTION(std::string, material, {});
            OE_OPTION(std::string, soiltype, {});
            OE_PROPERTY(std::vector<std::string>, traits, {});

            LandCoverSample() = default;

            LandCoverSample(const Config& conf)
            {
                conf.get("biome_id", biomeid());
                conf.get("traits", traits());
                conf.get("dense", dense());
                conf.get("lush", lush());
                conf.get("rugged", rugged());
                conf.get("material", material());
                conf.get("soiltype", soiltype());
            }

            Config getConfig() const
            {
                Config conf;
                conf.set("biome_id", biomeid());
                conf.set("traits", traits());
                conf.set("dense", dense());
                conf.set("lush", lush());
                conf.set("rugged", rugged());
                conf.set("material", material());
                conf.set("soiltype", soiltype());
                return conf;
            }

            bool valid() const {
                return
                    (biomeid().isSet() && !biomeid()->empty()) ||
                    !traits().empty() ||
                    dense().isSet() ||
                    lush().isSet() ||
                    rugged().isSet() ||
                    material().isSet() ||
                    soiltype().isSet();
            }

            bool operator < (const LandCoverSample& rhs) const
            {
                OE_OPTION_LESS(biomeid, rhs.biomeid);
                OE_OPTION_LESS(dense, rhs.dense);
                OE_OPTION_LESS(lush, rhs.lush);
                OE_OPTION_LESS(rugged, rhs.rugged);
                OE_OPTION_LESS(material, rhs.material);
                OE_OPTION_LESS(soiltype, rhs.soiltype);
                if (traits() < rhs.traits()) return true;
                if (traits() > rhs.traits()) return false;
                return false;
            }

            using Factory = CoverageLayer::Factory<LandCoverSample>;
        };


        struct OSGEARTHPROCEDURAL_EXPORT BiomeSample
        {
            OE_OPTION(std::string, biomeid);

            BiomeSample() { }

            BiomeSample(const Config& conf)
            {
                conf.get("biome_id", biomeid());
            }

            Config getConfig() const

            {
                Config conf;
                conf.set("biome_id", biomeid());
                return conf;
            }

            bool valid() const {
                return
                    (biomeid().isSet() && !biomeid()->empty());
            }

            bool operator < (const BiomeSample& rhs) const
            {
                OE_OPTION_LESS(biomeid, rhs.biomeid);
                return false;
            }

            using Factory = CoverageLayer::Factory<BiomeSample>;
        };


        /**
         * BiomeLayer creates rasters that encode Biome indices.
         *
         * Each Biome has an associated integer index, corresponding to the order
         * in which it appears in the biome catalog. (NB: This index is totally
         * transparent to the user, and has no relationship to the Biome ID.)
         * For a given TileKey, this layer will create a raster for which each
         * pixel holds an index corresponding to a specific Biome.
         *
         * At runtime this object will scan each raster, determine which Biomes
         * are currently visible, and tell the BiomeManager to load the corresponding
         * assets.
         */
        class OSGEARTHPROCEDURAL_EXPORT BiomeLayer :
            public ImageLayer,
            public osg::Observer
        {
        public:
            class OSGEARTHPROCEDURAL_EXPORT Options : public ImageLayer::Options
            {
            public:
                META_LayerOptions(osgEarthProcedural, Options, ImageLayer::Options);
                OE_OPTION_LAYER(CoverageLayer, landCoverLayer);
                OE_OPTION_LAYER(CoverageLayer, biomeBaseLayer);
                OE_OPTION_SHAREDPTR(BiomeCatalog, biomeCatalog);
                OE_OPTION(Distance, blendRadius, Distance(0.0f, Units::METERS));
                OE_OPTION(float, blendPercentage, 0.0f);
                virtual Config getConfig() const;
            private:
                void fromConfig(const Config& conf);
            };

        public:
            META_Layer(osgEarthProcedural, BiomeLayer, Options, ImageLayer, Biomes);

            //! Radius around which to randomly permute biome selection
            const Distance& getBlendRadius() const;
            void setBlendRadius(const Distance& value);

            //! Percentage (of tile size) by which to blend biome data
            //! for a natural transitions betwen adjacent biomes.
            //! [0..1] default is 0.1
            void setBlendPercentage(float value);
            float getBlendPercentage() const;

            //! The catalog holding all biome information
            std::shared_ptr<const BiomeCatalog> getBiomeCatalog() const;

            //! Manager that pages biome assets in and out
            BiomeManager& getBiomeManager() { return _biomeMan; }

            //! Coverage layer that specifies the base biome ID selection.
            //! This is a coverage that maps geographic regions to biome IDs.
            CoverageLayer* getBiomeBaseLayer() const;

            //! Coverage layer that can modify the biome ID selection based on
            //! land cover mappings. This will override a biomeID set by the 
            //! base biome layer.
            //! TODO: It is kind of odd to have both the base layer and the
            //!   land cover layer...consider consolidating.
            CoverageLayer* getLandCoverLayer() const;

            //! Whether to automatically load and unload biomes, or to 
            //! allow the user to manually do so with the setBiome() call.
            //! Default is true, meaning that as the user navigates the map,
            //! the manager will automatically load and unload biomes 
            //! according to location.
            //! NB: This is for debugging only -- not necessarily robust!
            void setAutoBiomeManagement(bool value);
            bool getAutoBiomeManagement() const;

        public: // Layer

            void addedToMap(const Map*) override;

            void removedFromMap(const Map*) override;

        protected: // Layer

            void init() override;

            Status openImplementation() override;

            Status closeImplementation() override;

            void resizeGLObjectBuffers(unsigned) override;

            void releaseGLObjects(osg::State*) const override;

        protected: // ImageLayer

            GeoImage createImageImplementation(
                const TileKey& key,
                ProgressCallback* progress) const override;

            void postCreateImageImplementation(
                GeoImage& createdImage,
                const TileKey& key,
                ProgressCallback* progress) const override;

        private:
            // handles memory residency of biome asset data
            BiomeManager _biomeMan;

            // whether to automatically load and unload biomes based on
            // the vector control set (defaults to true)
            bool _autoBiomeManagement;

            using Tracker = std::unordered_map<osg::Object*, TileKey>;
            mutable Mutexed<Tracker> _tracker;

            class BiomeTrackerToken : public osg::Object {
            public:
                META_Object(osgEarth, BiomeTrackerToken);
                BiomeTrackerToken() = default;
                BiomeTrackerToken(std::set<int>&& seen, BiomeLayer* layer) : _biome_index_set(seen), _biomeLayer(layer) { }
                BiomeTrackerToken(const BiomeTrackerToken& rhs, const osg::CopyOp& op) { }
                ~BiomeTrackerToken();
                std::set<int> _biome_index_set;
                osg::observer_ptr<BiomeLayer> _biomeLayer;
            };
            void trackerTokenDeleted(BiomeTrackerToken*);
            friend BiomeTrackerToken;

            void trackImage(
                GeoImage& image,
                const TileKey& key,
                std::set<int>& biomeids) const;

            using WeakCache = std::unordered_map<
                TileKey,
                osg::observer_ptr<osg::Image>>;

            mutable Mutexed<WeakCache> _imageCache;

            LandCoverSample::Factory::Ptr _landCoverFactory;
            BiomeSample::Factory::Ptr _biomeFactory;

        protected:
            virtual ~BiomeLayer() { }
        };

    }
} // namespace osgEarth::Procedural

