/* -*-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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * 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/>
 */

#ifndef OSGEARTH_ELEVATION_TERRAIN_LAYER_H
#define OSGEARTH_ELEVATION_TERRAIN_LAYER_H 1

#include <osgEarth/TileLayer>
#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, offset);
            OE_OPTION(ElevationNoDataPolicy, noDataPolicy);
            virtual Config getConfig() const;
        private:
            void fromConfig( const Config& conf );
        };

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

        //! Layer callbacks
        class OSGEARTH_EXPORT Callback : public osg::Referenced
        {
        public:
            //! Called 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.
            virtual void onCreate(const TileKey&, GeoHeightField&) { }
        };

        //! Vertical data identifier (options)
        void setVerticalDatum(const std::string& value);
        const std::string& getVerticalDatum() const;

        //! Whether this layer contains offsets instead of absolute elevation heights
        void setOffset(bool value);
        bool getOffset() const;
        bool isOffset() const { return getOffset(); }

        //! Poly for handling "no-data" elevation values
        void setNoDataPolicy(const ElevationNoDataPolicy& value);
        const ElevationNoDataPolicy& getNoDataPolicy() const;

        //! Override from VisibleLayer
        virtual void setVisible(bool value);

    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;

        //! Install a user callback
        void addCallback(Callback* callback);

        //! Remove a user callback
        void removeCallback(Callback* callback);

    protected: // Layer

        virtual void init() override;

    protected: // TileLayer

        //! Override aspects of the layer Profile as needed
        virtual 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:
        void assembleHeightField(
            const TileKey& key,
            osg::ref_ptr<osg::HeightField>& out_hf,
            ProgressCallback* progress) const;

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

        void invoke_onCreate(const TileKey&, GeoHeightField&);

        typedef std::vector< osg::ref_ptr<Callback> > Callbacks;
        Threading::Mutexed<Callbacks> _callbacks;

        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);

#endif // OSGEARTH_ELEVATION_TERRAIN_LAYER_H
