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

   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.

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

#ifndef __JUCE_RECTANGLE_JUCEHEADER__
#define __JUCE_RECTANGLE_JUCEHEADER__

#include "../../../text/juce_StringArray.h"
#include "juce_Point.h"
class RectangleList;


//==============================================================================
/**
    Manages a rectangle and allows geometric operations to be performed on it.

    @see RectangleList, Path, Line, Point
*/
template <typename ValueType>
class Rectangle
{
public:
    //==============================================================================
    /** Creates a rectangle of zero size.

        The default co-ordinates will be (0, 0, 0, 0).
    */
    Rectangle() throw()
      : x (0), y (0), w (0), h (0)
    {
    }

    /** Creates a copy of another rectangle. */
    Rectangle (const Rectangle& other) throw()
      : x (other.x), y (other.y),
        w (other.w), h (other.h)
    {
    }

    /** Creates a rectangle with a given position and size. */
    Rectangle (const ValueType initialX, const ValueType initialY,
               const ValueType width, const ValueType height) throw()
      : x (initialX), y (initialY),
        w (width), h (height)
    {
    }

    /** Creates a rectangle with a given size, and a position of (0, 0). */
    Rectangle (const ValueType width, const ValueType height) throw()
      : x (0), y (0), w (width), h (height)
    {
    }

    /** Creates a Rectangle from the positions of two opposite corners. */
    Rectangle (const Point<ValueType>& corner1, const Point<ValueType>& corner2) throw()
      : x (jmin (corner1.getX(), corner2.getX())),
        y (jmin (corner1.getY(), corner2.getY())),
        w (corner1.getX() - corner2.getX()),
        h (corner1.getY() - corner2.getY())
    {
        if (w < 0) w = -w;
        if (h < 0) h = -h;
    }

    Rectangle& operator= (const Rectangle& other) throw()
    {
        x = other.x; y = other.y;
        w = other.w; h = other.h;
        return *this;
    }

    /** Destructor. */
    ~Rectangle() throw() {}

    //==============================================================================
    /** Returns true if the rectangle's width and height are both zero or less */
    bool isEmpty() const throw()                                    { return w <= 0 || h <= 0; }

    /** Returns the x co-ordinate of the rectangle's left-hand-side. */
    inline ValueType getX() const throw()                           { return x; }

    /** Returns the y co-ordinate of the rectangle's top edge. */
    inline ValueType getY() const throw()                           { return y; }

    /** Returns the width of the rectangle. */
    inline ValueType getWidth() const throw()                       { return w; }

    /** Returns the height of the rectangle. */
    inline ValueType getHeight() const throw()                      { return h; }

    /** Returns the x co-ordinate of the rectangle's right-hand-side. */
    inline ValueType getRight() const throw()                       { return x + w; }

    /** Returns the y co-ordinate of the rectangle's bottom edge. */
    inline ValueType getBottom() const throw()                      { return y + h; }

    /** Returns the x co-ordinate of the rectangle's centre. */
    ValueType getCentreX() const throw()                            { return x + w / (ValueType) 2; }

    /** Returns the y co-ordinate of the rectangle's centre. */
    ValueType getCentreY() const throw()                            { return y + h / (ValueType) 2; }

    /** Returns the centre point of the rectangle. */
    const Point<ValueType> getCentre() const throw()                { return Point<ValueType> (x + w / (ValueType) 2, y + h / (ValueType) 2); }

    /** Returns the aspect ratio of the rectangle's width / height.
        If widthOverHeight is true, it returns width / height; if widthOverHeight is false,
        it returns height / width. */
    ValueType getAspectRatio (const bool widthOverHeight = true) const throw()                      { return widthOverHeight ? w / h : h / w; }

    //==============================================================================
    /** Returns the rectangle's top-left position as a Point. */
    const Point<ValueType> getPosition() const throw()                                              { return Point<ValueType> (x, y); }

    /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
    void setPosition (const Point<ValueType>& newPos) throw()                                       { x = newPos.getX(); y = newPos.getY(); }

    /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
    void setPosition (const ValueType newX, const ValueType newY) throw()                           { x = newX; y = newY; }

    /** Returns a rectangle with the same size as this one, but a new position. */
    const Rectangle withPosition (const Point<ValueType>& newPos) const throw()                     { return Rectangle (newPos.getX(), newPos.getY(), w, h); }

    /** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */
    void setSize (const ValueType newWidth, const ValueType newHeight) throw()                      { w = newWidth; h = newHeight; }

