/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2020 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.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* 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/>
*/
#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(bool, compressNormalMaps, false);
        OE_OPTION(unsigned, minNormalMapLOD, 0u);
        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);

        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;

        //! (Currently not used)
        //! 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;

        //! 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
        void setTessellationLevel(const float& value);
        const float& getTessellationLevel() const;

        //! Maximum range in meters to apply GPU tessellation
        void setTessellationRange(const float& value);
        const float& getTessellationRange() 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;

        //! @deprecated
        //! Scale factor for background loading priority of terrain tiles.
        //! Default = 1.0. Make it higher to prioritize terrain loading over
        //! other modules.
        void setPriorityScale(const float& value);
        const float& getPriorityScale() const;

        //! @deprecated
        //! Whether to activate debugging mode
        void setDebug(const bool& value);
        const bool& getDebug() const;

        //! @deprecated
        //! Whether to compress the normal maps before sending to the GPU
        void setCompressNormalMaps(const bool& value);
        const bool& getCompressNormalMaps() const;

        //! @deprecated
        //! The minimum level of detail to which the terrain should subdivide (no matter what).
        //! 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 subdivide
        //! to the specified LOD no matter what (and may continue farther if higher-resolution
        //! data is available).
        void setMinLOD(const unsigned& value);
        const unsigned& getMinLOD() 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
