/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#pragma once

#include <osgEarth/Common>
#include <osgEarth/Config>
#include <osgEarth/Color>
#include <osgEarth/CullingUtils>
#include <osg/LOD>

namespace osgEarth
{
    // Options structure for a terrain engine (internal)
    class OSGEARTH_EXPORT TerrainOptions : public DriverConfigOptions
    {
    public:
        META_ConfigOptions(osgEarth, TerrainOptions, DriverConfigOptions);
        OE_OPTION(int, tileSize, 17);
        OE_OPTION(float, minTileRangeFactor, 7.0f);
        OE_OPTION(bool, clusterCulling, true);
        OE_OPTION(unsigned, maxLOD, 19u);
        OE_OPTION(unsigned, minLOD, 0u);
        OE_OPTION(unsigned, firstLOD, 0u);
        OE_OPTION(bool, enableLighting, true);
        OE_OPTION(bool, enableBlending, true);
        OE_OPTION(unsigned, minNormalMapLOD, 0u);
        OE_OPTION(unsigned, normalMapTileSize, 256u);
        OE_OPTION(bool, gpuTessellation, false);
        OE_OPTION(float, tessellationLevel, 2.5f);
        OE_OPTION(float, tessellationRange, 75.0f);
        OE_OPTION(bool, debug, false);
        OE_OPTION(int, renderBinNumber, 0);
        OE_OPTION(unsigned, minExpiryFrames, 0u);
        OE_OPTION(double, minExpiryTime, 0.0);
        OE_OPTION(float, minExpiryRange, 0.0f);
        OE_OPTION(unsigned, maxTilesToUnloadPerFrame, ~0u);
        OE_OPTION(unsigned, minResidentTiles, 0u);
        OE_OPTION(bool, castShadows, false);
        OE_OPTION(LODMethod, lodMethod, LODMethod::CAMERA_DISTANCE);
        OE_OPTION(float, tilePixelSize, 256.0f);
        OE_OPTION(float, heightFieldSkirtRatio, 0.0f);
        OE_OPTION(Color, color, Color::White);
        OE_OPTION(bool, progressive, true);
        OE_OPTION(bool, useNormalMaps, true);
        OE_OPTION(bool, normalizeEdges, false);
        OE_OPTION(bool, morphTerrain, true);
        OE_OPTION(bool, morphImagery, true);
        OE_OPTION(unsigned, mergesPerFrame, ~0u);
        OE_OPTION(float, priorityScale, 1.0f);
        OE_OPTION(std::string, textureCompression, {});
        OE_OPTION(unsigned, concurrency, 4u);
        OE_OPTION(bool, useLandCover, true);
        OE_OPTION(float, screenSpaceError, 0.0f);
        OE_OPTION(unsigned, maxTextureSize, 65536u);
        OE_OPTION(bool, visible, true);
        OE_OPTION(bool, createTilesAsync, true);
        OE_OPTION(bool, createTilesGrouped, true);
        OE_OPTION(bool, restrictPolarSubdivision, true);
        OE_OPTION(bool, gpuPaging, false);
        OE_OPTION(float, tessellationResolution, 5000.0f);
        OE_OPTION(float, tessellationMinLevel, 0.1f);
        OE_OPTION(float, tessellationMaxLevel, 7.0f);

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

    class OSGEARTH_EXPORT TerrainOptionsAPI
    {
    public:
        //! Size of each dimension of each terrain tile, in verts.
        //! Ideally this will be a power of 2 plus 1, i.e.: a number X
        //! such that X = (2^Y)+1 where Y is an integer >= 1. Default=17.
        void setTileSize(const int& value);
        const int& getTileSize() const;

        //! The minimum tile LOD range as a factor of a tile's radius. Default = 7.0
        void setMinTileRangeFactor(const float& value);
        const float& getMinTileRangeFactor() const;

        //! Whether cluster culling is enabled on terrain tiles. Deafult=true
        void setClusterCulling(const bool& value);
        const bool& getClusterCulling() const;

