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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-10 by Raw Material Software Ltd.

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

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online 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.rawmaterialsoftware.com/juce for more information.

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

#include "../../../core/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE

#include "juce_PositionedRectangle.h"


//==============================================================================
PositionedRectangle::PositionedRectangle() throw()
    : x (0.0),
      y (0.0),
      w (0.0),
      h (0.0),
      xMode (anchorAtLeftOrTop | absoluteFromParentTopLeft),
      yMode (anchorAtLeftOrTop | absoluteFromParentTopLeft),
      wMode (absoluteSize),
      hMode (absoluteSize)
{
}

PositionedRectangle::PositionedRectangle (const PositionedRectangle& other) throw()
    : x (other.x),
      y (other.y),
      w (other.w),
      h (other.h),
      xMode (other.xMode),
      yMode (other.yMode),
      wMode (other.wMode),
      hMode (other.hMode)
{
}

PositionedRectangle& PositionedRectangle::operator= (const PositionedRectangle& other) throw()
{
    x = other.x;
    y = other.y;
    w = other.w;
    h = other.h;
    xMode = other.xMode;
    yMode = other.yMode;
    wMode = other.wMode;
    hMode = other.hMode;

    return *this;
}

PositionedRectangle::~PositionedRectangle() throw()
{
}

bool PositionedRectangle::operator== (const PositionedRectangle& other) const throw()
{
    return x == other.x
        && y == other.y
        && w == other.w
        && h == other.h
        && xMode == other.xMode
        && yMode == other.yMode
        && wMode == other.wMode
        && hMode == other.hMode;
}

bool PositionedRectangle::operator!= (const PositionedRectangle& other) const throw()
{
    return ! operator== (other);
}

//==============================================================================
PositionedRectangle::PositionedRectangle (const String& stringVersion) throw()
{
    StringArray tokens;
    tokens.addTokens (stringVersion, false);

    decodePosString (tokens [0], xMode, x);
    decodePosString (tokens [1], yMode, y);
    decodeSizeString (tokens [2], wMode, w);
    decodeSizeString (tokens [3], hMode, h);
}

const String PositionedRectangle::toString() const throw()
{
    String s;
    s.preallocateStorage (12);

    addPosDescription (s, xMode, x);
    s << ' ';
    addPosDescription (s, yMode, y);
    s << ' ';
    addSizeDescription (s, wMode, w);
    s << ' ';
    addSizeDescription (s, hMode, h);

    return s;
}

//==============================================================================
const Rectangle<int> PositionedRectangle::getRectangle (const Rectangle<int>& target) const throw()
{
    jassert (! target.isEmpty());

    double x_, y_, w_, h_;
    applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth());
    applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight());

    return Rectangle<int> (roundToInt (x_), roundToInt (y_),
                           roundToInt (w_), roundToInt (h_));
}

void PositionedRectangle::getRectangleDouble (const Rectangle<int>& target,
                                              double& x_, double& y_,
                                              double& w_, double& h_) const throw()
{
    jassert (! target.isEmpty());

    applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth());
    applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight());
}

void PositionedRectangle::applyToComponent (Component& comp) const throw()
{
    comp.setBounds (getRectangle (Rectangle<int> (0, 0, comp.getParentWidth(), comp.getParentHeight())));
}

//==============================================================================
void PositionedRectangle::updateFrom (const Rectangle<int>& rectangle,
                                      const Rectangle<int>& target) throw()
{
    updatePosAndSize (x, w, rectangle.getX(), rectangle.getWidth(), xMode, wMode, target.getX(), target.getWidth());
    updatePosAndSize (y, h, rectangle.getY(), rectangle.getHeight(), yMode, hMode, target.getY(), target.getHeight());
}

