/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2015 - ROLI Ltd.

   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3

   Details of these licenses can be found at: www.gnu.org/licenses

   JUCE 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 General Public License for more details.

   ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.

  ==============================================================================
*/

#ifndef JUCER_POSITIONPROPERTYBASE_H_INCLUDED
#define JUCER_POSITIONPROPERTYBASE_H_INCLUDED

#include "../ui/jucer_PaintRoutineEditor.h"
#include "../ui/jucer_ComponentLayoutEditor.h"

//==============================================================================
/**
    Base class for a property that edits the x, y, w, or h of a PositionedRectangle.
*/
class PositionPropertyBase  : public PropertyComponent,
                              protected ChangeListener,
                              private ButtonListener
{
public:
    enum ComponentPositionDimension
    {
        componentX          = 0,
        componentY          = 1,
        componentWidth      = 2,
        componentHeight     = 3
    };

    PositionPropertyBase (Component* comp,
                          const String& name,
                          ComponentPositionDimension dimension_,
                          const bool includeAnchorOptions_,
                          const bool allowRelativeOptions_,
                          ComponentLayout* layout_)
        : PropertyComponent (name),
          layout (layout_),
          button ("mode"),
          component (comp),
          dimension (dimension_),
          includeAnchorOptions (includeAnchorOptions_),
          allowRelativeOptions (allowRelativeOptions_)
    {
        addAndMakeVisible (button);
        button.addListener (this);
        button.setTriggeredOnMouseDown (true);
        button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);

        addAndMakeVisible (textEditor = new PositionPropLabel (*this));
    }

    String getText() const
    {
        RelativePositionedRectangle rpr (getPosition());
        PositionedRectangle& p = rpr.rect;
        String s;

        switch (dimension)
        {
        case componentX:
            if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
                s << valueToString (p.getX() * 100.0) << '%';
            else
                s << valueToString (p.getX());
            break;

        case componentY:
            if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
                s << valueToString (p.getY() * 100.0) << '%';
            else
                s << valueToString (p.getY());
            break;

        case componentWidth:
            if (p.getWidthMode() == PositionedRectangle::proportionalSize)
                s << valueToString (p.getWidth() * 100.0) << '%';
            else
                s << valueToString (p.getWidth());
            break;

        case componentHeight:
            if (p.getHeightMode() == PositionedRectangle::proportionalSize)
                s << valueToString (p.getHeight() * 100.0) << '%';
            else
                s << valueToString (p.getHeight());
            break;

        default:
            jassertfalse;
            break;
        };

        return s;
    }

    static String valueToString (const double n)
    {
        return String (roundToInt (n * 1000.0) / 1000.0);
    }

    void setText (const String& newText)
    {
        RelativePositionedRectangle rpr (getPosition());
        PositionedRectangle p (rpr.rect);
        const double value = newText.getDoubleValue();

        switch (dimension)
        {
        case componentX:
            if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
                p.setX (value / 100.0);
            else
                p.setX (value);
            break;

        case componentY:
            if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
                p.setY (value / 100.0);
            else
                p.setY (value);
            break;

        case componentWidth:
            if (p.getWidthMode() == PositionedRectangle::proportionalSize)
                p.setWidth (value / 100.0);
            else
                p.setWidth (value);
            break;

        case componentHeight:
            if (p.getHeightMode() == PositionedRectangle::proportionalSize)
                p.setHeight (value / 100.0);
            else
                p.setHeight (value);
            break;

        default:
            jassertfalse;
            break;
        };

        if (p != rpr.rect)
        {
            rpr.rect = p;
            setPosition (rpr);
        }
    }

    void changeListenerCallback (ChangeBroadcaster*)
    {
        refresh();
    }

    bool showMenu (ComponentLayout* compLayout)
    {
        RelativePositionedRectangle rpr (getPosition());
        PositionedRectangle p (rpr.rect);

        PositionedRectangle::AnchorPoint xAnchor = p.getAnchorPointX();
        PositionedRectangle::AnchorPoint yAnchor = p.getAnchorPointY();
        PositionedRectangle::PositionMode xMode = p.getPositionModeX();
        PositionedRectangle::PositionMode yMode = p.getPositionModeY();
        PositionedRectangle::SizeMode sizeW = p.getWidthMode();
        PositionedRectangle::SizeMode sizeH = p.getHeightMode();

        String relCompName ("parent");

        if (Component* const relComp = compLayout != nullptr ? compLayout->getComponentRelativePosTarget (component, (int) dimension)
                                                             : nullptr)
            relCompName = compLayout->getComponentMemberVariableName (relComp);

        jassert (relCompName.isNotEmpty());

        PopupMenu m;

        if (dimension == componentX || dimension == componentY)
        {
            const PositionedRectangle::PositionMode posMode = (dimension == componentX) ? xMode : yMode;

            m.addItem (10, ((dimension == componentX) ? "Absolute distance from left of "
                                                      : "Absolute distance from top of ") + relCompName,
                       true, posMode == PositionedRectangle::absoluteFromParentTopLeft);

            m.addItem (11, ((dimension == componentX) ? "Absolute distance from right of "
                                                      : "Absolute distance from bottom of ") + relCompName,
                       true, posMode == PositionedRectangle::absoluteFromParentBottomRight);

            m.addItem (12, "Absolute distance from centre of " + relCompName,
                       true, posMode == PositionedRectangle::absoluteFromParentCentre);

            m.addItem (13, ((dimension == componentX) ? "Percentage of width of "
                                                      : "Percentage of height of ") + relCompName,
                       true, posMode == PositionedRectangle::proportionOfParentSize);

            m.addSeparator();

            if (includeAnchorOptions)
            {
                const PositionedRectangle::AnchorPoint anchor = (dimension == componentX) ? xAnchor : yAnchor;

                m.addItem (14, (dimension == componentX) ? "Anchored at left of component"
                                                         : "Anchored at top of component",
                           true, anchor == PositionedRectangle::anchorAtLeftOrTop);

                m.addItem (15, "Anchored at centre of component", true, anchor == PositionedRectangle::anchorAtCentre);

                m.addItem (16, (dimension == componentX) ? "Anchored at right of component"
                                                         : "Anchored at bottom of component",
                           true, anchor == PositionedRectangle::anchorAtRightOrBottom);
            }
        }
        else
        {
            const PositionedRectangle::SizeMode sizeMode = (dimension == componentWidth) ? sizeW : sizeH;

            m.addItem (20, (dimension == componentWidth) ? "Absolute width"
                                                         : "Absolute height",
                       true, sizeMode == PositionedRectangle::absoluteSize);

            m.addItem (21, ((dimension == componentWidth) ? "Percentage of width of "
                                                          : "Percentage of height of ") + relCompName,
                       true, sizeMode == PositionedRectangle::proportionalSize);

            m.addItem (22, ((dimension == componentWidth) ? "Subtracted from width of "
                                                          : "Subtracted from height of ") + relCompName,
                       true, sizeMode == PositionedRectangle::parentSizeMinusAbsolute);
        }


        if (allowRelativeOptions && compLayout != nullptr)
        {
            m.addSeparator();
            m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
        }

        WeakReference<Component> ref (this);

        const int menuResult = m.showAt (&button);

        if (menuResult == 0 || ref == nullptr)
            return false;

        switch (menuResult)
        {
        case 10:
            if (dimension == componentX)
                xMode = PositionedRectangle::absoluteFromParentTopLeft;
            else
                yMode = PositionedRectangle::absoluteFromParentTopLeft;
            break;

        case 11:
            if (dimension == componentX)
                xMode = PositionedRectangle::absoluteFromParentBottomRight;
            else
                yMode = PositionedRectangle::absoluteFromParentBottomRight;
            break;

        case 12:
            if (dimension == componentX)
                xMode = PositionedRectangle::absoluteFromParentCentre;
            else
                yMode = PositionedRectangle::absoluteFromParentCentre;
            break;

        case 13:
            if (dimension == componentX)
                xMode = PositionedRectangle::proportionOfParentSize;
            else
                yMode = PositionedRectangle::proportionOfParentSize;
            break;

        case 14:
            if (dimension == componentX)
                xAnchor = PositionedRectangle::anchorAtLeftOrTop;
            else
                yAnchor = PositionedRectangle::anchorAtLeftOrTop;
            break;

        case 15:
            if (dimension == componentX)
                xAnchor = PositionedRectangle::anchorAtCentre;
            else
                yAnchor = PositionedRectangle::anchorAtCentre;
            break;

        case 16:
            if (dimension == componentX)
                xAnchor = PositionedRectangle::anchorAtRightOrBottom;
            else
                yAnchor = PositionedRectangle::anchorAtRightOrBottom;
            break;

        case 20:
            if (dimension == componentWidth)
                sizeW = PositionedRectangle::absoluteSize;
            else
                sizeH = PositionedRectangle::absoluteSize;
            break;

        case 21:
            if (dimension == componentWidth)
                sizeW = PositionedRectangle::proportionalSize;
            else
                sizeH = PositionedRectangle::proportionalSize;
            break;

        case 22:
            if (dimension == componentWidth)
                sizeW = PositionedRectangle::parentSizeMinusAbsolute;
            else
                sizeH = PositionedRectangle::parentSizeMinusAbsolute;
            break;

        default:
            if (allowRelativeOptions && compLayout != nullptr)
                compLayout->processRelativeTargetMenuResult (component, (int) dimension, menuResult);
            break;
        }

        Rectangle<int> parentArea;

        if (component->findParentComponentOfClass<ComponentLayoutEditor>() != 0)
            parentArea.setSize (component->getParentWidth(), component->getParentHeight());
        else if (PaintRoutineEditor* pre = dynamic_cast<PaintRoutineEditor*> (component->getParentComponent()))
            parentArea = pre->getComponentArea();
        else
            jassertfalse;

        int x, xw, y, yh, w, h;
        rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);

        PositionedRectangle xyRect (p);
        PositionedRectangle whRect (p);

        xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
                         Rectangle<int> (x, y, xw, yh));

        whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
                         Rectangle<int> (x, y, w, h));

        p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
                    Rectangle<int> (x, y, xw, yh));

        p.setX (xyRect.getX());
        p.setY (xyRect.getY());
        p.setWidth (whRect.getWidth());
        p.setHeight (whRect.getHeight());

        if (p != rpr.rect)
        {
            rpr.rect = p;
            setPosition (rpr);
        }

        return true;
    }

    void resized()
    {
        const Rectangle<int> r (getLookAndFeel().getPropertyComponentContentPosition (*this));

        button.changeWidthToFitText (r.getHeight());
        button.setTopRightPosition (r.getRight(), r.getY());

        textEditor->setBounds (r.getX(), r.getY(), button.getX() - r.getX(), r.getHeight());
    }

    void refresh()
    {
        textEditor->setText (getText(), dontSendNotification);
    }

    void buttonClicked (Button*)
    {
        if (showMenu (layout))
            refresh(); // (to clear the text editor if it's got focus)
    }

    void textWasEdited()
    {
        const String newText (textEditor->getText());
        if (getText() != newText)
            setText (newText);
    }

    //==============================================================================
    virtual void setPosition (const RelativePositionedRectangle& newPos) = 0;

    virtual RelativePositionedRectangle getPosition() const = 0;

protected:
    class PositionPropLabel  : public Label
    {
        PositionPropertyBase& owner;

    public:
        PositionPropLabel (PositionPropertyBase& owner_)
            : Label (String(), String()),
              owner (owner_)
        {
            setEditable (true, true, false);

            setColour (backgroundColourId, Colours::white);
            setColour (textColourId, Colours::black);
            setColour (outlineColourId, findColour (ComboBox::outlineColourId));

            setColour (TextEditor::textColourId, Colours::black);
            setColour (TextEditor::backgroundColourId, Colours::white);
            setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
        }

        TextEditor* createEditorComponent()
        {
            TextEditor* ed = Label::createEditorComponent();
            ed->setInputRestrictions (14, "0123456789.-%");

            return ed;
        }

        void textWasEdited()
        {
            owner.textWasEdited();
        }
    };

    ComponentLayout* layout;
    ScopedPointer<PositionPropLabel> textEditor;
    TextButton button;

    Component* component;
    ComponentPositionDimension dimension;
    const bool includeAnchorOptions, allowRelativeOptions;
};


#endif   // JUCER_POSITIONPROPERTYBASE_H_INCLUDED
