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

   This file is part of the JUCE library.
   Copyright (c) 2013 - Raw Material Software 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.

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

RectangleList::RectangleList() noexcept
{
}

RectangleList::RectangleList (const Rectangle<int>& rect)
{
    addWithoutMerging (rect);
}

RectangleList::RectangleList (const RectangleList& other)
    : rects (other.rects)
{
}

RectangleList& RectangleList::operator= (const RectangleList& other)
{
    rects = other.rects;
    return *this;
}

#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
RectangleList::RectangleList (RectangleList&& other) noexcept
    : rects (static_cast <Array <Rectangle<int> >&&> (other.rects))
{
}

RectangleList& RectangleList::operator= (RectangleList&& other) noexcept
{
    rects = static_cast <Array <Rectangle<int> >&&> (other.rects);
    return *this;
}
#endif

RectangleList::~RectangleList()
{
}

//==============================================================================
void RectangleList::clear()
{
    rects.clearQuick();
}

Rectangle<int> RectangleList::getRectangle (const int index) const noexcept
{
    if (isPositiveAndBelow (index, rects.size()))
        return rects.getReference (index);

    return Rectangle<int>();
}

bool RectangleList::isEmpty() const noexcept
{
    return rects.size() == 0;
}

//==============================================================================
RectangleList::Iterator::Iterator (const RectangleList& list) noexcept
    : current (nullptr),
      owner (list),
      index (list.rects.size())
{
}

RectangleList::Iterator::~Iterator()
{
}

bool RectangleList::Iterator::next() noexcept
{
    if (--index >= 0)
    {
        current = &(owner.rects.getReference (index));
        return true;
    }

    return false;
}


//==============================================================================
void RectangleList::add (const Rectangle<int>& rect)
{
    if (! rect.isEmpty())
    {
        if (rects.size() == 0)
        {
            rects.add (rect);
        }
        else
        {
            bool anyOverlaps = false;

            for (int j = rects.size(); --j >= 0;)
            {
                Rectangle<int>& ourRect = rects.getReference (j);

                if (rect.intersects (ourRect))
                {
                    if (rect.contains (ourRect))
                        rects.remove (j);
                    else if (! ourRect.reduceIfPartlyContainedIn (rect))
                        anyOverlaps = true;
                }
            }

            if (anyOverlaps && rects.size() > 0)
            {
                RectangleList r (rect);

                for (int i = rects.size(); --i >= 0;)
                {
                    const Rectangle<int>& ourRect = rects.getReference (i);

                    if (rect.intersects (ourRect))
                    {
                        r.subtract (ourRect);

                        if (r.rects.size() == 0)
                            return;
                    }
                }

                rects.addArray (r.rects);
            }
            else
            {
                rects.add (rect);
            }
        }
    }
}

void RectangleList::addWithoutMerging (const Rectangle<int>& rect)
{
    if (! rect.isEmpty())
        rects.add (rect);
}

void RectangleList::add (const int x, const int y, const int w, const int h)
{
    add (Rectangle<int> (x, y, w, h));
}

void RectangleList::add (const RectangleList& other)
{
    for (const Rectangle<int>* r = other.begin(), * const e = other.end(); r != e; ++r)
        add (*r);
}

