/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2010 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * 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_CONTROLS
#define OSGEARTHUTIL_CONTROLS

#define HAVE_OSGEARTH_CHILD_SPACING 1

#include <osgEarthUtil/Common>
#include <osgEarth/Common>
#include <osg/Drawable>
#include <osg/Geode>
#include <osg/MatrixTransform>
#include <osg/MixinVector>
#include <osgGA/GUIEventHandler>
#include <osgViewer/View>
#include <osgText/Font>
#include <osgText/Text>
#include <vector>
#include <queue>
#include <qglobal.h>

#undef OSGEARTHUTIL_EXPORT
#define OSGEARTHUTIL_EXPORT

/**
 * Controls - A simple 2D UI toolkit.
 *
 * Controls are 2D interface components that automatically layout to fit their containers.
 * The support layout containers, margins and padding, alignment/justification, and docking.
 * Controls are a quick and easy way to add "HUD" components to a scene. Just create a
 * ControlSurface and add it to a View's scene. Then create and add Controls to that
 * surface.
 */
namespace osgEarth { namespace Util { namespace Controls21
{
    using namespace osgEarth;

    typedef std::vector< osg::ref_ptr<osg::Drawable> > DrawableList;

    typedef std::map< class Control*, osg::Geode* > GeodeTable;

    // holds 4-sided gutter dimensions (for margins and padding) .. no-export, header-only.
    struct Gutter
    {
        Gutter()
            : _top(0), _right(0), _bottom(0), _left(0) { }
        Gutter( float top, float right, float bottom, float left )
            : _top(top), _right(right), _bottom(bottom), _left(left) { }
        Gutter( float y, float x )
            : _top(y), _right(x), _bottom(y), _left(x) { }
        Gutter( float all )
            : _top(all), _right(all), _bottom(all), _left(all) { }
        bool operator !=( const Gutter& rhs ) const {
            return top() != rhs.top() || right() != rhs.right() || bottom() != rhs.bottom() || left() != rhs.left(); }

        float top()  const { return _top; }
        float left() const { return _left; }
        float right() const { return _right; }
        float bottom() const { return _bottom; }

        float x() const { return _left + _right; }
        float y() const { return _top + _bottom; }

        osg::Vec2f size() const { return osg::Vec2f(x(), y()); }

        osg::Vec2f offset() const { return osg::Vec2f( _left, _top ); }

    private:
        float _top, _right, _bottom, _left;
    };

    // internal state class
    struct ControlContext
    {
        ControlContext() : _viewContextID(~0) { }
        osg::View* _view;
        osg::ref_ptr<const osg::Viewport> _vp;
        unsigned int _viewContextID;
        std::queue< osg::ref_ptr<class Control> > _active;
        const osg::FrameStamp* _frameStamp;
    };

    // base class for control events
    class ControlEventHandler : public osg::Referenced
    {
    public:
        /** Click event. */
        virtual void onClick( class Control* control ) { Q_UNUSED(control); }

        /** Click event with mouse button mask (see osgGA::GUIEventAdapter::MouseButtonMask) */
        virtual void onClick( class Control* control, int mouseButtonMask ) { Q_UNUSED( mouseButtonMask ); onClick(control); }

        /** Click event with click position (negative values mean you're in the left/top padding) */
        virtual void onClick( class Control* control, const osg::Vec2f& pos, int mouseButtonMask ) { Q_UNUSED( pos); onClick(control, mouseButtonMask); }

        /** Value events */
        virtual void onValueChanged( class Control* control, bool value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, double value ) {  Q_UNUSED( control ); Q_UNUSED( value );}
        virtual void onValueChanged( class Control* control, float value ) {  Q_UNUSED( control ); Q_UNUSED( value );}
        virtual void onValueChanged( class Control* control, int value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, const osg::Vec3f& value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, const osg::Vec2f& value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, const osg::Vec3d& value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, const osg::Vec2d& value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, const std::string& value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
        virtual void onValueChanged( class Control* control, void* value ) { Q_UNUSED( control ); Q_UNUSED( value ); }
    };

    typedef std::list< osg::ref_ptr<ControlEventHandler> > ControlEventHandlerList;

