// Copyright (C) 2015  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_DRECTANGLe_
#define DLIB_DRECTANGLe_

#include "drectangle_abstract.h"
#include "rectangle.h"

namespace dlib
{
    class drectangle;
    drectangle operator* (
        const drectangle& rect,
        const double& scale 
    );

// ----------------------------------------------------------------------------------------

    class drectangle
    {
    public:

        drectangle (
        ) : l(0), t(0), r(-1), b(-1) {}

        drectangle (
            double l_,
            double t_,
            double r_,
            double b_
        ) :
            l(l_),
            t(t_),
            r(r_),
            b(b_)
        {}

        drectangle (
            const dlib::vector<double,2>& p
        ) :
            l(p.x()),
            t(p.y()),
            r(p.x()),
            b(p.y())
        {
        }

        template <typename T, typename U>
        drectangle (
            const vector<T,2>& p1,
            const vector<U,2>& p2
        )
        {
            *this = drectangle(p1) + drectangle(p2);
        }

        drectangle (
            const rectangle& rect
        ) : l(rect.left()), 
            t(rect.top()),
            r(rect.right()),
            b(rect.bottom()) {}

        operator rectangle (
        ) const
        {
            return rectangle(std::lround(l),
                             std::lround(t),
                             std::lround(r),
                             std::lround(b));
        }

        double left()   const { return l; }
        double top()    const { return t; }
        double right()  const { return r; }
        double bottom() const { return b; }

        double& left()   { return l; }
        double& top()    { return t; }
        double& right()  { return r; }
        double& bottom() { return b; }

        const dlib::vector<double,2> tl_corner (
        ) const { return dlib::vector<double,2>(left(), top()); }

        const dlib::vector<double,2> bl_corner (
        ) const { return dlib::vector<double,2>(left(), bottom()); } 

        const dlib::vector<double,2> tr_corner (
        ) const { return dlib::vector<double,2>(right(), top()); }

        const dlib::vector<double,2> br_corner (
        ) const { return dlib::vector<double,2>(right(), bottom()); }

        double width (
        ) const 
        { 
            if (is_empty())
                return 0;
            else
                return r - l + 1; 
        }

        double height (
        ) const 
        { 
            if (is_empty())
                return 0;
            else
                return b - t + 1; 
        }

        double area (
        ) const
        {
            return width()*height();
        }

        bool is_empty (
        ) const { return (t > b || l > r); }

        drectangle operator + (
            const drectangle& rhs
        ) const
        {
            if (rhs.is_empty())
                return *this;
            else if (is_empty())
                return rhs;

            return drectangle (
                std::min(l,rhs.l),
                std::min(t,rhs.t),
                std::max(r,rhs.r),
                std::max(b,rhs.b)
                );
        }

        drectangle intersect (
            const drectangle& rhs
        ) const
        {
            return drectangle (
                std::max(l,rhs.l),
                std::max(t,rhs.t),
                std::min(r,rhs.r),
                std::min(b,rhs.b)
                );
        }

        bool contains (
            const dlib::vector<double,2>& p
        ) const
        {
            if (p.x() < l || p.x() > r || p.y() < t || p.y() > b)
                return false;
            return true;
        }

        bool contains (
            const drectangle& rect
        ) const
        {
            if (rect.is_empty())
                return true;
            if (l <= rect.left() &&
                r >= rect.right() &&
                t <= rect.top() &&
                b >= rect.bottom())
                return true;
            return false;
        }

        drectangle& operator *= (
            const double& scale
        )
        {
            *this = *this*scale;
            return *this;
        }

        drectangle& operator /= (
            const double& scale
        )
        {
            *this = *this*(1.0/scale);
            return *this;
        }

        drectangle& operator += (
            const dlib::vector<double,2>& p
        )
        {
            *this = *this + drectangle(p);
            return *this;
        }

        bool operator== (
            const drectangle& rect
        ) const
        {
            return (l == rect.l) && (t == rect.t) && (r == rect.r) && (b == rect.b);
        }

        bool operator!= (
            const drectangle& rect
        ) const
        {
            return !(*this == rect);
        }

    private:
        double l;
        double t;
        double r;
        double b;
    };

// ----------------------------------------------------------------------------------------

    inline void serialize (
        const drectangle& item, 
        std::ostream& out
    )
    {
        try
        {
            serialize(item.left(),out); 
            serialize(item.top(),out); 
            serialize(item.right(),out); 
            serialize(item.bottom(),out); 
        }
        catch (serialization_error& e)
        {
            throw serialization_error(e.info + "\n   while serializing an object of type drectangle");
        }
    }

    inline void deserialize (
        drectangle& item, 
        std::istream& in
    )
    {
        try
        {
            deserialize(item.left(),in); 
            deserialize(item.top(),in); 
            deserialize(item.right(),in); 
            deserialize(item.bottom(),in); 
        }
        catch (serialization_error& e)
        {
            throw serialization_error(e.info + "\n   while deserializing an object of type drectangle");
        }
    }

    inline std::ostream& operator<< (
        std::ostream& out, 
        const drectangle& item 
    )   
    {
        out << "[(" << item.left() << ", " << item.top() << ") (" << item.right() << ", " << item.bottom() << ")]";
        return out;
    }

