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

#include <osgEarthProcedural/Export>
#include <osgEarthProcedural/Biome>
#include <osgEarthProcedural/LifeMapLayer>
#include <osgEarthProcedural/BiomeLayer>
#include <osgEarthProcedural/BiomeManager>

#include <osgEarth/PatchLayer>
#include <osgEarth/LayerReference>

#include <osg/Drawable>

namespace osgEarth { namespace Procedural
{
    using namespace osgEarth;

    /**
     * Layer that renders trees and undergrowth.
     */
    class OSGEARTHPROCEDURAL_EXPORT VegetationLayer : public PatchLayer
    {
    public:
        class OSGEARTHPROCEDURAL_EXPORT Options : public PatchLayer::Options {
        public:
            META_LayerOptions(osgEarthProcedural, Options, PatchLayer::Options);

            //! Biomes layer in the map
            OE_OPTION_LAYER(BiomeLayer, biomeLayer);

            //! Whether to render with GL_ALPHA_TO_COVERAGE
            //! default = true
            OE_OPTION(bool, alphaToCoverage, true);

            //! Angle of view at which a polygon will become transparent
            //! default = 45 degrees
            OE_OPTION(Angle, impostorLowAngle, Angle(45.0, Units::DEGREES));

            //! Angle of view at which a polygon will become opaque
            //! default = 67.5 degrees
            OE_OPTION(Angle, impostorHighAngle, Angle(67.5, Units::DEGREES));

            //! Whether to use normal maps on impostor geometries
            //! default = true
            OE_OPTION(bool, useImpostorNormalMaps, true);

            //! Whether to use PBR maps on impostor geometries
            //! default = true
            OE_OPTION(bool, useImpostorPBRMaps, true);

            //! Whether to assume normal maps are RG-compressed
            //! default = true;
            OE_OPTION(bool, useRGCompressedNormalMaps, true);

            //! Scale factor for LOD-ing the far-away impostor vegetation
            //! default = 1.0f
            OE_OPTION(float, farLODScale, 1.0f);

            //! Scale factor for the LOD at which to transition from 
            //! high-resolution 3D models up close to impostor models
            //! at distance.
            //! default = 1.0f
            OE_OPTION(float, nearLODScale, 1.0f);

            //! Amount of an LOD boundary (from near to far) should be
            //! used to display a transition between the two LODs [0..1]
            //! default = 0.5f
            OE_OPTION(float, lodTransitionPadding, 0.5f);

            //! Maximum texture size to transfer to the GPU
            //! default = INT_MAX (no limit)
            OE_OPTION(unsigned, maxTextureSize, INT_MAX);

            //! OSG render bin number for vegetation render bins
            OE_OPTION(int, renderBinNumber, 3);

            //! Number of threads to use for background loading
            OE_OPTION(unsigned, threads, 2u);

            struct OSGEARTHPROCEDURAL_EXPORT Group
            {
                //! Whether to render this group at all
                //! default = true
                OE_OPTION(bool, enabled, false);

                //! Terrain LOD at which to render this group
                OE_OPTION(unsigned, lod, 0u);

                //! Not used
                OE_OPTION(float, maxRange, FLT_MAX);

                //! Number of assets to place per square kilometer
                //! default = 4096
                OE_OPTION(int, instancesPerSqKm, 4096);

                //! Whether assets in this group cast shadows
                //! default = false
                OE_OPTION(bool, castShadows, false);

                //! Percentage of overlap allowed in asset bounding spheres [0..1]
                //! default = 0.0f
                OE_OPTION(float, overlap, 0.0f);

                //! Base SSE scale factor for far visibility
                //! default = 1.0f
                OE_OPTION(float, farLODScale, 1.0f);

                //! Cut off for alpha discard/blend in the shader (0..1]
                //! default = 0.2f
                OE_OPTION(float, alphaCutoff, 0.2f);

                //! Maximum terrain resolution at which to clamp instances
                //! Defaults to zero (maximum available / slowest)
                OE_OPTION(Distance, clampingResolution);

                // internal
                osg::ref_ptr<osg::StateSet> _drawStateSet;
                BiomeManager::CreateImpostorFunction _createImpostor;

                Config getConfig() const;
            };

            using GroupMap = vector_map<std::string, Group>;
            OE_PROPERTY(GroupMap, groups, {});
            Group& group(const std::string& name);
            const Group& group(const std::string& name) const;

            virtual Config getConfig() const;
        private:
            void fromConfig(const Config& conf);
            Group _emptyGroup;
        };

    public:
        META_Layer(osgEarth, VegetationLayer, Options, PatchLayer, Vegetation);

        //! Layer containing biome data
        void setBiomeLayer(BiomeLayer*);
        BiomeLayer* getBiomeLayer() const;

        //! Layer containing life map data
        void setLifeMapLayer(LifeMapLayer*);
        LifeMapLayer* getLifeMapLayer() const;