    /**
     * Base class for all controls. You can actually use a Control directly and it
     * will just render as a rectangle.
     */
    class OSGEARTHUTIL_EXPORT Control : public osg::Referenced
    {
    public:
        enum Alignment
        {
            ALIGN_NONE, ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM
        };

        enum Dock
        {
            DOCK_NONE, DOCK_LEFT, DOCK_RIGHT, DOCK_TOP, DOCK_BOTTOM, DOCK_FILL
        };

    public:
        Control();

        void setX( float value );
        const osgEarth::optional<float>& x() const { return _x; }
        void clearX() { _x.unset(); dirty(); }

        void setY( float value );
        const osgEarth::optional<float>& y() const { return _y; }
        void clearY() { _y.unset(); dirty(); }

        void setPosition( float x, float y );

        void setWidth( float value );
        const osgEarth::optional<float>& width() const { return _width; }
        void clearWidth() { _width.unset(); dirty(); }

        void setHeight( float value );
        const osgEarth::optional<float>& height() const { return _height; }
        void clearHeight() { _height.unset(); dirty(); }

        void setSize( float w, float h );

        void setMargin( const Gutter& value );
        const Gutter& margin() const { return _margin; }

        // space between container and its content
        void setPadding( const Gutter& value );
        void setPadding( float globalValue );
        const Gutter& padding() const { return _padding; }

        void setVertAlign( const Alignment& value );
        const optional<Alignment>& vertAlign() const { return _valign; }

        void setHorizAlign( const Alignment& value );
        const optional<Alignment>& horizAlign() const { return _halign; }

        void setHorizFill( bool value, float minWidth =0.0f );
        bool horizFill() const { return _hfill; }

        void setVertFill( bool value, float minHeight =0.0f );
        bool vertFill() const { return _vfill; }

        void setVisible( bool value );
        bool visible() const { return _visible; }

        void setForeColor( const osg::Vec4f& value );
        void setForeColor( float r, float g, float b, float a ) { setForeColor( osg::Vec4f(r,g,b,a) ); }
        const osgEarth::optional<osg::Vec4f> foreColor() const { return _foreColor; }
        void clearForeColor() { _foreColor.unset(); dirty(); }

        void setBackColor( const osg::Vec4f& value );
        void setBackColor( float r, float g, float b, float a ) { setBackColor( osg::Vec4f(r,g,b,a) ); }
        const osgEarth::optional<osg::Vec4f>& backColor() const { return _backColor; }
        void clearBackColor() { _backColor.unset(); dirty(); }

        void setActiveColor( const osg::Vec4f& value );
        void setActiveColor( float r, float g, float b, float a ) { setActiveColor( osg::Vec4f(r,g,b,a) ); }
        const osgEarth::optional<osg::Vec4f>& activeColor() const { return _activeColor; }
        void clearActiveColor() { _activeColor.unset(); dirty(); }

        bool getParent( osg::ref_ptr<Control>& out ) const;

        void setActive( bool value );
        bool getActive() const { return _active; }

        void setAbsorbEvents( bool value ) { _absorbEvents = value; }
        bool getAbsorbEvents() const { return _absorbEvents; }

        void addEventHandler( ControlEventHandler* handler );

    public:
        
        // mark the control as dirty so that it will regenerate on the next pass.
        virtual void dirty();
        bool isDirty() const { return _dirty; }

        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void calcFill( const ControlContext& context ) { Q_UNUSED( context ); }
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw    ( const ControlContext& context, DrawableList& out_drawables );

        // actual rendering region on the control surface
        const osg::Vec2f& renderPos() const { return _renderPos; }
        const osg::Vec2f& renderSize() const { return _renderSize; }

        // does the control contain the point?
        bool intersects( float x, float y ) const;

        void setParent( class Control* c ) { _parent = c; }

    protected:
        bool _dirty;
        osg::Vec2f _renderPos; // rendering position (includes padding offset)
        osg::Vec2f _renderSize; // rendering size (includes padding)

        // adjusts renderpos for alignment.
        void align();

        friend class ControlCanvas;
        friend class Container;

        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );

        ControlEventHandlerList _eventHandlers;