void RectangleList::subtract (const Rectangle<int>& rect)
{
    const int originalNumRects = rects.size();

    if (originalNumRects > 0)
    {
        const int x1 = rect.pos.x;
        const int y1 = rect.pos.y;
        const int x2 = x1 + rect.w;
        const int y2 = y1 + rect.h;

        for (int i = getNumRectangles(); --i >= 0;)
        {
            Rectangle<int>& r = rects.getReference (i);

            const int rx1 = r.pos.x;
            const int ry1 = r.pos.y;
            const int rx2 = rx1 + r.w;
            const int ry2 = ry1 + r.h;

            if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
            {
                if (x1 > rx1 && x1 < rx2)
                {
                    if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
                    {
                        r.w = x1 - rx1;
                    }
                    else
                    {
                        r.pos.x = x1;
                        r.w = rx2 - x1;

                        rects.insert (++i, Rectangle<int> (rx1, ry1, x1 - rx1,  ry2 - ry1));
                        ++i;
                    }
                }
                else if (x2 > rx1 && x2 < rx2)
                {
                    r.pos.x = x2;
                    r.w = rx2 - x2;

                    if (y1 > ry1 || y2 < ry2 || x1 > rx1)
                    {
                        rects.insert (++i, Rectangle<int> (rx1, ry1, x2 - rx1,  ry2 - ry1));
                        ++i;
                    }
                }
                else if (y1 > ry1 && y1 < ry2)
                {
                    if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
                    {
                        r.h = y1 - ry1;
                    }
                    else
                    {
                        r.pos.y = y1;
                        r.h = ry2 - y1;

                        rects.insert (++i, Rectangle<int> (rx1, ry1, rx2 - rx1, y1 - ry1));
                        ++i;
                    }
                }
                else if (y2 > ry1 && y2 < ry2)
                {
                    r.pos.y = y2;
                    r.h = ry2 - y2;

                    if (x1 > rx1 || x2 < rx2 || y1 > ry1)
                    {
                        rects.insert (++i, Rectangle<int> (rx1, ry1, rx2 - rx1, y2 - ry1));
                        ++i;
                    }
                }
                else
                {
                    rects.remove (i);
                }
            }
        }
    }
}

bool RectangleList::subtract (const RectangleList& otherList)
{
    for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;)
        subtract (otherList.rects.getReference (i));

    return rects.size() > 0;
}

bool RectangleList::clipTo (const Rectangle<int>& rect)
{
    bool notEmpty = false;

    if (rect.isEmpty())
    {
        clear();
    }
    else
    {
        for (int i = rects.size(); --i >= 0;)
        {
            Rectangle<int>& r = rects.getReference (i);

            if (! rect.intersectRectangle (r.pos.x, r.pos.y, r.w, r.h))
                rects.remove (i);
            else
                notEmpty = true;
        }
    }

    return notEmpty;
}

bool RectangleList::clipTo (const RectangleList& other)
{
    if (rects.size() == 0)
        return false;

    RectangleList result;

    for (int j = 0; j < rects.size(); ++j)
    {
        const Rectangle<int>& rect = rects.getReference (j);

        for (int i = other.rects.size(); --i >= 0;)
        {
            Rectangle<int> r (other.rects.getReference (i));

            if (rect.intersectRectangle (r.pos.x, r.pos.y, r.w, r.h))
                result.rects.add (r);
        }
    }

    swapWith (result);

    return ! isEmpty();
}

bool RectangleList::getIntersectionWith (const Rectangle<int>& rect, RectangleList& destRegion) const
{
    destRegion.clear();

    if (! rect.isEmpty())
    {
        for (int i = rects.size(); --i >= 0;)
        {
            Rectangle<int> r (rects.getReference (i));

            if (rect.intersectRectangle (r.pos.x, r.pos.y, r.w, r.h))
                destRegion.rects.add (r);
        }
    }

    return destRegion.rects.size() > 0;
}

void RectangleList::swapWith (RectangleList& otherList) noexcept
{
    rects.swapWithArray (otherList.rects);
}


