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

#include <osgEarth/Config>
#include <osgEarth/GeoData>
#include <osgEarth/Units>
#include <osgEarth/optional>
#include <osg/Node>
#include <osg/observer_ptr>

namespace osgEarth
{
    /**
     * Viewpoint stores a focal point (or a focal node) and camera parameters
     * relative to that point.
     */
    class OSGEARTH_EXPORT Viewpoint
    {
    public:
        /**
         * Constructs a blank (invalid) viewpoint.
         */
        Viewpoint();

        /**
         * Copy constructor.
         */
        Viewpoint(const Viewpoint& rhs);

        /**
         * Deserialize a Config into this viewpoint object.
         */
        Viewpoint(const Config& conf);

    public:

        /**
         * Returns true if this viewpoint contains either a valid focal point or
         * a valid tracked node.
         */
        bool isValid() const;
        bool valid() const { return isValid(); }

        /**
         * Gets or sets the name of the viewpoint.
         */
        optional<std::string>& name()             { return _name; }
        const optional<std::string>& name() const { return _name; }
        void setName(const std::string& value)    { _name = value; }

        /**
         * The geospatial location at which the camera is pointing. If the Node (below)
         * is set, this is ignored.
         */
        optional<GeoPoint>& focalPoint()             { return _point; }
        const optional<GeoPoint>& focalPoint() const { return _point; }
        void setFocalPoint(const GeoPoint& value)    { _point = value; }

        /**
         * The node in the scene graph at which the camera is pointing. The "getter" follows
         * the observer pattern; i.e. you must copy its value into a ref_ptr before safely
         * accessing it. getNode() returns true if the safe-reference was successful.
         *
         * If a node is set, the Focal Point is ignored.
         * Node will not serialize into a Config.
         */
        void setNode(osg::Node* value)                    { _node = value; }
        osg::ref_ptr<osg::Node> getNode() const;
        bool nodeIsSet() const                            { return _node.valid(); }

        /**
         * Heading (in degrees) of the camera relative to the focal point.
         */
        optional<Angle>& heading()             { return _heading; }
        const optional<Angle>& heading() const { return _heading; }
        void setHeading(const Angle& value)    { _heading = value; }

        /**
         * The pitch of the camera relative to the focal point.
         */
        optional<Angle>& pitch()             { return _pitch; }
        const optional<Angle>& pitch() const { return _pitch; }
        void setPitch(const Angle& value)    { _pitch = value; }

        /**
         * The distance from the camera to the focal point.
         */
        optional<Distance>& range()             { return _range; }
        const optional<Distance>& range() const { return _range; }
        void setRange(const Distance& value)    { _range = value; }

        /**
         * Local offsets from the focal point, in meters. For example if the Viewpoint
         * is looking at a Node, this will shift the focal point so it is offset from
         * the node in a cartesian coordinate system where X=east, Y=north, Z=up.
         */
        optional<osg::Vec3d>& positionOffset()             { return _posOffset; }
        const optional<osg::Vec3d>& positionOffset() const { return _posOffset; }
        void setPositionOffset(const osg::Vec3d& value)    { _posOffset = value; }

        /**
         * Returns a printable string with the viewpoint data
         */
        std::string toString() const;

        /**
         * Serialize this viewpoint to a config object.
         */
        Config getConfig() const;


    public:

        //! Convenience constructor - SRS is WGS84, angles are in degrees, range is in meters.
        Viewpoint(const char* name, double lon, double lat, double z, double heading, double pitch, double range);

    public:

        //! DTOR
        ~Viewpoint() { }

    private:
        optional<std::string> _name;
        optional<GeoPoint>    _point;
        optional<Angle>       _heading;
        optional<Angle>       _pitch;
        optional<Distance>    _range;
        optional<osg::Vec3d>  _posOffset;

        osg::observer_ptr<osg::Node> _node;
    };

} // namespace osgEarth

#endif // OSGEARTH_VIEWPOINT