        //! (Legacy property)
        //! The maximum level of detail to which the terrain should subdivide.
        //! If you leave this unset the terrain will subdivide until the map layers
        //! stop providing data (default behavior). If you set a value, the terrain
        //! will stop subdividing at the specified LOD even if higher-resolution
        //! data is available. (It still might stop subdividing before it reaches
        //! this level if data runs out
        void setMaxLOD(const unsigned& value);
        const unsigned& getMaxLOD() const;

        //! (Legacy property)
        //! The lowest LOD to display. By default, the terrain begins at LOD 0.
        //! Set this to start the terrain tile mesh at a higher LOD.
        //! Don't set this TOO high though or you will run into memory problems.
        void setFirstLOD(const unsigned& value);
        const unsigned& getFirstLOD() const;

        //! Whether to explicity enable or disable GL lighting on the map node.
        //! Default = true
        void setEnableLighting(const bool& value);
        const bool& getEnableLighting() const;
            
        //! (Legacy property)
        //! Whether to enable blending on the terrain. Default is true.
        void setEnableBlending(const bool& value);
        const bool& getEnableBlending() const;

        //! Minimum level of detail at which to generate elevation-based normal maps,
        //! assuming normal maps have been activated. This mitigates the overhead of 
        //! calculating normal maps for very high altitude scenes where they are no
        //! of much use. Default is 0 (zero).
        void setMinNormalMapLOD(const unsigned& value);
        const unsigned& getMinNormalMapLOD() const;

        //! Size of normal map textures, in pixels, in each dimension.
        void setNormalMapTileSize(const unsigned& vlaue);
        const unsigned& getNormalMapTileSize() const;

        //! Whether the terrain engine will be using GPU tessellation shaders.
        //! Even if the terrain option is set to true, the getting will return
        //! false if tessellation shader support is not available.
        void setGPUTessellation(bool value);
        bool getGPUTessellation() const;

        //! GPU tessellation level
        [[deprecated]]
        void setTessellationLevel(const float& value);
        [[deprecated]]
        const float& getTessellationLevel() const;

        //! Maximum range in meters to apply GPU tessellation
        [[deprecated]]
        void setTessellationRange(const float& value);
        [[deprecated]]
        const float& getTessellationRange() const;

        //! Target resolution when using GPU tessellation
        void setTessellationResolution(const float& value);
        const float& getTessellationResolution() const;

        //! Minimum tessellation level when using GPU tessellation
        void setTessellationMinLevel(const float& value);
        const float& getTessellationMinLevel() const;

        //! Maximum tessellation level when using GPU tessellation
        void setTessellationMaxLevel(const float& value);
        const float& getTessellationMaxLevel() const;

        //! Render bin number for the terrain
        void setRenderBinNumber(const int& value);
        const int& getRenderBinNumber() const;

        //! Minimum number of frames before unused terrain data is eligible to expire
        void setMinExpiryFrames(const unsigned& value);
        const unsigned& getMinExpiryFrames() const;

        //! Minimum time (seconds) before unused terrain data is eligible to expire
        void setMinExpiryTime(const double& value);
        const double& getMinExpiryTime() const;

        //! Minimun range (distance from camera) beyond which unused terrain data 
        //! is eligible to expire
        void setMinExpiryRange(const float& value);
        const float& getMinExpiryRange() const;

        //! Maximum number of terrain tiles to unload/expire each frame.
        void setMaxTilesToUnloadPerFrame(const unsigned& value);
        const unsigned& getMaxTilesToUnloadPerFrame() const;

        //! Minimum number of terrain tiles to keep in memory before expiring usused data
        void setMinResidentTiles(const unsigned& value);
        const unsigned& getMinResidentTiles() const;

        //! Whether the terrain should cast shadows - default is false
        void setCastShadows(const bool& value);
        const bool& getCastShadows() const;

        //! Mode to use when calculating LOD switching distances.
        //! Choices are TerrainLODMode::CAMERA_DISTANCE (default) or TerrainLODMode::SCREEN_SPACE
        void setLODMethod(const LODMethod& value);
        const LODMethod& getLODMethod() const;