    private:
        osgEarth::optional<float> _x, _y, _width, _height;
        Gutter _margin;
        Gutter _padding;
        bool _visible;
        optional<Alignment> _valign, _halign;
        optional<osg::Vec4f> _backColor, _foreColor, _activeColor;
        bool _active;
        bool _absorbEvents;
        bool _hfill, _vfill;
        osg::observer_ptr<Control> _parent;
        osg::ref_ptr<osg::Geometry> _geom;
    };

    typedef std::vector< osg::ref_ptr<Control> > ControlVector;


    /**
     * Control that contains a raster image
     */
    class OSGEARTHUTIL_EXPORT ImageControl : public Control
    {
    public:
        ImageControl( osg::Image* image =0L );

        void setImage( osg::Image* image );
        osg::Image* getImage() const { return _image.get(); }

        /** Rotates the image. */
        void setRotation( float rotation_deg );
        float getRotation() const { return osg::RadiansToDegrees(_rotation_rad); }

        /** Tells the control to fix its minimum size to account to rotation. Otherwise the
            control will auto-size its width/height based on the rotation angle. */
        void setFixSizeForRotation( bool value );
        bool getFixSizeForRotation() const { return _fixSizeForRot; }

    public: // Control
        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void draw( const ControlContext& cx, DrawableList& out );

    private:
        osg::ref_ptr<osg::Image> _image;
        float _rotation_rad;
        bool _fixSizeForRot;
    };

    /**
     * A control that provides a horizontal sliding value controller.
     */
    class OSGEARTHUTIL_EXPORT HSliderControl : public Control
    {
    public:
        HSliderControl( float min = 0.0f, float max = 100.0f, float value = 50.0f );

        void setMin( float min, bool notify =true );
        float getMin() const { return _min; }

        void setMax( float max, bool notify =true );
        float getMax() const { return _max; }

        void setValue( float value, bool notify =true );
        float getValue() const { return _value; }

    public: // Control
        //virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void draw( const ControlContext& cx, DrawableList& out );

    protected:
        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );

        void fireValueChanged();

    private:
        float _min, _max, _value;
    };

    /**
     * A check box toggle.
     */
    class OSGEARTHUTIL_EXPORT CheckBoxControl : public Control
    {
    public:
        CheckBoxControl( bool checked =false );
        CheckBoxControl( bool checked, ControlEventHandler* callback );

        void setValue( bool value );
        bool getValue() const { return _value; }

    public:
        virtual void draw( const ControlContext& cx, DrawableList& out );

    protected:
        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );

        void fireValueChanged();

    private:
        bool _value;
    };

    typedef std::vector< osg::ref_ptr<Control> > ControlList;

    /**
     * A control that renders a simple rectangular border for a container.
     * This is also the base class for all Frame objects.
     */
    class OSGEARTHUTIL_EXPORT Frame : public ImageControl
    {
    public:
        Frame();

    public: // Control
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw( const ControlContext& context, DrawableList& drawables );
    };

    /**
     * A Frame with nice rounded corners.
     */
    class OSGEARTHUTIL_EXPORT RoundedFrame : public Frame
    {
    public:
        RoundedFrame();

    public:
        virtual void draw( const ControlContext& cx, DrawableList& drawables );
    };

    /**
     * Container is a control that houses child controls. This is the base class for
     * all containers. (It is abstract so cannot be used directly)
     * Containers are control, so you can nest them in other containers.
     */
    class OSGEARTHUTIL_EXPORT Container : public Control
    {
    public:
        Container();

        // the Frame connected to this container. can be NULL for no frame.
        void setFrame( Frame* frame );
        Frame* getFrame() const { return _frame.get(); }
        
        // space between children
        void setChildSpacing( float value );
        float childSpacing() const { return _spacing; }

        // horiz alignment to set on children (that do not already have alignment explicity set)
        void setChildHorizAlign( Alignment align );
        const optional<Alignment>& childHorizAlign() const { return _childhalign; }

        // vert alignment to set on children (that do not already have alignment explicity set)
        void setChildVertAlign( Alignment align );
        const optional<Alignment>& childVertAlign() const { return _childvalign; }

        // default add function.
        virtual void addControl( Control* control, int index =-1 ) =0;

