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

#include <osgEarth/TileLayer>
#include <osgEarth/Callbacks>
#include <osg/MixinVector>

namespace osgEarth
{
    /**
     * A map terrain layer containing elevation grid heightfields.
     */
    class OSGEARTH_EXPORT ElevationLayer : public TileLayer
    {
    public:
        class OSGEARTH_EXPORT Options : public TileLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, TileLayer::Options);
            OE_OPTION(std::string, verticalDatum);
            OE_OPTION(bool, interpretValuesAsOffsets, false);
            OE_OPTION(osg::Vec2f, clampBetween, osg::Vec2f(-FLT_MAX, FLT_MAX));
            Config getConfig() const override;
            void fromConfig(const Config& conf);
        };

    public:
        META_Layer_Abstract(osgEarth, ElevationLayer, Options, TileLayer);

        //! Callback fired when new data is created. This callback fires
        //! before the data is cached, and does NOT fire if the data
        //! was read from a cache.
        //! NOTE: This may be invoked from a worker thread. Use caution.
        Callback<void(const TileKey&, GeoHeightField&)> onCreate;

        //! Vertical datum of height data. This is metadata that tells osgEarth the
        //! vertical datum used to express the height values. This setting does
        //! not cause any transformation of the data, it only describes the data.
        void setVerticalDatum(const std::string& value);
        const std::string& getVerticalDatum() const;

        //! Whether this layer contains offsets instead of absolute elevation heights
        void setInterpretValuesAsOffsets(bool value);
        bool getInterpretValuesAsOffsets() const;

        //! Clamp source data values between a min and max.
        void setClampBetween(const osg::Vec2f& value);
        const osg::Vec2f& getClampBetween() const;

    public: // deprecated

        OE_DEPRECATED("use setInterpretValuesAsOffsets() instead")
        void setOffset(bool value) { setInterpretValuesAsOffsets(value); }

        OE_DEPRECATED("use getInterpretValuesAsOffsets() instead")
        bool getOffset() const { return getInterpretValuesAsOffsets(); }

        OE_DEPRECATED("use getInterpretValuesAsOffsets() instead")
        bool isOffset() const { return getInterpretValuesAsOffsets(); }

    public: // methods

        /**
         * Creates a GeoHeightField for this layer that corresponds to the extents and LOD
         * in the specified TileKey. The returned HeightField will always match the geospatial
         * extents of that TileKey.
         *
         * @param key TileKey for which to create a heightfield.
         */
        GeoHeightField createHeightField(const TileKey& key);

        /**
         * Creates a GeoHeightField for this layer that corresponds to the extents and LOD
         * in the specified TileKey. The returned HeightField will always match the geospatial
         * extents of that TileKey.
         *
         * @param key TileKey for which to create a heightfield.
         * @param progress Callback for tracking progress and cancelation
         */
        GeoHeightField createHeightField(const TileKey& key, ProgressCallback* progress);

        /**
         * Writes a height field for the specified key, if writing is
         * supported and the layer was opened with openForWriting.
         */
        Status writeHeightField(const TileKey& key, const osg::HeightField* hf, ProgressCallback* progress) const;

    protected: // Layer

        void init() override;

    protected: // TileLayer

        //! Override aspects of the layer Profile as needed
        void applyProfileOverrides(osg::ref_ptr<const Profile>& inOutProfile) const override;

    protected: // ElevationLayer

        //! Entry point for createHeightField
        GeoHeightField createHeightFieldInKeyProfile(const TileKey& key, ProgressCallback* progress);

        //! Subclass overrides this to generate image data for the key.
        //! The key will always be in the same profile as the layer.
        virtual GeoHeightField createHeightFieldImplementation(const TileKey&, ProgressCallback* progress) const
            { return GeoHeightField::INVALID; }

        //! Subalss can override this to enable writing heightfields.
        virtual Status writeHeightFieldImplementation(
            const TileKey& key,
            const osg::HeightField* hf,
            ProgressCallback* progress) const;

        virtual ~ElevationLayer() { }

    private:
        GeoHeightField assembleHeightField(const TileKey& key, ProgressCallback* progress);

        void normalizeNoDataValues(osg::HeightField* hf) const;

        Gate<TileKey> _sentry;
    };


    /**
     * Vector of elevation layers, with added methods.
     */
    class OSGEARTH_EXPORT ElevationLayerVector : public osg::MixinVector< osg::ref_ptr<ElevationLayer> >
    {
    public:
        /**
         * Populates an existing height field (hf must already exist) with height
         * values from the elevation layers.
         *
         * @param hf Heightfield object to populate; must be pre-allocated
         * @param resolutions If non-null, populate with resolution of each sample
         * @param key Tilekey for which to populate
         * @param haeProfile Optional geodetic (no vdatum) tiling profile to use
         * @param interpolation Elevation interpolation technique
         * @param progress Optional progress callback for cancelation
         * @return True if "hf" was populated, false if no real data was available for key
         */
        bool populateHeightField(
            osg::HeightField*      hf,
            std::vector<float>*    resolutions,
            const TileKey&         key,
            const Profile*         haeProfile,
            RasterInterpolation    interpolation,
            ProgressCallback*      progress ) const;

    public:
        /** Default ctor */
        ElevationLayerVector();

        /** Copy ctor */
        ElevationLayerVector(const ElevationLayerVector& rhs);
    };

} // namespace osgEarth

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::ElevationLayer::Options);