void PositionedRectangle::updateFromDouble (const double newX, const double newY,
                                            const double newW, const double newH,
                                            const Rectangle<int>& target) throw()
{
    updatePosAndSize (x, w, newX, newW, xMode, wMode, target.getX(), target.getWidth());
    updatePosAndSize (y, h, newY, newH, yMode, hMode, target.getY(), target.getHeight());
}

void PositionedRectangle::updateFromComponent (const Component& comp) throw()
{
    if (comp.getParentComponent() == 0 && ! comp.isOnDesktop())
        updateFrom (comp.getBounds(), Rectangle<int>());
    else
        updateFrom (comp.getBounds(), Rectangle<int> (0, 0, comp.getParentWidth(), comp.getParentHeight()));
}

//==============================================================================
PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointX() const throw()
{
    return (AnchorPoint) (xMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre));
}

PositionedRectangle::PositionMode PositionedRectangle::getPositionModeX() const throw()
{
    return (PositionMode) (xMode & (absoluteFromParentTopLeft
                                     | absoluteFromParentBottomRight
                                     | absoluteFromParentCentre
                                     | proportionOfParentSize));
}

PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointY() const throw()
{
    return (AnchorPoint) (yMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre));
}

PositionedRectangle::PositionMode PositionedRectangle::getPositionModeY() const throw()
{
    return (PositionMode) (yMode & (absoluteFromParentTopLeft
                                     | absoluteFromParentBottomRight
                                     | absoluteFromParentCentre
                                     | proportionOfParentSize));
}

PositionedRectangle::SizeMode PositionedRectangle::getWidthMode() const throw()
{
    return (SizeMode) wMode;
}

PositionedRectangle::SizeMode PositionedRectangle::getHeightMode() const throw()
{
    return (SizeMode) hMode;
}

void PositionedRectangle::setModes (const AnchorPoint xAnchor,
                                    const PositionMode xMode_,
                                    const AnchorPoint yAnchor,
                                    const PositionMode yMode_,
                                    const SizeMode widthMode,
                                    const SizeMode heightMode,
                                    const Rectangle<int>& target) throw()
{
    if (xMode != (xAnchor | xMode_) || wMode != widthMode)
    {
        double tx, tw;
        applyPosAndSize (tx, tw, x, w, xMode, wMode, target.getX(), target.getWidth());

        xMode = (uint8) (xAnchor | xMode_);
        wMode = (uint8) widthMode;

        updatePosAndSize (x, w, tx, tw, xMode, wMode, target.getX(), target.getWidth());
    }

    if (yMode != (yAnchor | yMode_) || hMode != heightMode)
    {
        double ty, th;
        applyPosAndSize (ty, th, y, h, yMode, hMode, target.getY(), target.getHeight());

        yMode = (uint8) (yAnchor | yMode_);
        hMode = (uint8) heightMode;

        updatePosAndSize (y, h, ty, th, yMode, hMode, target.getY(), target.getHeight());
    }
}

bool PositionedRectangle::isPositionAbsolute() const throw()
{
    return xMode == absoluteFromParentTopLeft
        && yMode == absoluteFromParentTopLeft
        && wMode == absoluteSize
        && hMode == absoluteSize;
}

//==============================================================================
void PositionedRectangle::addPosDescription (String& s, const uint8 mode, const double value) const throw()
{
    if ((mode & proportionOfParentSize) != 0)
    {
        s << (roundToInt (value * 100000.0) / 1000.0) << '%';
    }
    else
    {
        s << (roundToInt (value * 100.0) / 100.0);

        if ((mode & absoluteFromParentBottomRight) != 0)
            s << 'R';
        else if ((mode & absoluteFromParentCentre) != 0)
            s << 'C';
    }

    if ((mode & anchorAtRightOrBottom) != 0)
        s << 'r';
    else if ((mode & anchorAtCentre) != 0)
        s << 'c';
}

