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

#include "rectangle_abstract.h"
#include "../algs.h"
#include <algorithm>
#include <iostream>
#include "../serialize.h"
#include "vector.h"
#include "../image_processing/generic_image.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------
    
    class rectangle
    {
        /*!
            INITIAL VALUE
                The initial value of this object is defined by its constructor.

            CONVENTION
                left() == l
                top() == t
                right() == r
                bottom() == b
        !*/

    public:

        rectangle (
            long l_,
            long t_,
            long r_,
            long b_
        ) :
            l(l_),
            t(t_),
            r(r_),
            b(b_)
        {}

        rectangle (
            unsigned long w,
            unsigned long h
        ) :
            l(0),
            t(0),
            r(static_cast<long>(w)-1),
            b(static_cast<long>(h)-1)
        {
            DLIB_ASSERT((w > 0 && h > 0) || (w == 0 && h == 0),
                "\trectangle(width,height)"
                << "\n\twidth and height must be > 0 or both == 0"
                << "\n\twidth:  " << w 
                << "\n\theight: " << h 
                << "\n\tthis: " << this
                );
        }

        rectangle (
            const point& p
        ) :
            l(p.x()),
            t(p.y()),
            r(p.x()),
            b(p.y())
        {
        }

        rectangle (
            const point& p1,
            const point& p2
        )
        {
            *this = rectangle(p1) + rectangle(p2);
        }

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

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

        long top (
        ) const { return t; }

        long& top (
        ) { return t; }

        void set_top (
            long top_
        ) { t = top_; }

        long left (
        ) const { return l; }

        long& left (
        ) { return l; }

        void set_left (
            long left_
        ) { l = left_; }

        long right (
        ) const { return r; }

        long& right (
        ) { return r; }

        void set_right (
            long right_
        ) { r = right_; }

        long bottom (
        ) const { return b; }

        long& bottom (
        ) { return b; }

        void set_bottom (
            long bottom_
        ) { b = bottom_; }

        const point tl_corner (
        ) const { return point(left(), top()); }

        const point bl_corner (
        ) const { return point(left(), bottom()); } 

        const point tr_corner (
        ) const { return point(right(), top()); }

        const point br_corner (
        ) const { return point(right(), bottom()); }
       
        unsigned long width (
        ) const 
        { 
            if (is_empty())
                return 0;
            else
                return r - l + 1; 
        }

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

        unsigned long area (
        ) const
        {
            return width()*height();
        }

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

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

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

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

        bool contains (
            const point& p
        ) const
        {
            if (p.x() < l || p.x() > r || p.y() < t || p.y() > b)
                return false;
            return true;
        }

        bool contains (
            long x,
            long y
        ) const
        {
            if (x < l || x > r || y < t || y > b)
                return false;
            return true;
        }

        bool contains (
            const rectangle& rect
        ) const
        {
            return (rect + *this == *this);
        }

        rectangle& operator+= (
            const point& p 
        )
        {
            *this = *this + rectangle(p);
            return *this;
        }

        rectangle& operator+= (
            const rectangle& rect
        )
        {
            *this = *this + rect;
            return *this;
        }

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

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

        inline bool operator< (const dlib::rectangle& b) const
        { 
            if      (left() < b.left()) return true;
            else if (left() > b.left()) return false;
            else if (top() < b.top()) return true;
            else if (top() > b.top()) return false;
            else if (right() < b.right()) return true;
            else if (right() > b.right()) return false;
            else if (bottom() < b.bottom()) return true;
            else if (bottom() > b.bottom()) return false;
            else                    return false;
        }

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

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

    inline void serialize (
        const rectangle& 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 rectangle");
        }
    }

    inline void deserialize (
        rectangle& 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 rectangle");
        }
    }

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

    inline std::istream& operator>>(
        std::istream& in, 
        rectangle& 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;
        }

        point p1, p2;
        in >> p1;
        in >> p2;
        item = rectangle(p1) + rectangle(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 const rectangle centered_rect (
        long x,
        long y,
        unsigned long width,
        unsigned long height
    )
    {
        rectangle result;
        result.set_left ( x - static_cast<long>(width) / 2 );
        result.set_top ( y - static_cast<long>(height) / 2 );
        result.set_right ( result.left() + width - 1 );
        result.set_bottom ( result.top() + height - 1 );
        return result;
    }

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

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

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

    inline unsigned long area (
        const rectangle& a
    ) { return a.area(); }

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

    inline point center (
        const dlib::rectangle& rect
    )
    {
        point temp(rect.left() + rect.right() + 1,
                   rect.top() + rect.bottom() + 1);

        if (temp.x() < 0)
            temp.x() -= 1;

        if (temp.y() < 0)
            temp.y() -= 1;

        return temp/2;
    }

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

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

        return temp/2.0;
    }

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

    inline long distance_to_rect_edge (
        const rectangle& rect,
        const point& p
    )
    {
        using std::max;
        using std::min;
        using std::abs;

        const long dist_x = min(abs(p.x()-rect.left()), abs(p.x()-rect.right()));
        const long dist_y = min(abs(p.y()-rect.top()),  abs(p.y()-rect.bottom()));

        if (rect.contains(p))
            return min(dist_x,dist_y);
        else if (rect.left() <= p.x() && p.x() <= rect.right())
            return dist_y;
        else if (rect.top() <= p.y() && p.y() <= rect.bottom())
            return dist_x;
        else
            return dist_x + dist_y;
    }

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

    inline const point nearest_point (
        const rectangle& rect,
        const point& p
    )
    {
        point temp(p);
        if (temp.x() < rect.left())
            temp.x() = rect.left();
        else if (temp.x() > rect.right())
            temp.x() = rect.right();

        if (temp.y() < rect.top())
            temp.y() = rect.top();
        else if (temp.y() > rect.bottom())
            temp.y() = rect.bottom();

        return temp;
    }

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

    inline size_t nearest_rect (
        const std::vector<rectangle>& rects,
        const point& p
    )
    {
        DLIB_ASSERT(rects.size() > 0);
        size_t idx = 0;
        double best_dist = std::numeric_limits<double>::infinity();

        for (size_t i = 0; i < rects.size(); ++i)
        {
            if (rects[i].contains(p))
            {
                return i;
            }
            else
            {
                double dist = (nearest_point(rects[i],p)-p).length();
                if (dist < best_dist)
                {
                    best_dist = dist;
                    idx = i;
                }
            }
        }
        return idx;
    }

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

    inline void clip_line_to_rectangle (
        const rectangle& box,
        point& p1,
        point& p2
    )
    {
        // Now clip the line segment so it is contained inside box.
        if (p1.x() == p2.x())
        {
            if (!box.contains(p1))
                p1.y() = box.top();
            if (!box.contains(p2))
                p2.y() = box.bottom();
        }
        else if (p1.y() == p2.y())
        {
            if (!box.contains(p1))
                p1.x() = box.left();
            if (!box.contains(p2))
                p2.x() = box.right();
        }
        else
        {
            // We use these relations to find alpha values.  These values tell us
            // how to generate points intersecting the rectangle boundaries.  We then
            // test the resulting points for ones that are inside the rectangle and output
            // those.
            //box.left()  == alpha1*(p1.x()-p2.x()) + p2.x();
            //box.right() == alpha2*(p1.x()-p2.x()) + p2.x();

            const point d = p1-p2;
            double alpha1 = (box.left()  -p2.x())/(double)d.x();
            double alpha2 = (box.right() -p2.x())/(double)d.x();
            double alpha3 = (box.top()   -p2.y())/(double)d.y();
            double alpha4 = (box.bottom()-p2.y())/(double)d.y();

            const point c1 = alpha1*d + p2;
            const point c2 = alpha2*d + p2;
            const point c3 = alpha3*d + p2;
            const point c4 = alpha4*d + p2;

            if (!box.contains(p1))
                p1 = c1;
            if (!box.contains(p2))
                p2 = c2;
            if (box.contains(c3))
            {
                if (!box.contains(p2))
                    p2 = c3;
                else if (!box.contains(p1))
                    p1 = c3;
            }
            if (box.contains(c4))
            {
                if (!box.contains(p2))
                    p2 = c4;
                else if (!box.contains(p1))
                    p1 = c4;
            }
        }

        p1 = nearest_point(box, p1);
        p2 = nearest_point(box, p2);
    }

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

    inline const rectangle centered_rect (
        const point& p,
        unsigned long width,
        unsigned long height
    )
    {
        return centered_rect(p.x(),p.y(),width,height);
    }

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

    inline std::vector<rectangle> centered_rects (
        const std::vector<point>& pts,
        unsigned long width,
        unsigned long height
    )
    {
        std::vector<rectangle> tmp;
        tmp.reserve(pts.size());
        for (auto& p : pts)
            tmp.emplace_back(centered_rect(p, width, height));
        return tmp;
    }

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

    inline const rectangle centered_rect (
        const rectangle& rect,
        unsigned long width,
        unsigned long height
    )
    {
        return centered_rect((rect.left()+rect.right())/2,  (rect.top()+rect.bottom())/2, width, height);
    }

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

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

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

    inline const rectangle grow_rect (
        const rectangle& rect,
        long num 
    )
    {
        return shrink_rect(rect, -num);
    }

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

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

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

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

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

    inline const rectangle translate_rect (
        const rectangle& rect,
        const point& p
    )
    {
        rectangle result;
        result.set_top ( rect.top() + p.y() );
        result.set_bottom ( rect.bottom() + p.y() );
        result.set_left ( rect.left() + p.x() );
        result.set_right ( rect.right() + p.x() );
        return result;
    }

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

    inline const rectangle translate_rect (
        const rectangle& rect,
        long x,
        long y
    )
    {
        rectangle result;
        result.set_top ( rect.top() + y );
        result.set_bottom ( rect.bottom() + y );
        result.set_left ( rect.left() + x );
        result.set_right ( rect.right() + x );
        return result;
    }

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

    inline const rectangle resize_rect (
        const rectangle& rect,
        unsigned long width,
        unsigned long height
    )
    {
        return rectangle(rect.left(),rect.top(), 
                         rect.left()+width-1,
                         rect.top()+height-1);
    }

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

    inline const rectangle resize_rect_width (
        const rectangle& rect,
        unsigned long width
    )
    {
        return rectangle(rect.left(),rect.top(), 
                         rect.left()+width-1,
                         rect.bottom());
    }

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

    inline const rectangle resize_rect_height (
        const rectangle& rect,
        unsigned long height 
    )
    {
        return rectangle(rect.left(),rect.top(), 
                         rect.right(),
                         rect.top()+height-1);
    }

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

    inline const rectangle move_rect (
        const rectangle& rect,
        const point& p
    )
    {
        return rectangle(p.x(), p.y(), p.x()+rect.width()-1, p.y()+rect.height()-1);
    }

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

    inline const rectangle move_rect (
        const rectangle& rect,
        long x,
        long y 
    )
    {
        return rectangle(x, y, x+rect.width()-1, y+rect.height()-1);
    }

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

    inline rectangle set_rect_area (
        const rectangle& rect,
        unsigned long area
    )
    {
        DLIB_ASSERT(area > 0);

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

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

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

        // aspect ratio is w/h

        // we need to find the rectangle that is nearest to rect in area but
        // with an aspect ratio of ratio.

        // w/h == ratio
        // w*h == rect.area()

        if (ratio >= 1)
        {
            const long h = static_cast<long>(std::sqrt(rect.area()/ratio) + 0.5);
            const long w = static_cast<long>(h*ratio + 0.5);
            return centered_rect(rect, w, h);
        }
        else
        {
            const long w = static_cast<long>(std::sqrt(rect.area()*ratio) + 0.5);
            const long h = static_cast<long>(w/ratio + 0.5);
            return centered_rect(rect, w, h);
        }
    }

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

    template <
        typename T 
        >
    inline const rectangle get_rect (
        const T& m
    )
    {
        return rectangle(0, 0, num_columns(m)-1, num_rows(m)-1);
    }

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

    inline rectangle operator+ (
        const rectangle& r,
        const point& p
    )
    {
        return r + rectangle(p);
    }

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

    inline rectangle operator+ (
        const point& p,
        const rectangle& r
    )
    {
        return r + rectangle(p);
    }

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

}

#endif // DLIB_RECTANGLe_