#ifndef OSGEARTH_UTIL_OBJECT_LOCATOR_H
#define OSGEARTH_UTIL_OBJECT_LOCATOR_H

#include <osgEarthUtil/Common>
#include <osgEarth/Map>
#include <osgEarth/Revisioning>
#include <osg/MatrixTransform>

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    /**
     * ObjectLocator - a revisioned object that generates a positional matrix for a node.
     */
    class OSGEARTHUTIL_EXPORT ObjectLocator : public osg::Referenced, public Revisioned
    {
    public:

        /** Flags that represent separable location components. */
        enum Components {
            COMP_NONE           = 0x00,
            COMP_POSITION       = 0x01,
            COMP_HEADING        = 0x02,
            COMP_PITCH          = 0x04,
            COMP_ROLL           = 0x08,
            COMP_ORIENTATION    = COMP_HEADING | COMP_PITCH | COMP_ROLL,
            COMP_ALL            = COMP_POSITION | COMP_ORIENTATION
        };

        /** The order in which rotation are calculated */
        enum RotationOrder {
            HPR,
            RPH
        };

    public:

        /**
         * Constructs a new locator that will generate positional matricies based on
         * the specified SRS and rotation order.
         */
        ObjectLocator( const osgEarth::Map* map );

        /**
         * Constucts a new relative locator that inherits the mask of specified
         * components from a parent locator.
         */
        ObjectLocator(ObjectLocator* parent, unsigned int compsToInherit =COMP_ALL );

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

        /**
         * Sets the absolute OR relative positioning of this locator (depending on whether
         * this locator has a parent). Units conform to this Locator's SRS.
         */
        void setPosition( const osg::Vec3d& pos );
        const osg::Vec3d& getPosition() const { return _pos; }

        /**
         * Sets the absolute OR relative orientation of this locator (depending on whether
         * this locator has a parent). Units are Euler angle degrees.
         */
        void setOrientation( const osg::Vec3d& hpr_deg );
        const osg::Vec3d& getOrientation() const { return _hpr; }

        /**
         * The timestamp associated with this locator's position information.
         * (Note: setting the time does not "dirty" the locator)
         */
        void setTime( double t ) { _timestamp = t; }
        double getTime() const { return _timestamp; }

        /**
         * The order in which to calculate heading, pitch, and roll rotations
         */
        void setRotationOrder( RotationOrder value ) { _rotOrder = value; }
        RotationOrder getRotationOrder() const { return _rotOrder; }

        /**
         * The optional parent locator. If a Locator has a parent, it inherits position and
         * orientation from that parent as prescribed by the Components flags. Otherwise,
         * the Locator is absolute.
         */
        void setParentLocator( ObjectLocator* parent, unsigned int componentsToInherit =COMP_ALL );
        ObjectLocator* getParentLocator() { return _parentLoc.get(); }
        const ObjectLocator* getParentLocator() const { return _parentLoc.get(); }

        /** Policy for inheriting parent locator's components */
        void setComponentsToInherit( unsigned int compMask );
        unsigned int getComponentsToInherit() const { return _componentsToInherit; }

        /** Gets the map associated with this locator. */
        const Map* getMap() const { return _map.get(); }

        /** Whether the locator contains a valid position/orientation. */
        bool isEmpty() const;

        /** Whether this location contains valid data */
        bool isValid() const { return !_isEmpty && _map.valid(); }

    public:

        /**
         * Gets the aggregate position represented by this locator,
         * returning true upon success.
         */
        bool getLocatorPosition( osg::Vec3d& output ) const;

        /**
         * Gets the aggregate positioning matrix for this locator,
         * returning true upon success.
         */
        bool getPositionMatrix( osg::Matrixd& output ) const;

        /**
         * Gets the aggregate orientation (HPR degrees) represented by this locator,
         * returning true upon success.
         */
        bool getLocatorOrientation( osg::Vec3d& output ) const;

        /**
         * Gets the aggregate orientation matrix for this locator,
         * returning true upon success.
         */
        bool getOrientationMatrix( osg::Matrixd& output, unsigned inherit =COMP_ALL ) const;

        /**
         * Gets a matrix that can be used to position and orient an object corresponding
         * to this locator, returning true upon success.
         */
        bool getLocatorMatrix( osg::Matrixd& output, unsigned components =COMP_ALL ) const;

    public:
        //override
        /** Override Revisioned::inSync to track with the parent locator's revision. */
        virtual bool inSyncWith( int exRev ) const;

    private:
        osg::observer_ptr<const osgEarth::Map> _map;
        osg::ref_ptr<ObjectLocator> _parentLoc;
        unsigned int _componentsToInherit; // Locator::Components mask
        RotationOrder _rotOrder;
        osg::Vec3d _pos;
        osg::Vec3d _hpr;
        double _timestamp;
        bool _isEmpty;
    };


    /**
     * A transform node that tracks the position/orientation information in an ObjectLocator.
     */
    class OSGEARTHUTIL_EXPORT ObjectLocatorNode : public osg::MatrixTransform
    {
    public:
        ObjectLocatorNode();
        ObjectLocatorNode( ObjectLocator* locator );
        ObjectLocatorNode( const Map* map );
        ObjectLocatorNode( const ObjectLocatorNode& rhs, const osg::CopyOp& =osg::CopyOp::SHALLOW_COPY );
        META_Node(osgEarthUtil, ObjectLocatorNode);

    public:
        /** 
         * The locator creates the positioning matrix for the component.
         */
        void setLocator( ObjectLocator* locator );
        ObjectLocator* getLocator() { return _locator.get(); }
        const ObjectLocator* getLocator() const { return _locator.get(); }

    public:
        /** Synchronizes the transform matrix with the locator. */
        virtual void update();

        virtual void traverse(osg::NodeVisitor &nv);

    private:
        osg::ref_ptr<ObjectLocator> _locator;
        osgEarth::Revision _matrixRevision;
    };

} } // namespace osgEarth::Util

#endif // OSGEARTH_UTIL_OBJECT_LOCATOR_H

