/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#ifndef OSGEARTHUTIL_TERRAINPROFILE
#define OSGEARTHUTIL_TERRAINPROFILE

#include <osgEarth/Common>
#include <osgEarth/Terrain>
#include <osgSim/ElevationSlice>

namespace osgEarth {     
    class MapNode;
}
    
namespace osgEarth { namespace Contrib
{

    /**
     * Stores the results of a terrain profile calculation
     */
    class OSGEARTH_EXPORT TerrainProfile
    {
    public:
        /**
         * Create a new TerrainProfile
         */
        TerrainProfile();

        /**
         * Copy constructor
         */
        TerrainProfile(const TerrainProfile& profile);

        /** dtor */
        virtual ~TerrainProfile() { }

        /**
         * Gets the distance at the given index
         * @param i
         *        The index of the profile measurement
         */
        double getDistance( int i ) const;

        /**
         * Gets the total distance covered by this TerrainProfile
         */
        double getTotalDistance() const;

        /**
         * Gets the min and max elevations of this TerrainProfile
         * @param min
         *        The minimum elevation in meters of this TerrainProfile
         * @param max
         *        The maximum elevation in meters of this TerrainProfile
         */
        void getElevationRanges(double &min, double &max ) const;

        /**
         * Gets the elevation at the given index
         * @param i
         *        The index of the profile measurement
         */
        double getElevation( int i ) const;

        /**
         * Gets the number of elevation measurements in this TerrainProfile
         */
        unsigned int getNumElevations() const;
        
        /**
         * Add an elevation measurement to this TerrainProfile
         */
        void addElevation( double distance, double elevation );

        /**
         * Clears all measurements from this TerrainProfile
         */
        void clear();


    private:
        double _spacing;
        typedef std::pair<double,double> DistanceHeight;
        typedef std::vector<DistanceHeight> DistanceHeightList;
        DistanceHeightList                      _elevations;
    };


    /**
     * Computes a TerrainProfile between two points.  Monitors the scene graph for changes
     * to elevation and updates the profile.
     */
    class OSGEARTH_EXPORT TerrainProfileCalculator : public TerrainCallback
    {
    public:

        /**
         * Callback that is fired when the profile changes
         */
        struct ChangedCallback : public osg::Referenced
        {
        public:
            virtual void onChanged(const TerrainProfileCalculator* sender) {};
            virtual ~ChangedCallback() { }
        };

        typedef std::list< osg::observer_ptr<ChangedCallback> > ChangedCallbackList;


        /**
         * Creates a new TerrainProfileCalculator
         * @param mapNode
         *        The MapNode to compute the terrain profile against
         * @param start
         *        The start point of the profile
         * @param end
         *        The end point of the profile
         */
        TerrainProfileCalculator(osgEarth::MapNode* mapNode, const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end);

        /**
         * Creates a new TerrainProfileCalculator
         * @param mapNode
         *        The MapNode to compute the terrain profile against
         */
        TerrainProfileCalculator(osgEarth::MapNode* mapNode);

        /** dtor */
        virtual ~TerrainProfileCalculator();

        /**
         * Set the MapNode to compute the terrain profile against
         */
        void setMapNode( osgEarth::MapNode* mapNode );
    
        /**
         * Add a ChangedCallback
         */
        void addChangedCallback( ChangedCallback* callback );

        /**
         * Remove a ChangedCallback
         */
        void removeChangedCallback( ChangedCallback* callback );

        /**
         * Gets the computed TerrainProfile
         */
        const TerrainProfile& getProfile() const;

        /**
         * Gets the start point of the terrain profile
         */
        const GeoPoint& getStart() const;

        /**
         * Gets the start point of the terrain profile
         */
        GeoPoint getStart(AltitudeMode altMode) const;

        /**
         * Gets the end point of the terrain profile
         */
        const GeoPoint& getEnd() const;

        /**
         * Gets the end point of the terrain profile
         */
        GeoPoint getEnd(AltitudeMode altMode) const;

        /**
         * Sets the start and end points of the terrain profile
         */
        void setStartEnd(const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end);

        virtual void onTileUpdate(const osgEarth::TileKey& tileKey, osg::Node* graph, TerrainCallbackContext&);

        /**
         * Recomputes the terrain profile
         */
        void recompute();

        /**
         * Utility to directly compute a terrain profile
         * @param mapNode
         *        The MapNode to compute the terrain profile one
         * @param start
         *        The start point of the terrain profile
         * @param end
         *        The end point of the terrain profile
         * @param numSamples
         *        The number of samples to compute
         * @param profile
         *        The resulting TerrainProfile
         */
        static void computeTerrainProfile( osgEarth::MapNode* mapNode, const osgEarth::GeoPoint& start, const osgEarth::GeoPoint& end, TerrainProfile& profile);



    private:
        osgEarth::GeoPoint _start;
        osgEarth::GeoPoint _end;
        TerrainProfile _profile;
        osg::ref_ptr< osgEarth::MapNode > _mapNode;
        ChangedCallbackList _changedCallbacks;
    };

} } // namespace osgEarth::Tools

#endif // OSGEARTHUTIL_TERRAINPROFILE