void PositionedRectangle::addSizeDescription (String& s, const uint8 mode, const double value) const throw()
{
    if (mode == proportionalSize)
        s << (roundToInt (value * 100000.0) / 1000.0) << '%';
    else if (mode == parentSizeMinusAbsolute)
        s << (roundToInt (value * 100.0) / 100.0) << 'M';
    else
        s << (roundToInt (value * 100.0) / 100.0);
}

void PositionedRectangle::decodePosString (const String& s, uint8& mode, double& value) throw()
{
    if (s.containsChar ('r'))
        mode = anchorAtRightOrBottom;
    else if (s.containsChar ('c'))
        mode = anchorAtCentre;
    else
        mode = anchorAtLeftOrTop;

    if (s.containsChar ('%'))
    {
        mode |= proportionOfParentSize;
        value = s.removeCharacters ("%rcRC").getDoubleValue() / 100.0;
    }
    else
    {
        if (s.containsChar ('R'))
            mode |= absoluteFromParentBottomRight;
        else if (s.containsChar ('C'))
            mode |= absoluteFromParentCentre;
        else
            mode |= absoluteFromParentTopLeft;

        value = s.removeCharacters ("rcRC").getDoubleValue();
    }
}

void PositionedRectangle::decodeSizeString (const String& s, uint8& mode, double& value) throw()
{
    if (s.containsChar ('%'))
    {
        mode = proportionalSize;
        value = s.upToFirstOccurrenceOf ("%", false, false).getDoubleValue() / 100.0;
    }
    else if (s.containsChar ('M'))
    {
        mode = parentSizeMinusAbsolute;
        value = s.getDoubleValue();
    }
    else
    {
        mode = absoluteSize;
        value = s.getDoubleValue();
    }
}

void PositionedRectangle::applyPosAndSize (double& xOut, double& wOut,
                                           const double x_, const double w_,
                                           const uint8 xMode_, const uint8 wMode_,
                                           const int parentPos,
                                           const int parentSize) const throw()
{
    if (wMode_ == proportionalSize)
        wOut = roundToInt (w_ * parentSize);
    else if (wMode_ == parentSizeMinusAbsolute)
        wOut = jmax (0, parentSize - roundToInt (w_));
    else
        wOut = roundToInt (w_);

    if ((xMode_ & proportionOfParentSize) != 0)
        xOut = parentPos + x_ * parentSize;
    else if ((xMode_ & absoluteFromParentBottomRight) != 0)
        xOut = (parentPos + parentSize) - x_;
    else if ((xMode_ & absoluteFromParentCentre) != 0)
        xOut = x_ + (parentPos + parentSize / 2);
    else
        xOut = x_ + parentPos;

    if ((xMode_ & anchorAtRightOrBottom) != 0)
        xOut -= wOut;
    else if ((xMode_ & anchorAtCentre) != 0)
        xOut -= wOut / 2;
}

void PositionedRectangle::updatePosAndSize (double& xOut, double& wOut,
                                            double x_, const double w_,
                                            const uint8 xMode_, const uint8 wMode_,
                                            const int parentPos,
                                            const int parentSize) const throw()
{
    if (wMode_ == proportionalSize)
    {
        if (parentSize > 0)
            wOut = w_ / parentSize;
    }
    else if (wMode_ == parentSizeMinusAbsolute)
        wOut = parentSize - w_;
    else
        wOut = w_;

    if ((xMode_ & anchorAtRightOrBottom) != 0)
        x_ += w_;
    else if ((xMode_ & anchorAtCentre) != 0)
        x_ += w_ / 2;

    if ((xMode_ & proportionOfParentSize) != 0)
    {
        if (parentSize > 0)
            xOut = (x_ - parentPos) / parentSize;
    }
    else if ((xMode_ & absoluteFromParentBottomRight) != 0)
        xOut = (parentPos + parentSize) - x_;
    else if ((xMode_ & absoluteFromParentCentre) != 0)
        xOut = x_ - (parentPos + parentSize / 2);
    else
        xOut = x_ - parentPos;
}

END_JUCE_NAMESPACE