    inline std::istream& operator>>(
        std::istream& in, 
        drectangle& item 
    )
    {
        // ignore any whitespace
        while (in.peek() == ' ' || in.peek() == '\t' || in.peek() == '\r' || in.peek() == '\n')
            in.get();
        // now eat the leading '[' character
        if (in.get() != '[')
        {
            in.setstate(in.rdstate() | std::ios::failbit);
            return in;
        }

        dlib::vector<double,2> p1, p2;
        in >> p1;
        in >> p2;
        item = drectangle(p1) + drectangle(p2);

        // ignore any whitespace
        while (in.peek() == ' ' || in.peek() == '\t' || in.peek() == '\r' || in.peek() == '\n')
            in.get();
        // now eat the trailing ']' character
        if (in.get() != ']')
        {
            in.setstate(in.rdstate() | std::ios::failbit);
        }
        return in;
    }

// ----------------------------------------------------------------------------------------

    inline dlib::vector<double,2> center (
        const drectangle& rect
    )
    {
        dlib::vector<double,2> temp(rect.left() + rect.right(),
                                    rect.top() + rect.bottom());

        return temp/2.0;
    }

    inline dlib::vector<double,2> dcenter (
        const drectangle& rect
    )
    {
        return center(rect);
    }

    inline drectangle operator* (
        const drectangle& rect,
        const double& scale 
    )
    {
        if (!rect.is_empty())
        {
            const double width = (rect.right()-rect.left())*scale;
            const double height = (rect.bottom()-rect.top())*scale;
            const dlib::vector<double,2> p = center(rect);
            return drectangle(p.x()-width/2, p.y()-height/2, p.x()+width/2, p.y()+height/2);
        }
        else
        {
            return rect;
        }
    }

    inline drectangle operator* (
        const double& scale,
        const drectangle& rect
    )
    {
        return rect*scale;
    }

    inline drectangle operator/ (
        const drectangle& rect,
        const double& scale
    )
    {
        return rect*(1.0/scale);
    }

    inline drectangle operator+ (
        const drectangle& r,
        const dlib::vector<double,2>& p
    )
    {
        return r + drectangle(p);
    }

    inline drectangle operator+ (
        const dlib::vector<double,2>& p,
        const drectangle& r
    )
    {
        return r + drectangle(p);
    }

    template <typename T>
    inline drectangle translate_rect (
        const drectangle& rect,
        const dlib::vector<T,2>& p
    )
    {
        drectangle result;
        result.top    () = rect.top()    + p.y();
        result.bottom () = rect.bottom() + p.y();
        result.left   () = rect.left()   + p.x();
        result.right  () = rect.right()  + p.x();
        return result;
    }

    inline drectangle intersect (
        const drectangle& a,
        const drectangle& b
    ) { return a.intersect(b); }

    inline double area (
        const drectangle& a
    ) { return a.area(); }

    inline drectangle centered_drect (
        const dlib::vector<double,2>& p,
        double width,
        double height
    )
    {
        width--;
        height--;

        return drectangle(p.x()-width/2, p.y()-height/2, p.x()+width/2, p.y()+height/2);
    }

    inline drectangle centered_drect (
        const drectangle& rect,
        double width,
        double height
    )
    {
        return centered_drect(dcenter(rect), width, height);
    }

    inline const drectangle shrink_rect (
        const drectangle& rect,
        double num 
    )
    {
        return drectangle(rect.left()+num, rect.top()+num, rect.right()-num, rect.bottom()-num);
    }

    inline const drectangle grow_rect (
        const drectangle& rect,
        double num 
    )
    {
        return shrink_rect(rect, -num);
    }

    inline const drectangle shrink_rect (
        const drectangle& rect,
        double width,
        double height
    )
    {
        return drectangle(rect.left()+width, rect.top()+height, rect.right()-width, rect.bottom()-height);
    }

    inline const drectangle grow_rect (
        const drectangle& rect,
        double width,
        double height
    )
    {
        return shrink_rect(rect, -width, -height);
    }

    inline const drectangle scale_rect (
        const drectangle& rect,
        double scale
    )
    {
        DLIB_ASSERT(scale > 0, "scale factor must be > 0");

        double l = rect.left() * scale;
        double t = rect.top() * scale;
        double r = rect.right() * scale;
        double b = rect.bottom() * scale;
        return drectangle(l, t, r, b);
    }

    inline drectangle set_rect_area (
        const drectangle& rect,
        double area
    )
    {
        DLIB_ASSERT(area >= 0, "drectangle can't have a negative area.");

        if (area == 0)
            return drectangle(dcenter(rect));

        if (rect.area() == 0)
        {
            // In this case we will make the output rectangle a square with the requested
            // area.
            double scale = std::sqrt(area);
            return centered_drect(rect, scale, scale);
        }
        else
        {
            double scale = std::sqrt(area/rect.area());
            return centered_drect(rect, rect.width()*scale, rect.height()*scale);
        }
    }

    inline drectangle set_aspect_ratio (
        const drectangle& rect,
        double ratio
    )
    {
        DLIB_ASSERT(ratio > 0,
            "\t drectangle set_aspect_ratio()"
            << "\n\t ratio: " << ratio 
            );

        const double h = std::sqrt(rect.area()/ratio);
        const double w = h*ratio;
        return centered_drect(rect, w, h);
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_DRECTANGLe_