        // default multiple-add function.
        virtual void addControls( const ControlVector& controls );

        // access to the child list.
        virtual const ControlList& children() const =0;

        // clear the controls list.
        virtual void clearControls() =0;

    public:
        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void calcFill( const ControlContext& context );
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw( const ControlContext& context, DrawableList& drawables );

    protected:
        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );

        void applyChildAligns();
        
        //void setChildRenderSize( Control* child, float w, float h ) { child->_renderSize.set( w, h ); }
        float& renderWidth(Control* child) { return child->_renderSize.x(); }
        float& renderHeight(Control* child) { return child->_renderSize.y(); }

    private:
        osg::ref_ptr<Frame> _frame;
        float _spacing;
        optional<Alignment> _childhalign;
        optional<Alignment> _childvalign;
        
        ControlList& mutable_children() { return const_cast<ControlList&>(children()); }
    };

    /**
     * Container that stacks controls vertically.
     */
    class OSGEARTHUTIL_EXPORT VBox : public Container
    {
    public:
        VBox();

    public: // Container
        virtual void addControl( Control* control, int index =-1 );
        virtual const ControlList& children() const { return _controls; }
        virtual void clearControls();

    public: // Control        
        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void calcFill( const ControlContext& context );
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw( const ControlContext& context, DrawableList& drawables );

    private:
        ControlList _controls;
    };

    /**
     * Container that stacks controls horizontally.
     */
    class OSGEARTHUTIL_EXPORT HBox : public Container
    {
    public:
        HBox();

    public: // Container
        virtual void addControl( Control* control, int index =-1 );
        virtual const ControlList& children() const { return _controls; }
        virtual void clearControls();

    public: // Control        
        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void calcFill( const ControlContext& context );
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw( const ControlContext& context, DrawableList& drawables );

    private:
        ControlList _controls;
    };

    /**
     * Container that organizes its children in a grid.
     */
    class OSGEARTHUTIL_EXPORT Grid : public Container
    {
    public:
        Grid();

        void setControl( int col, int row, Control* control );

        unsigned getNumRows() const { return _rows.size(); }
        unsigned getNumColumns() const { return _colWidths.size(); }

    public: // Container
        virtual void addControl( Control* control, int index =-1 );
        virtual const ControlList& children() const { return _children; }
        virtual void clearControls();

        // adds the controls as a row at the bottom of the grid.
        virtual void addControls( const ControlVector& controls );

    public: // Control        
        virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
        virtual void calcFill( const ControlContext& context );
        virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
        virtual void draw( const ControlContext& context, DrawableList& drawables );

    private:
        typedef std::vector< osg::ref_ptr<Control> > Row;
        typedef std::vector< Row > RowVector;
        RowVector _rows;
        ControlList _children;
        
        osg::ref_ptr<Control>& cell(int col, int row);
        void expandToInclude(int cols, int rows);
        
        std::vector<float> _rowHeights, _colWidths;
    };

    class OSGEARTHUTIL_EXPORT RefNodeVector :
        public osg::Referenced,
        public osg::MixinVector<osg::Node*> { };

    /**
     * A control wrapped in a node that you can place anywhere in the scene
     * graph. Its scene location will control its 2D screen position, and it
     * can participate in conflict resolution.
     */
    class OSGEARTHUTIL_EXPORT ControlNode : public osg::Node
    {
    public:
        /** Constructs a new control node with an embedded control. */
        ControlNode( Control* control, float priority =0.0f );

        /** The control encaspulated in this node */
        Control* getControl() const { return _control.get(); }

        /** The draw priority of this control */
        float getPriority() const { return _priority; }

        /** The point (in screen-space, relative to the lower-left of the control) that should anchor to the scene */
        optional<osg::Vec2f>& anchorPoint() { return _anchor; }
        const optional<osg::Vec2f>& anchorPoint() const { return _anchor; }


    public: // osg::Node overrides

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

        virtual osg::BoundingSphere computeBound() const;

    protected:

        struct PerViewData
        {
            PerViewData();
            bool                              _obscured;
            float                             _visibleTime;
            osg::Vec3f                        _screenPos;
            unsigned                          _visitFrame;
            osg::ref_ptr<osg::Uniform>        _uniform;
            osg::observer_ptr<osg::Camera>    _canvas;
        };
        typedef std::map<osg::View*,PerViewData> PerViewDataMap;

        PerViewDataMap         _perViewData;
        osg::ref_ptr<Control>  _control;
        float                  _priority;
        optional<osg::Vec2f>   _anchor;

        PerViewData& getData( osg::View* view ) { return _perViewData[view]; }

        friend class ControlNodeBin;
    };

    /**
     * Internal class that renders ControlNode objects found in the scene graph.
     * There is no need to instantiate or access this object directly.
     */
    class OSGEARTHUTIL_EXPORT ControlNodeBin : public osg::Group
    {
    public:
        ControlNodeBin();

        /** Registers a control node with this bin. */
        void addNode( ControlNode* node );

        /** Whether to fade-in controls when they appear in view (default=true) */
        void setFading( bool value );

    private:
        typedef std::pair<float, osg::ref_ptr<ControlNode> > ControlNodePair;
        typedef std::multimap<float, osg::ref_ptr<ControlNode> > ControlNodeCollection;
        ControlNodeCollection _controlNodes;

        typedef std::pair<Control*, ControlNodeCollection::iterator> ControlIndexPair;
        typedef std::map<Control*, ControlNodeCollection::iterator> ControlIndex;
        ControlIndex _index;

        typedef std::map<ControlNode*, osg::MatrixTransform*> RenderNodeTable;
        typedef std::pair<ControlNode*, osg::MatrixTransform*> RenderNodePair;
        RenderNodeTable _renderNodes;

        osg::ref_ptr<osg::Group>      _group;
        std::vector<osg::BoundingBox> _taken;
        bool                          _sortingEnabled;
        bool                          _sortByDistance;
        bool                          _fading;

        friend class ControlCanvas;
        friend class ControlNode;

        void draw( const ControlContext& context, bool newContext, int bin );
        osg::Group* getControlGroup() const { return _group.get(); }
    };

    /**
     * Associates controls with an OSG View.
     */
    class OSGEARTHUTIL_EXPORT ControlCanvas : public osg::Camera
    {
    public:
        /** Accesses the control canvas attached the the specified view. */
        static ControlCanvas* get( osg::View* view, bool installCanvasInSceneData =false );

    public:
        /** adds a top-level control to this surface. */
        void addControl( Control* control );

        /** removes a top-level control. */
        void removeControl( Control* control );

        /** gets the top-most control that intersects the specified position. */
        Control* getControlAtMouse( float x, float y ) const;

        /** Toggles whether ControlNodes are allowed to overlap. */
        void setAllowControlNodeOverlap( bool value );

    public:
        // internal- no need to call directly
        void update( const osg::FrameStamp* frameStamp );

        // internal - no need to call directly
        void setControlContext( const ControlContext& );

    public:
        virtual void traverse( osg::NodeVisitor& nv ); // override

        virtual ~ControlCanvas();

        /**
         * Constructs a new canvas and attaches it to a view. Call
         * getOrCreateControlCanvas(view) to create one.
         */
        ControlCanvas( osgViewer::View* view );

    protected:

        ControlList     _controls;
        GeodeTable      _geodeTable;
        ControlContext  _context;
        bool            _contextDirty;

        osg::ref_ptr<ControlNodeBin> _controlNodeBin;

    private:
        friend struct ControlCanvasEventHandler;
        friend class ControlNode;

        ControlCanvas( osgViewer::View* view, bool registerCanvas );
        void init( osgViewer::View* view, bool registerCanvas );

        bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );

        // a static registry of canvases in the scene graph.
        typedef std::map<osg::View*, ControlCanvas*> ViewCanvasMap;
        static ViewCanvasMap      _viewCanvasMap;        
        static OpenThreads::Mutex _viewCanvasMapMutex;

        /** Accesses the priority rendering bin for this canvas. */
        ControlNodeBin* getControlNodeBin() { return _controlNodeBin.get(); }
    };


} } } // namespace osgEarth::Util::Controls

#endif // OSGEARTHUTIL_CONTROLS