    /** Returns a rectangle with the same position as this one, but a new size. */
    const Rectangle withSize (const ValueType newWidth, const ValueType newHeight) const throw()    { return Rectangle (x, y, newWidth, newHeight); }

    /** Changes all the rectangle's co-ordinates. */
    void setBounds (const ValueType newX, const ValueType newY,
                    const ValueType newWidth, const ValueType newHeight) throw()
    {
        x = newX; y = newY; w = newWidth; h = newHeight;
    }

    /** Changes the rectangle's width */
    void setWidth (const ValueType newWidth) throw()                { w = newWidth; }

    /** Changes the rectangle's height */
    void setHeight (const ValueType newHeight) throw()              { h = newHeight; }

    /** Moves the x position, adjusting the width so that the right-hand edge remains in the same place.
        If the x is moved to be on the right of the current right-hand edge, the width will be set to zero.
    */
    void setLeft (const ValueType newLeft) throw()
    {
        w = jmax (ValueType(), x + w - newLeft);
        x = newLeft;
    }

    /** Moves the y position, adjusting the height so that the bottom edge remains in the same place.
        If the y is moved to be below the current bottom edge, the height will be set to zero.
    */
    void setTop (const ValueType newTop) throw()
    {
        h = jmax (ValueType(), y + h - newTop);
        y = newTop;
    }

    /** Adjusts the width so that the right-hand edge of the rectangle has this new value.
        If the new right is below the current X value, the X will be pushed down to match it.
        @see getRight
    */
    void setRight (const ValueType newRight) throw()
    {
        x = jmin (x, newRight);
        w = newRight - x;
    }

    /** Adjusts the height so that the bottom edge of the rectangle has this new value.
        If the new bottom is lower than the current Y value, the Y will be pushed down to match it.
        @see getBottom
    */
    void setBottom (const ValueType newBottom) throw()
    {
        y = jmin (y, newBottom);
        h = newBottom - y;
    }

    //==============================================================================
    /** Moves the rectangle's position by adding amount to its x and y co-ordinates. */
    void translate (const ValueType deltaX,
                    const ValueType deltaY) throw()
    {
        x += deltaX;
        y += deltaY;
    }

    /** Returns a rectangle which is the same as this one moved by a given amount. */
    const Rectangle translated (const ValueType deltaX,
                                const ValueType deltaY) const throw()
    {
        return Rectangle (x + deltaX, y + deltaY, w, h);
    }

    /** Returns a rectangle which is the same as this one moved by a given amount. */
    const Rectangle operator+ (const Point<ValueType>& deltaPosition) const throw()
    {
        return Rectangle (x + deltaPosition.getX(), y + deltaPosition.getY(), w, h);
    }

    /** Returns a rectangle which is the same as this one moved by a given amount. */
    const Rectangle operator- (const Point<ValueType>& deltaPosition) const throw()
    {
        return Rectangle (x - deltaPosition.getX(), y - deltaPosition.getY(), w, h);
    }

    /** Expands the rectangle by a given amount.

        Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
        @see expanded, reduce, reduced
    */
    void expand (const ValueType deltaX,
                 const ValueType deltaY) throw()
    {
        const ValueType nw = jmax (ValueType(), w + deltaX * 2);
        const ValueType nh = jmax (ValueType(), h + deltaY * 2);
        setBounds (x - deltaX, y - deltaY, nw, nh);
    }

    /** Returns a rectangle that is larger than this one by a given amount.

        Effectively, the rectangle returned is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
        @see expand, reduce, reduced
    */
    const Rectangle expanded (const ValueType deltaX,
                              const ValueType deltaY) const throw()
    {
        const ValueType nw = jmax (ValueType(), w + deltaX * 2);
        const ValueType nh = jmax (ValueType(), h + deltaY * 2);
        return Rectangle (x - deltaX, y - deltaY, nw, nh);
    }

    /** Shrinks the rectangle by a given amount.

        Effectively, its new size is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
        @see reduced, expand, expanded
    */
    void reduce (const ValueType deltaX,
                 const ValueType deltaY) throw()
    {
        expand (-deltaX, -deltaY);
    }

    /** Returns a rectangle that is smaller than this one by a given amount.

        Effectively, the rectangle returned is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
        @see reduce, expand, expanded
    */
    const Rectangle reduced (const ValueType deltaX,
                             const ValueType deltaY) const throw()
    {
        return expanded (-deltaX, -deltaY);
    }

    //==============================================================================
    /** Returns true if the two rectangles are identical. */
    bool operator== (const Rectangle& other) const throw()
    {
        return x == other.x && y == other.y
            && w == other.w && h == other.h;
    }

