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

#include <osgEarth/GeoPositionNode>
#include <osgEarth/Style>
#include <osgEarth/Containers>
#include <osgEarth/Units>
#include <osg/Image>
#include <osgText/String>
    
namespace osgEarth
{
    /**
     * Defines for a labeling field associated with a TrackNode. A TrackNode
     * can have zero or more "fields", each being a text label rendered 
     * along with the node's icon.
     */
    struct /*no-export*/ TrackNodeField
    {
        /**
         * Constructs a new field definition.
         * @param symbol  Text symbol describing the field's appearance and placement
         * @param dynamic Whether the text label will be dynamic; i.e. whether the user
         *                intends to update it at runtime. Setting this to "false" for a
         *                static label yields better performance in a multithreaded app.
         */
        TrackNodeField( const TextSymbol* symbol, bool dynamic =true )
            : _symbol(symbol), _dynamic(dynamic) { }

        /** other constructors (internal) */
        TrackNodeField()
            : _dynamic(true) { }

        TrackNodeField( const TrackNodeField& rhs ) 
            : _symbol(rhs._symbol.get()), _dynamic(rhs._dynamic) { }

        osg::ref_ptr<const TextSymbol> _symbol;
        bool                           _dynamic;
    };

    /**
     * Schema that maps field names to field definitions.
     */
    typedef std::map<std::string, TrackNodeField> TrackNodeFieldSchema;

    /** 
     * TrackNode is a movable, single-point entity represented by an icon,
     * optional placeable text labels, and optional localized geometry.
     *
     * NOTE. TrackNode does not automatically support terrain clamping. This is
     * because its intention is for use as a trackable entity marker, and 
     * presumably the entity itself will be responsible for its own absolute
     * positioning (and clamping, if applicable).
     */
    class OSGEARTH_EXPORT TrackNode : public GeoPositionNode
    {
    public:
        META_AnnotationNode(osgEarth, TrackNode);

        /**
         * Constructs a new track node
         * @param mapNode     Map node under which this track will live
         * @param position    Initial position
         * @param image       Icon image to use
         * @param fieldSchema Schema for track label fields
         */
        TrackNode(
            const GeoPoint&             position,
            osg::Image*                 image,
            const TrackNodeFieldSchema& fieldSchema );

        /**
         * Constructs a new track node
         * @param mapNode     Map node under which this track will live
         * @param position    Initial position
         * @param style       Style containing an IconSymbol for the image
         * @param fieldSchema Schema for track label fields
         */
        TrackNode(
            const GeoPoint&             position,
            const Style&                style,
            const TrackNodeFieldSchema& fieldSchema );

        /** 
         * Sets the value of one of the field labels.
         * @param name  Field name as identified in the field schema.
         * @param value Value to which to set the field label.
         */
        void setFieldValue( const std::string& name, const std::string& value ) { setFieldValue(name, osgText::String(value)); }
        void setFieldValue( const std::string& name, const osgText::String& value );

        /**
         * Adds an arbitrary drawable to this track node. Useful for adding
         * user-defined graphics.
         * @param name     Name of the drawable (for later retrieval). The namespace
         *                 should not conflict with that of the field schema.
         * @param drawable Drawable to add.
         */
        void addDrawable( const std::string& name, osg::Drawable* drawable );

        /**
         * Gets a drawable (as previously added with addDrawable). 
         * Note: Make sure that if you are modifying a drawable, mark it with a 
         * DYNAMIC data variance so it will be thread-safe.
         */
        osg::Drawable* getDrawable( const std::string& name ) const;


        void setIconRotation(const Angle& angle);
        const Angle& getIconRotation() const { return _iconAngle; }
        
    public: // AnnotationNode

        virtual void setPriority(float value);
        
    protected: // AnnotationNode override

        virtual bool supportsRenderBinDetails() const { return false; }

    protected:

        virtual ~TrackNode() { }

    private:
        // required by META_Node, but this object is not cloneable
        TrackNode() { }
        TrackNode(const TrackNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL) { }

        void updateLayoutData();

        void construct();       
        
        static osg::observer_ptr<osg::StateSet> s_geodeStateSet;
        static osg::observer_ptr<osg::StateSet> s_imageStateSet;

        osg::ref_ptr<osg::StateSet> _imageStateSet;
        Angle _iconAngle;
        osg::Drawable* _iconDrawable = nullptr;

        Style _style;
        class osg::Group* _geode;
        TrackNodeFieldSchema _fieldSchema;

        using NamedDrawables = std::unordered_map<std::string, osg::Drawable*>;
        NamedDrawables _namedDrawables;

        void compile();
    };

}