        //! LOD at which to draw ground cover
        bool hasEnabledGroupAtLOD(unsigned lod) const;
        std::string getGroupAtLOD(unsigned lod) const;
        unsigned getGroupLOD(const std::string&) const;

        //! Whether any of the vegetation casts shadows on the terrain
        bool getCastShadows() const;

        //! Whether to enable alpha-to-coverage mode.
        //! Only use this when multisampling it ON
        void setUseAlphaToCoverage(bool value);
        bool getUseAlphaToCoverage() const;

        //! Whether you can enable A2C by calling setUseAlphaToCoverage(true).
        //! A2C requires multisampling to be enabled.
        bool isAlphaToCoverageAvailable() const {
            return _alphaToCoverageSupported;
        }

        //! Visibility range of the specified asset group
        //! This setting will take effect to next time the layer is opened.
        void setMaxRange(const std::string&, float);
        float getMaxRange(const std::string&) const;

        //! Whether to render the named group
        void setEnabled(const std::string&, bool);
        bool getEnabled(const std::string&) const;

        //! Sets the allowable instance overlap percentage for a group.
        //! Note: call dirty() to refresh after setting this value.
        //! @param groupName Name of the group for which to set overlap
        //! @param value Percentage [0..1] of overlap allowed.
        void setOverlapPercentage(const std::string& group, float value);
        float getOverlapPercentage(const std::string& group) const;

        //! The threshold for discarding fragments with high alpha
        void setAlphaCutoff(const std::string& group, float value);
        float getAlphaCutoff(const std::string& group) const;

        void setImpostorLowAngle(const Angle& value);
        const Angle& getImpostorLowAngle() const;

        void setImpostorHighAngle(const Angle& value);
        const Angle& getImpostorHighAngle() const;

        void setFarLODScale(float value);
        float getFarLODScale() const;

        void setNearLODScale(float value);
        float getNearLODScale() const;

        void setLODTransitionPadding(float value);
        float getLODTransitionPadding() const;
        
        void setUseImpostorNormalMaps(bool value);
        bool getUseImpostorNormalMaps() const;

        void setUseImpostorPBRMaps(bool value);
        bool getUseImpostorPBRMaps() const;

        void setMaxTextureSize(unsigned value);
        unsigned getMaxTextureSize() const;

        void setShowTileBoundingBoxes(bool value);
        bool getShowTileBoundingBoxes() const;

        //! Force regeneration of all visible data
        void dirty();

        Stats reportStats() const override;

    public:
        struct Placement
        {
            Placement() = default;
            Placement(const Placement& rhs) = default;
            Placement& operator =(Placement&& rhs) = default;
            Placement(Placement&&) = default;
            Placement& operator =(const Placement& rhs) = default;

            //! Location in map coordinates
            OE_PROPERTY(osg::Vec3d, mapPoint, {});

            //! Location in tile local tangent plane coordinates
            OE_PROPERTY(osg::Vec2d, localPoint, {});

            //! Location in tile normalized coordinates [0..1]
            OE_PROPERTY(osg::Vec2f, uv, {});

            //! Scale of asset
            OE_PROPERTY(osg::Vec3f, scale, {});

            //! Rotation of asset in radians
            OE_PROPERTY(float, rotation, 0.f);

            //! Pointer to asset model record
            OE_PROPERTY(ResidentModelAsset::Ptr, asset, nullptr);

            //! Density of the LifeMap at this location
            OE_PROPERTY(float, density, 0.0f);

            //! Biome this asset came from
            const Biome* biome = nullptr;
        };

        //! Gets all the asset placement information for a given
        //! tile within the given asset group.
        //! @param key Tile key for which to generate asset placements
        //! @param group Group for which to generate placements (e.g. TREES or UNDERGROWTH)
        //! @param loadBiomesOnDemand When set to true, load the asset models necessary
        //!   for this tile key immediately if they are not already resident. During a normal
        //!   runtime frame loop this should be false since biome assets get loaded asynchronously.
        //!   Set to true if you are loading asset placements without a frame loop.
        //! @param output Output vector to populate with results
        //! @param progress Progress/cancelation tracker
        bool getAssetPlacements(
            const TileKey& key,
            const std::string& group,
            bool loadBiomesOnDemand,
            std::vector<Placement>& output,
            ProgressCallback* progress) const;

        //! Simulates placing an asset at a point, for debugging
        std::string simulateAssetPlacement(
            const GeoPoint& point,
            const std::string& group) const;

    protected:

        void init() override;
        Status openImplementation() override;
        Status closeImplementation() override;

    public:


        void modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const override;
        void update(osg::NodeVisitor& nv) override;
        void cull(const TileBatch&, osg::NodeVisitor& nv) const override;

        void addedToMap(const Map* map) override;
        void removedFromMap(const Map* map) override;
        void prepareForRendering(TerrainEngine*) override;

        void resizeGLObjectBuffers(unsigned maxSize) override;
        void releaseGLObjects(osg::State* state) const override;

    protected:
        virtual ~VegetationLayer();