    /** Returns true if the two rectangles are not identical. */
    bool operator!= (const Rectangle& other) const throw()
    {
        return x != other.x || y != other.y
            || w != other.w || h != other.h;
    }

    /** Returns true if this co-ordinate is inside the rectangle. */
    bool contains (const ValueType xCoord, const ValueType yCoord) const throw()
    {
        return xCoord >= x && yCoord >= y && xCoord < x + w && yCoord < y + h;
    }

    /** Returns true if this co-ordinate is inside the rectangle. */
    bool contains (const Point<ValueType>& point) const throw()
    {
        return point.getX() >= x && point.getY() >= y && point.getX() < x + w && point.getY() < y + h;
    }

    /** Returns true if this other rectangle is completely inside this one. */
    bool contains (const Rectangle& other) const throw()
    {
        return x <= other.x && y <= other.y
            && x + w >= other.x + other.w && y + h >= other.y + other.h;
    }

    /** Returns the nearest point to the specified point that lies within this rectangle. */
    const Point<ValueType> getConstrainedPoint (const Point<ValueType>& point) const throw()
    {
        return Point<ValueType> (jlimit (x, x + w, point.getX()),
                                 jlimit (y, y + h, point.getY()));
    }

    /** Returns true if any part of another rectangle overlaps this one. */
    bool intersects (const Rectangle& other) const throw()
    {
        return x + w > other.x
            && y + h > other.y
            && x < other.x + other.w
            && y < other.y + other.h
            && w > ValueType() && h > ValueType();
    }

    /** Returns the region that is the overlap between this and another rectangle.

        If the two rectangles don't overlap, the rectangle returned will be empty.
    */
    const Rectangle getIntersection (const Rectangle& other) const throw()
    {
        const ValueType nx = jmax (x, other.x);
        const ValueType ny = jmax (y, other.y);
        const ValueType nw = jmin (x + w, other.x + other.w) - nx;
        const ValueType nh = jmin (y + h, other.y + other.h) - ny;

        if (nw >= ValueType() && nh >= ValueType())
            return Rectangle (nx, ny, nw, nh);

        return Rectangle();
    }

    /** Clips a rectangle so that it lies only within this one.

        This is a non-static version of intersectRectangles().

        Returns false if the two regions didn't overlap.
    */
    bool intersectRectangle (ValueType& otherX, ValueType& otherY, ValueType& otherW, ValueType& otherH) const throw()
    {
        const int maxX = jmax (otherX, x);
        otherW = jmin (otherX + otherW, x + w) - maxX;

        if (otherW > 0)
        {
            const int maxY = jmax (otherY, y);
            otherH = jmin (otherY + otherH, y + h) - maxY;

            if (otherH > 0)
            {
                otherX = maxX; otherY = maxY;
                return true;
            }
        }

        return false;
    }

    /** Returns the smallest rectangle that contains both this one and the one
        passed-in.
    */
    const Rectangle getUnion (const Rectangle& other) const throw()
    {
        const ValueType newX = jmin (x, other.x);
        const ValueType newY = jmin (y, other.y);

        return Rectangle (newX, newY,
                          jmax (x + w, other.x + other.w) - newX,
                          jmax (y + h, other.y + other.h) - newY);
    }

    /** If this rectangle merged with another one results in a simple rectangle, this
        will set this rectangle to the result, and return true.

        Returns false and does nothing to this rectangle if the two rectangles don't overlap,
        or if they form a complex region.
    */
    bool enlargeIfAdjacent (const Rectangle& other) throw()
    {
        if (x == other.x && getRight() == other.getRight()
             && (other.getBottom() >= y && other.y <= getBottom()))
        {
            const ValueType newY = jmin (y, other.y);
            h = jmax (getBottom(), other.getBottom()) - newY;
            y = newY;
            return true;
        }
        else if (y == other.y && getBottom() == other.getBottom()
                  && (other.getRight() >= x && other.x <= getRight()))
        {
            const ValueType newX = jmin (x, other.x);
            w = jmax (getRight(), other.getRight()) - newX;
            x = newX;
            return true;
        }

        return false;
    }