//==============================================================================
void RectangleList::consolidate()
{
    for (int i = 0; i < getNumRectangles() - 1; ++i)
    {
        Rectangle<int>& r = rects.getReference (i);
        const int rx1 = r.pos.x;
        const int ry1 = r.pos.y;
        const int rx2 = rx1 + r.w;
        const int ry2 = ry1 + r.h;

        for (int j = rects.size(); --j > i;)
        {
            Rectangle<int>& r2 = rects.getReference (j);
            const int jrx1 = r2.pos.x;
            const int jry1 = r2.pos.y;
            const int jrx2 = jrx1 + r2.w;
            const int jry2 = jry1 + r2.h;

            // if the vertical edges of any blocks are touching and their horizontals don't
            // line up, split them horizontally..
            if (jrx1 == rx2 || jrx2 == rx1)
            {
                if (jry1 > ry1 && jry1 < ry2)
                {
                    r.h = jry1 - ry1;
                    rects.add (Rectangle<int> (rx1, jry1, rx2 - rx1, ry2 - jry1));
                    i = -1;
                    break;
                }

                if (jry2 > ry1 && jry2 < ry2)
                {
                    r.h = jry2 - ry1;
                    rects.add (Rectangle<int> (rx1, jry2, rx2 - rx1, ry2 - jry2));
                    i = -1;
                    break;
                }
                else if (ry1 > jry1 && ry1 < jry2)
                {
                    r2.h = ry1 - jry1;
                    rects.add (Rectangle<int> (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
                    i = -1;
                    break;
                }
                else if (ry2 > jry1 && ry2 < jry2)
                {
                    r2.h = ry2 - jry1;
                    rects.add (Rectangle<int> (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
                    i = -1;
                    break;
                }
            }
        }
    }

    for (int i = 0; i < rects.size() - 1; ++i)
    {
        Rectangle<int>& r = rects.getReference (i);

        for (int j = rects.size(); --j > i;)
        {
            if (r.enlargeIfAdjacent (rects.getReference (j)))
            {
                rects.remove (j);
                i = -1;
                break;
            }
        }
    }
}

//==============================================================================
bool RectangleList::containsPoint (const int x, const int y) const noexcept
{
    for (const Rectangle<int>* r = rects.begin(), * const e = rects.end(); r != e; ++r)
        if (r->contains (x, y))
            return true;

    return false;
}

bool RectangleList::containsRectangle (const Rectangle<int>& rectangleToCheck) const
{
    if (rects.size() > 1)
    {
        RectangleList r (rectangleToCheck);

        for (int i = rects.size(); --i >= 0;)
        {
            r.subtract (rects.getReference (i));

            if (r.rects.size() == 0)
                return true;
        }
    }
    else if (rects.size() > 0)
    {
        return rects.getReference (0).contains (rectangleToCheck);
    }

    return false;
}

bool RectangleList::intersectsRectangle (const Rectangle<int>& rectangleToCheck) const noexcept
{
    for (const Rectangle<int>* r = rects.begin(), * const e = rects.end(); r != e; ++r)
        if (r->intersects (rectangleToCheck))
            return true;

    return false;
}

bool RectangleList::intersects (const RectangleList& other) const noexcept
{
    for (const Rectangle<int>* r = rects.begin(), * const e = rects.end(); r != e; ++r)
        if (other.intersectsRectangle (*r))
            return true;

    return false;
}

Rectangle<int> RectangleList::getBounds() const noexcept
{
    if (rects.size() <= 1)
    {
        if (rects.size() == 0)
            return Rectangle<int>();

        return rects.getReference (0);
    }
    else
    {
        const Rectangle<int>& r = rects.getReference (0);

        int minX = r.pos.x;
        int minY = r.pos.y;
        int maxX = minX + r.w;
        int maxY = minY + r.h;

        for (int i = rects.size(); --i > 0;)
        {
            const Rectangle<int>& r2 = rects.getReference (i);

            minX = jmin (minX, r2.pos.x);
            minY = jmin (minY, r2.pos.y);
            maxX = jmax (maxX, r2.getRight());
            maxY = jmax (maxY, r2.getBottom());
        }

        return Rectangle<int> (minX, minY, maxX - minX, maxY - minY);
    }
}

void RectangleList::offsetAll (const int dx, const int dy) noexcept
{
    for (Rectangle<int>* r = rects.begin(), * const e = rects.end(); r != e; ++r)
    {
        r->pos.x += dx;
        r->pos.y += dy;
    }
}

//==============================================================================
Path RectangleList::toPath() const
{
    Path p;

    for (int i = 0; i < rects.size(); ++i)
        p.addRectangle (rects.getReference (i));

    return p;
}