        struct LayerAcceptor : public PatchLayer::AcceptCallback
        {
            VegetationLayer* _layer;
            LayerAcceptor(VegetationLayer* layer) : _layer(layer) { }
            bool acceptLayer(osg::NodeVisitor& nv, const osg::Camera* camera) const;
            bool acceptKey(const TileKey& key) const;
        };
        friend struct LayerAcceptor;

        LayerReference<LifeMapLayer> _lifeMapLayer;
        LayerReference<BiomeLayer> _biomeLayer;
        osg::observer_ptr<const Map> _map;

        void reset();

        // call from UPDATE peridoically to see if new biomes
        // caused new assets to page in.
        // Returns true if the revision changes and a job to load new
        // assets started.
        bool checkForNewAssets() const;

        // Result of background drawable-creation jobs
        using FutureDrawable = Future<osg::ref_ptr<osg::Drawable>>;

        // Combine the TileKey and the data revision into a struct
        // that uniquely identifies a version of a tile.
        struct TileKeyAndRevision
        {
            TileKey key;
            int revision;

            bool operator == (const TileKeyAndRevision& rhs) const {
                return key == rhs.key && revision == rhs.revision;
            }
            std::size_t operator()(const TileKeyAndRevision& rhs) const {
                return hash_value_unsigned(key.hash(), revision);
            }
        };

        // One tile's drawable vegetation.
        struct Tile
        {
            using Ptr = std::shared_ptr<Tile>;
            using WeakPtr = std::weak_ptr<Tile>;
            FutureDrawable _drawable;
            osg::ref_ptr<osg::Node> _debugBound;
        };

        using Tiles = std::unordered_map<
            TileKeyAndRevision,
            Tile::WeakPtr,
            TileKeyAndRevision>;

        // Shared (amongst all cameras) cache of vegetation tiles,
        // indexed by a revisioned tile key.
        mutable Mutexed<Tiles> _tiles;

        using Placeholders = std::unordered_map<
            TileKey,
            Tile::Ptr>;

        // Shared cache of tile placeholders. These are just tiles,
        // but indexed only by the tile key (no revision) and we 
        // use them to draw the old version of the tile while loading
        // a new revision.
        // NOTE: this "shares" the _tiles mutex. Only access when the
        // _tiles mutex is locked.
        mutable Placeholders _placeholders;

        // One camera's view on a tile (and its placeholder).
        // We update the matrix each frame as necessary just before
        // accepting the cull.
        struct TileView
        {
            Tile::Ptr _tile;
            Tile::Ptr _placeholder;
            bool _loaded;
            osg::ref_ptr<osg::RefMatrix> _matrix;
            TileView() : _loaded(false) { }
        };

        // Data unique to a specific camera traversal
        // (multiple views, shadow cams, etc)
        struct CameraState
        {
            using Ptr = std::shared_ptr<CameraState>;

            using TileViews = std::unordered_map<
                TileKeyAndRevision,
                TileView,
                TileKeyAndRevision>;

            // views indexed by the current key+revision
            TileViews _views;
        };

        using CameraStates = vector_map<
            osg::ref_ptr<const osg::Camera>,
            CameraState::Ptr>;

        // Collection tile views for each camera
        mutable Mutexed<CameraStates> _cameraState;

        // So we can timeout the layer and release resources
        mutable osg::FrameStamp _lastVisit;

        // noise texture for wind
        Texture::Ptr _noiseTex;

        struct ResidentBiomeModelAssetInstances {
            const Biome* biome;
            std::vector<ResidentModelAssetInstance> instances;
        };

        using AssetsByBiomeId = std::unordered_map<
            std::string, //id
            ResidentBiomeModelAssetInstances>;

        using AssetsByGroup = vector_map<
            std::string, // group name
            AssetsByBiomeId>;

        // Asynchronously loading resident asset collection
        mutable Future<AssetsByGroup> _newAssets;

        // Resident asset collection (created by the BiomeManager)
        mutable Mutexed<AssetsByGroup> _assets;

        // Track biome changes so we can reload as necessary
        mutable std::atomic_int _biomeRevision;

        // Uniform to scale the SSE
        osg::ref_ptr<osg::Uniform> _pixelScalesU;

        void configureImpostor(
            const std::string& groupName);

        Future<osg::ref_ptr<osg::Drawable>> createDrawableAsync(
            const TileKey&,
            const std::string&,
            const osg::BoundingBox& bbox,
            const osg::FrameStamp*,
            double explicit_birthday,
            float range) const;

        osg::ref_ptr<osg::Drawable> createDrawable(
            const TileKey&,
            const std::string&,
            const osg::BoundingBox& bbox,
            ProgressCallback* c) const;

        // Things that help us detect the presence of multisampling
        mutable bool _alphaToCoverageSupported = false;
        mutable bool _alphaToCoverageInstalled = false;
        bool _renderingSupported = true;
        bool _showTileBoundingBoxes = false;

        void updateAlphaToCoverage();
    };

} } // namespace osgEarth::Procedural

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Procedural::VegetationLayer::Options);
