/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2019 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/>
*/
#ifndef OSGEARTHUTIL_TERRAINPROFILE
#define OSGEARTHUTIL_TERRAINPROFILE

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

namespace osgEarth {     
    class MapNode;
}
    
namespace osgEarth { namespace Util {

    /**
     * Stores the results of a terrain profile calculation
     */
    class OSGEARTHUTIL_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 OSGEARTHUTIL_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 onTileAdded(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::Util

#endif // OSGEARTHUTIL_TERRAINPROFILE