    /** If after removing another rectangle from this one the result is a simple rectangle,
        this will set this object's bounds to be the result, and return true.

        Returns false and does nothing to this rectangle if the two rectangles don't overlap,
        or if removing the other one would form a complex region.
    */
    bool reduceIfPartlyContainedIn (const Rectangle& other) throw()
    {
        int inside = 0;
        const int otherR = other.getRight();
        if (x >= other.x && x < otherR) inside = 1;
        const int otherB = other.getBottom();
        if (y >= other.y && y < otherB) inside |= 2;
        const int r = x + w;
        if (r >= other.x && r < otherR) inside |= 4;
        const int b = y + h;
        if (b >= other.y && b < otherB) inside |= 8;

        switch (inside)
        {
            case 1 + 2 + 8:     w = r - otherR; x = otherR; return true;
            case 1 + 2 + 4:     h = b - otherB; y = otherB; return true;
            case 2 + 4 + 8:     w = other.x - x; return true;
            case 1 + 4 + 8:     h = other.y - y; return true;
        }

        return false;
    }

    /** Returns the smallest rectangle that can contain the shape created by applying
        a transform to this rectangle.

        This should only be used on floating point rectangles.
    */
    const Rectangle<ValueType> transformed (const AffineTransform& transform) const throw()
    {
        float x1 = x,     y1 = y;
        float x2 = x + w, y2 = y;
        float x3 = x,     y3 = y + h;
        float x4 = x2,    y4 = y3;

        transform.transformPoint (x1, y1);
        transform.transformPoint (x2, y2);
        transform.transformPoint (x3, y3);
        transform.transformPoint (x4, y4);

        const float x = jmin (x1, x2, x3, x4);
        const float y = jmin (y1, y2, y3, y4);

        return Rectangle (x, y,
                          jmax (x1, x2, x3, x4) - x,
                          jmax (y1, y2, y3, y4) - y);
    }

    /** Returns the smallest integer-aligned rectangle that completely contains this one.
        This is only relevent for floating-point rectangles, of course.
        @see toFloat()
    */
    const Rectangle<int> getSmallestIntegerContainer() const throw()
    {
        const int x1 = (int) floorf ((float) x);
        const int y1 = (int) floorf ((float) y);
        const int x2 = (int) floorf ((float) (x + w + 0.9999f));
        const int y2 = (int) floorf ((float) (y + h + 0.9999f));

        return Rectangle<int> (x1, y1, x2 - x1, y2 - y1);
    }

    /** Casts this rectangle to a Rectangle<float>.
        Obviously this is mainly useful for rectangles that use integer types.
        @see getSmallestIntegerContainer
    */
    const Rectangle<float> toFloat() const throw()
    {
        return Rectangle<float> (static_cast<float> (x), static_cast<float> (y),
                                 static_cast<float> (w), static_cast<float> (h));
    }

    //==============================================================================
    /** Static utility to intersect two sets of rectangular co-ordinates.

        Returns false if the two regions didn't overlap.

        @see intersectRectangle
    */
    static bool intersectRectangles (ValueType& x1, ValueType& y1, ValueType& w1, ValueType& h1,
                                     const ValueType x2, const ValueType y2, const ValueType w2, const ValueType h2) throw()
    {
        const ValueType x = jmax (x1, x2);
        w1 = jmin (x1 + w1, x2 + w2) - x;

        if (w1 > 0)
        {
            const ValueType y = jmax (y1, y2);
            h1 = jmin (y1 + h1, y2 + h2) - y;

            if (h1 > 0)
            {
                x1 = x; y1 = y;
                return true;
            }
        }

        return false;
    }

    //==============================================================================
    /** Creates a string describing this rectangle.

        The string will be of the form "x y width height", e.g. "100 100 400 200".

        Coupled with the fromString() method, this is very handy for things like
        storing rectangles (particularly component positions) in XML attributes.

        @see fromString
    */
    const String toString() const
    {
        String s;
        s.preallocateStorage (16);
        s << x << ' ' << y << ' ' << w << ' ' << h;
        return s;
    }

    /** Parses a string containing a rectangle's details.

        The string should contain 4 integer tokens, in the form "x y width height". They
        can be comma or whitespace separated.

        This method is intended to go with the toString() method, to form an easy way
        of saving/loading rectangles as strings.

        @see toString
    */
    static const Rectangle fromString (const String& stringVersion)
    {
        StringArray toks;
        toks.addTokens (stringVersion.trim(), ",; \t\r\n", String::empty);

        return Rectangle (toks[0].trim().getIntValue(),
                          toks[1].trim().getIntValue(),
                          toks[2].trim().getIntValue(),
                          toks[3].trim().getIntValue());
    }

    //==============================================================================
    juce_UseDebuggingNewOperator

private:
    friend class RectangleList;
    ValueType x, y, w, h;
};


#endif   // __JUCE_RECTANGLE_JUCEHEADER__