        //! Size of the tile, in pixels, when using rangeMode = PIXEL_SIZE_ON_SCREEN
        void setTilePixelSize(const float& value);
        const float& getTilePixelSize() const;

        //! Ratio of skirt height to tile width. The "skirt" is geometry extending
        //! down from the edge of terrain tiles meant to hide cracks between adjacent
        //! levels of detail. Default is 0 (no skirt).
        void setHeightFieldSkirtRatio(const float& value);
        const float& getHeightFieldSkirtRatio() const;

        //! Color of the untextured globe (where no imagery is displayed) (default is white)
        void setColor(const Color& value);
        const Color& getColor() const;

        //! Whether to load levels of details one after the other, instead of 
        //! prioritizing them based on the camera position. (default = false)
        void setProgressive(const bool& value);
        const bool& getProgressive() const;

        //! Whether to generate normal map textures. Default is true
        void setUseNormalMaps(const bool& value);
        const bool& getUseNormalMaps() const;

        //! Whether to include landcover textures. Default is true.
        void setUseLandCover(const bool& value);
        const bool& getUseLandCover() const;

        //! Whether to average normal vectors on tile boundaries. Doing so reduces the
        //! the appearance of seams when using lighting, but requires extra CPU work.
        void setNormalizeEdges(const bool& value);
        const bool& getNormalizeEdges() const;

        //! Whether to morph terrain data between terrain tile LODs.
        //! This feature is not available when rangeMode is PIXEL_SIZE_ON_SCREEN
        void setMorphTerrain(const bool& value);
        const bool& getMorphTerrain() const;

        //! Whether to morph imagery between terrain tile LODs.
        //! This feature is not available when rangeMode is PIXEL_SIZE_ON_SCREEN
        void setMorphImagery(const bool& value);
        const bool& getMorphImagery() const;

        //! Maximum number of tile data merges permitted per frame. 0 = infinity.
        void setMergesPerFrame(const unsigned& value);
        const unsigned& getMergesPerFrame() const;

        //! Texture compression to use by default on terrain image textures
        void setTextureCompressionMethod(const std::string& method);
        const std::string& getTextureCompressionMethod() const;

        //! Target concurrency of terrain data loading operations. Default = 4.
        void setConcurrency(const unsigned& value);
        const unsigned& getConcurrency() const;

        //! Screen space error for PIXEL SIZE ON SCREEN LOD mode
        void setScreenSpaceError(const float& value);
        const float& getScreenSpaceError() const;

        //! Sets a maximum size for textures used by the terrain
        void setMaxTextureSize(const unsigned& value);
        const unsigned& getMaxTextureSize() const;

        //! Whether to render the terrain
        void setVisible(const bool& value);
        const bool& getVisible() const;

        //! Whether the engine should create child tiles asynchronously
        void setCreateTilesAsync(const bool& value);
        const bool& getCreateTilesAsync() const;

        //! Whether the engine should create all child tiles in a group
        void setCreateTilesGrouped(const bool& value);
        const bool& getCreateTilesGrouped() const;

        //! Whether to stop a geocentric mesh from subdividing all the way at the poles.
        void setRestrictPolarSubdivision(const bool& value);
        const bool& getRestrictPolarSubdivision() const;

        //! Whether to use GPU-based paging of terrain textures (in NVGL mode only)
        void setGPUPaging(const bool& value);
        const bool& getGPUPaging() const;

        TerrainOptionsAPI();
        TerrainOptionsAPI(TerrainOptions*);
        TerrainOptionsAPI(const TerrainOptionsAPI&);

    public: // Legacy support

        //! Sets the name of the terrain engine driver to use
        void setDriver(const std::string& name);

    private:
        friend class MapNode;
        friend class TerrainEngineNode;
        TerrainOptions* _ptr;
        TerrainOptions& options() { return *_ptr; }
        const TerrainOptions& options() const { return *_ptr; }
    };

} // namespace osgEarth
