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

#include <osgEarth/Common>
#include <osgEarth/ElevationLayer>
#include <osgEarth/ElevationPool>
#include <osgEarth/LayerReference>
#include <osgEarth/FeatureSource>
#include <osgEarth/Session>
#include <osgEarth/StyleSheet>

namespace osgEarth { namespace Contrib
{
    /**
     * Elevation layer that overlays modified elevation samples intended to
     * flatten the terrain around vector features. The use case is to make
     * roads flat or prevent rivers and lakes from sloping with the terrain.
     */
    class OSGEARTH_EXPORT FlatteningLayer : public ElevationLayer
    {
    public:
        class OSGEARTH_EXPORT Options : public ElevationLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, ElevationLayer::Options);
            OE_OPTION_LAYER(FeatureSource, featureSource);
            OE_OPTION_VECTOR(ConfigOptions, filters);
            OE_OPTION_LAYER(StyleSheet, styleSheet);
            OE_OPTION(Expression<double>, lineWidth, 10);
            OE_OPTION(Expression<double>, bufferWidth, 20);
            OE_OPTION(bool, fill, false);
            virtual Config getConfig() const;

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

    public:
        META_Layer(osgEarth, FlatteningLayer, Options, ElevationLayer, FlattenedElevation);

        //! Feature source layer to get features from
        void setFeatureSource(FeatureSource* layer);
        FeatureSource* getFeatureSource() const { return options().featureSource().getLayer(); }

        //! Style sheet for optional scripting
        void setStyleSheet(StyleSheet* layer);
        StyleSheet* getStyleSheet() const { return options().styleSheet().getLayer(); }

        //! For line features, the width around the line to flatten
        void setLineWidth(const Expression<double>& value);
        const Expression<double>& getLineWidth() const;

        //! Width of the buffer between the flattened terrain and the natural terrain,
        //! which will serve as a transition area.
        void setBufferWidth(const Expression<double>& value);
        const Expression<double>& getBufferWidth() const;

        //! Whether to write all samples (default=false) with source elev instead of
        //! writing NO_DATA_VALUE where no features exist
        void setFill(const bool& value);
        const bool& getFill() const;

    public: // ElevationLayer

        void init() override;

        Status openImplementation() override;

        Config getConfig() const override;

    protected: // ElevationLayer

        GeoHeightField createHeightFieldImplementation(
            const TileKey& key,
            ProgressCallback* progress) const override;

        //! called by the map when this layer is added
        void addedToMap(const class Map*) override;

        //! called by the map when this layer is removed
        void removedFromMap(const class Map*) override;

    protected:

        virtual ~FlatteningLayer();

    private:

        osg::ref_ptr<ElevationPool> _pool;
        mutable ElevationPool::WorkingSet _elevWorkingSet;
        osg::ref_ptr<Session> _session;
        FeatureFilterChain _filterChain;

        GeoHeightField createFromFeatures(const TileKey& key, ProgressCallback* progress) const;
        GeoHeightField createFromSDF(const TileKey& key, ProgressCallback* progress) const;
    };

} }

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::Contrib::FlatteningLayer::Options);
