/**
 * @file llrect.h
 * @brief A rectangle in GL coordinates, with bottom,left = 0,0
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library 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;
 * version 2.1 of the License only.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */


#ifndef LL_LLRECT_H
#define LL_LLRECT_H

#include <iostream>
#include "llmath.h"
#include "llsd.h"

// Top > Bottom due to GL coords
template <class Type> class LLRectBase
{
public:
    typedef Type tCoordType;
    Type        mLeft;
    Type        mTop;
    Type        mRight;
    Type        mBottom;

    // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect
    Type        getWidth()  const { return mRight - mLeft; }
    Type        getHeight() const { return mTop - mBottom; }
    Type        getCenterX() const { return (mLeft + mRight) / 2; }
    Type        getCenterY() const { return (mTop + mBottom) / 2; }

    LLRectBase():   mLeft(0), mTop(0), mRight(0), mBottom(0)
    {}

    LLRectBase(const LLRectBase &r):
    mLeft(r.mLeft), mTop(r.mTop), mRight(r.mRight), mBottom(r.mBottom)
    {}

    LLRectBase(Type left, Type top, Type right, Type bottom):
    mLeft(left), mTop(top), mRight(right), mBottom(bottom)
    {}

    explicit LLRectBase(const LLSD& sd)
    {
        setValue(sd);
    }

    void setValue(const LLSD& sd)
    {
        mLeft = (Type)sd[0].asInteger();
        mTop = (Type)sd[1].asInteger();
        mRight = (Type)sd[2].asInteger();
        mBottom = (Type)sd[3].asInteger();
    }

    LLSD getValue() const
    {
        LLSD ret;
        ret[0] = mLeft;
        ret[1] = mTop;
        ret[2] = mRight;
        ret[3] = mBottom;
        return ret;
    }

    // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect
    BOOL        pointInRect(const Type x, const Type y) const
    {
        return  mLeft <= x && x < mRight &&
                mBottom <= y && y < mTop;
    }

    //// Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect
    BOOL        localPointInRect(const Type x, const Type y) const
    {
        return  0 <= x && x < getWidth() &&
                0 <= y && y < getHeight();
    }

    void        clampPointToRect(Type& x, Type& y)
    {
        x = llclamp(x, mLeft, mRight);
        y = llclamp(y, mBottom, mTop);
    }

    void        clipPointToRect(const Type start_x, const Type start_y, Type& end_x, Type& end_y)
    {
        if (!pointInRect(start_x, start_y))
        {
            return;
        }
        Type clip_x = 0;
        Type clip_y = 0;
        Type delta_x = end_x - start_x;
        Type delta_y = end_y - start_y;
        if (end_x > mRight) clip_x = end_x - mRight;
        if (end_x < mLeft) clip_x = end_x - mLeft;
        if (end_y > mTop) clip_y = end_y - mTop;
        if (end_y < mBottom) clip_y = end_y - mBottom;
        // clip_? and delta_? should have same sign, since starting point is in rect
        // so ratios will be positive
        F32 ratio_x = 0;
        F32 ratio_y = 0;
        if (delta_x != 0) ratio_x = ((F32)clip_x / (F32)delta_x);
        if (delta_y != 0) ratio_y = ((F32)clip_y / (F32)delta_y);
        if (ratio_x > ratio_y)
        {
            // clip along x direction
            end_x -= (Type)(clip_x);
            end_y -= (Type)(delta_y * ratio_x);
        }
        else
        {
            // clip along y direction
            end_x -= (Type)(delta_x * ratio_y);
            end_y -= (Type)clip_y;
        }
    }

    // Note: Does NOT follow GL_QUAD conventions: the top and right edges ARE considered part of the rect
    // returns TRUE if any part of rect is is inside this LLRect
    BOOL        overlaps(const LLRectBase& rect) const
    {
        return !(mLeft > rect.mRight
            || mRight < rect.mLeft
            || mBottom > rect.mTop
            || mTop < rect.mBottom);
    }

    BOOL        contains(const LLRectBase& rect) const
    {
        return mLeft <= rect.mLeft
            && mRight >= rect.mRight
            && mBottom <= rect.mBottom
            && mTop >= rect.mTop;
    }

    LLRectBase& set(Type left, Type top, Type right, Type bottom)
    {
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        return *this;
    }

    // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect
    LLRectBase& setOriginAndSize( Type left, Type bottom, Type width, Type height)
    {
        mLeft = left;
        mTop = bottom + height;
        mRight = left + width;
        mBottom = bottom;
        return *this;
    }

    // Note: follows GL_QUAD conventions: the top and right edges are not considered part of the rect
    LLRectBase& setLeftTopAndSize( Type left, Type top, Type width, Type height)
    {
        mLeft = left;
        mTop = top;
        mRight = left + width;
        mBottom = top - height;
        return *this;
    }

    LLRectBase& setCenterAndSize(Type x, Type y, Type width, Type height)
    {
        // width and height could be odd, so favor top, right with extra pixel
        mLeft = x - width/2;
        mBottom = y - height/2;
        mTop = mBottom + height;
        mRight = mLeft + width;
        return *this;
    }


    LLRectBase& translate(Type horiz, Type vertical)
    {
        mLeft += horiz;
        mRight += horiz;
        mTop += vertical;
        mBottom += vertical;
        return *this;
    }

    LLRectBase& stretch( Type dx, Type dy)
    {
        mLeft -= dx;
        mRight += dx;
        mTop += dy;
        mBottom -= dy;
        return makeValid();
    }

    LLRectBase& stretch( Type delta )
    {
        stretch(delta, delta);
        return *this;
    }

    LLRectBase& makeValid()
    {
        mLeft = llmin(mLeft, mRight);
        mBottom = llmin(mBottom, mTop);
        return *this;
    }

    bool isValid() const
    {
        return mLeft <= mRight && mBottom <= mTop;
    }

    bool isEmpty() const
    {
        return mLeft == mRight || mBottom == mTop;
    }

    bool notEmpty() const
    {
        return !isEmpty();
    }

    void unionWith(const LLRectBase &other)
    {
        mLeft = llmin(mLeft, other.mLeft);
        mRight = llmax(mRight, other.mRight);
        mBottom = llmin(mBottom, other.mBottom);
        mTop = llmax(mTop, other.mTop);
    }

    void intersectWith(const LLRectBase &other)
    {
        mLeft = llmax(mLeft, other.mLeft);
        mRight = llmin(mRight, other.mRight);
        mBottom = llmax(mBottom, other.mBottom);
        mTop = llmin(mTop, other.mTop);
        if (mLeft > mRight)
        {
            mLeft = mRight;
        }
        if (mBottom > mTop)
        {
            mBottom = mTop;
        }
    }

    friend std::ostream &operator<<(std::ostream &s, const LLRectBase &rect)
    {
        s << "{ L " << rect.mLeft << " B " << rect.mBottom
            << " W " << rect.getWidth() << " H " << rect.getHeight() << " }";
        return s;
    }

    bool operator==(const LLRectBase &b) const
    {
        return ((mLeft == b.mLeft) &&
                (mTop == b.mTop) &&
                (mRight == b.mRight) &&
                (mBottom == b.mBottom));
    }

    bool operator!=(const LLRectBase &b) const
    {
        return ((mLeft != b.mLeft) ||
                (mTop != b.mTop) ||
                (mRight != b.mRight) ||
                (mBottom != b.mBottom));
    }

    static LLRectBase<Type> null;
};

template <class Type> LLRectBase<Type> LLRectBase<Type>::null(0,0,0,0);

typedef LLRectBase<S32> LLRect;
typedef LLRectBase<F32> LLRectf;

#endif