# Copyright (C) 2007-2015 various contributors (see AUTHORS)
#
# This file is part of Python-EFL.
#
# Python-EFL 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; either
# version 3 of the License, or (at your option) any later version.
#
# Python-EFL 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 Python-EFL.  If not, see <http://www.gnu.org/licenses/>.

from efl.utils.deprecated cimport DEPRECATED
cimport cython


cdef inline int _spans_intersect(int c1, int l1, int c2, int l2) nogil:
    return not (((c2 + l2) <= c1) or (c2 >= (c1 + l1)))


@cython.freelist(8)
cdef class Rect(object):
    """

    Type to store and manipulate rectangular coordinates.

    This class provides the description of a rectangle and means to
    access and modify its properties in an easy way.

    Usage example:

     >>> r1 = Rect(10, 20, 30, 40)
     >>> r2 = Rect((0, 0), (100, 100))
     >>> r1
     Rect(x=10, y=20, w=30, h=40)
     >>> r2
     Rect(x=0, y=0, w=100, h=100)
     >>> r1.contains(r2)
     False
     >>> r2.contains(r1)
     True
     >>> r1 in r2 # same as r2.contains(r1)
     True
     >>> r1.intercepts(r2)
     True

    .. attention:: This is not a graphical object! Do not confuse with
       :py:class:`efl.evas.Rectangle`.

    """
    def __init__(self, *args, **kargs):
        cdef Rect other
        self.x0 = 0
        self.y0 = 0
        self._w = 0
        self._h = 0
        if args:
            if len(args) == 1:
                o = args[0]
                if isinstance(o, Rect):
                    other = <Rect>o
                    self.x0 = other.x0
                    self.y0 = other.y0
                    self._w = other._w
                    self._h = other._h
                elif isinstance(o, (tuple, list)):
                    self.x0, self.y0, self._w, self._h = o
            elif len(args) == 2:
                self.x0, self.y0 = args[0]
                self._w, self._h = args[1]
            elif len(args) == 4:
                self.x0 = <int>args[0]
                self.y0 = <int>args[1]
                self._w = <int>args[2]
                self._h = <int>args[3]

        if kargs:
            if "rect" in kargs:
                other = <Rect?>kargs["rect"]
                self.x0 = other.x0
                self.y0 = other.y0
                self._w = other._w
                self._h = other._h
            elif "geometry" in kargs:
                self.x0, self.y0, self._w, self._h = kargs["geometry"]
            else:
                if "size" in kargs:
                    self._w, self._h = kargs["size"]

                if "pos" in kargs:
                    self.x0, self.y0 = kargs["pos"]

                if "w" in kargs:
                    self._w = kargs["w"]
                elif "width" in kargs:
                    self._w = kargs["width"]

                if "h" in kargs:
                    self._h = kargs["h"]
                elif "height" in kargs:
                    self._h = kargs["height"]

                if "x" in kargs:
                    self.x0 = kargs["x"]
                elif "left" in kargs:
                    self.x0 = kargs["left"]

                if "right" in kargs:
                    self.x1 = kargs["right"]
                    if "x" in kargs or "left" in kargs or "pos" in kargs:
                        self._w = self.x1 - self.x0
                    elif "w" in kargs or "width" in kargs or "size" in kargs:
                        self.x0 = self.x1 - self._w

                if "y" in kargs:
                    self.y0 = kargs["y"]
                elif "top" in kargs:
                    self.y0 = kargs["top"]

                if "bottom" in kargs:
                    self.y1 = kargs["bottom"]
                    if "y" in kargs or "top" in kargs or "pos" in kargs:
                        self._h = self.y1 - self.y0
                    elif "h" in kargs or "height" in kargs or "size" in kargs:
                        self.y0 = self.y1 - self._h

        self.x1 = self.x0 + self._w
        self.cx = self.x0 + self._w/2

        self.y1 = self.y0 + self._h
        self.cy = self.y0 + self._h/2

    def __str__(self):
        return "%s(x=%d, y=%d, w=%d, h=%d)" % \
               (self.__class__.__name__, self.x0, self.y0, self._w, self._h)

    def __repr__(self):
        return "%s(x=%d, y=%d, w=%d, h=%d)" % \
               (self.__class__.__name__, self.x0, self.y0, self._w, self._h)

    property x:
        """:type: int"""
        def __get__(self):
            return self.x0

        def __set__(self, int x):
            self.x0 = x
            self.x1 = x + self._w
            self.cx = x + self._w/2

    property left: # same as "x"
        """:type: int"""
        def __get__(self):
            return self.x0

        def __set__(self, int x):
            self.x0 = x
            self.x1 = x + self._w
            self.cx = x + self._w/2

    property right:
        """:type: int"""
        def __get__(self):
            return self.x1

        def __set__(self, int x):
            self.x0 = x - self._w
            self.x1 = x
            self.cx = self.x0 + self._w/2

    property center_x:
        """:type: int"""
        def __get__(self):
            return self.cx

        def __set__(self, int cx):
            self.x0 = cx - self._w/2
            self.x1 = self.x0 + self._w
            self.cx = cx

    property y:
        """:type: int"""
        def __get__(self):
            return self.y0

        def __set__(self, int y):
            self.y0 = y
            self.y1 = y + self._h
            self.cy = y + self._h/2

    property top: # same as "y"
        """:type: int"""
        def __get__(self):
            return self.y0

        def __set__(self, int y):
            self.y0 = y
            self.y1 = y + self._h
            self.cy = y + self._h/2

    property bottom:
        """:type: int"""
        def __get__(self):
            return self.y1

        def __set__(self, int y):
            self.y0 = y - self._h
            self.y1 = y
            self.cy = self.y0 + self._h/2

    property center_y:
        """:type: int"""
        def __get__(self):
            return self.cy

        def __set__(self, int cy):
            self.y0 = cy - self._h/2
            self.y1 = self.y0 + self._h
            self.cy = cy

    property w:
        """:type: int"""
        def __get__(self):
            return self._w

        def __set__(self, int w):
            self._w = w
            self.x1 = self.x0 + w
            self.cx = self.x0 + w/2

    property width:
        """:type: int"""
        def __get__(self):
            return self._w

        def __set__(self, int w):
            self._w = w
            self.x1 = self.x0 + w
            self.cx = self.x0 + w/2

    property h:
        """:type: int"""
        def __get__(self):
            return self._h

        def __set__(self, int h):
            self._h = h
            self.y1 = self.y0 + h
            self.cy = self.y0 + h/2

    property height:
        """:type: int"""
        def __get__(self):
            return self._h

        def __set__(self, int h):
            self._h = h
            self.y1 = self.y0 + h
            self.cy = self.y0 + h/2

    property center:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.cx, self.cy)

        def __set__(self, spec):
            cdef int cx, cy

            cx, cy = spec

            self.x0 = cx - self._w/2
            self.x1 = self.x0 + self._w
            self.cx = cx

            self.y0 = cy - self._h/2
            self.y1 = self.y0 + self._h
            self.cy = cy

    property top_left:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.x0, self.y0)

        def __set__(self, spec):
            cdef int x, y
            x, y = spec

            self.x0 = x
            self.x1 = x + self._w
            self.cx = x + self._w/2

            self.y0 = y
            self.y1 = y + self._h
            self.cy = y + self._h/2

    property top_right:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.x1, self.y0)

        def __set__(self, spec):
            cdef int x, y
            x, y = spec

            self.x0 = x - self._w
            self.x1 = x
            self.cx = self.x0 + self._w/2

            self.y0 = y
            self.y1 = y + self._h
            self.cy = y + self._h/2

    property bottom_left:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.x0, self.y1)

        def __set__(self, spec):
            cdef int x, y
            x, y = spec

            self.x0 = x
            self.x1 = x + self._w
            self.cx = x + self._w/2

            self.y0 = y - self._h
            self.y1 = y
            self.cy = self.y0 + self._h/2

    property bottom_right:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.x1, self.y1)

        def __set__(self, spec):
            cdef int x, y
            x, y = spec

            self.x0 = x - self._w
            self.x1 = x
            self.cx = self.x0 + self._w/2

            self.y0 = y - self._h
            self.y1 = y
            self.cy = self.y0 + self._h/2

    property pos:
        """:type: (int **x**, int **y**)"""
        def __get__(self):
            return (self.x0, self.y0)

        def __set__(self, spec):
            cdef int x, y
            x, y = spec

            self.x0 = x
            self.x1 = x + self._w
            self.cx = x + self._w/2

            self.y0 = y
            self.y1 = y + self._h
            self.cy = y + self._h/2

    property size:
        """:type: (int **w**, int **h**)"""
        def __get__(self):
            return (self._w, self._h)

        def __set__(self, spec):
            cdef int w, h
            w, h = spec

            self._w = w
            self.x1 = self.x0 + w
            self.cx = self.x0 + w/2

            self._h = h
            self.y1 = self.y0 + h
            self.cy = self.y0 + h/2

    property area:
        """:type: (int **w**, int **h**)"""
        def __get__(self):
            return self._w * self._h

    def normalize(self):
        """Normalize coordinates so both width and height are positive."""
        cdef int tmp
        if self._w < 0:
            tmp = self.x0
            self.x0 = self.x1
            self.x1 = tmp
            self._w = -self._w

        if self._h < 0:
            tmp = self.y0
            self.y0 = self.y1
            self.y1 = tmp
            self._h = -self._h

    def __richcmp__(a, b, int op):
        """Compares two rectangles for (in)equality"""
        cdef Rect o1, o2
        cdef int res
        if isinstance(a, Rect):
            o1 = a
        else:
            o1 = Rect(a)

        if isinstance(b, Rect):
            o2 = b
        else:
            o2 = Rect(b)

        if op == 2 or op == 3:
            res = o1.x == o2.x and \
                  o1.y == o2.y and \
                  o1.w == o2.w and \
                  o1.h == o2.h
            if op == 3:
                res = not res

            return res
        else:
            raise TypeError("unsupported comparison operation")

    def __nonzero__(self):
        """Checks whether all coordinates are non-zero."""
        return bool(self.x0 != 0 and self._w != 0 and \
                    self.y0 != 0 and self._h != 0)

    def __contains__(self, obj):
        """Checks if this rectangle contains given rectangle."""
        cdef Rect o
        if isinstance(obj, Rect):
            o = obj
        elif isinstance(obj, (tuple, list)) and len(obj) == 2:
            o = Rect(pos=obj)
        else:
            o = Rect(obj)

        return bool(self.x0 <= o.left and o.right <= self.x1 and \
                    self.y0 <= o.top and o.bottom <= self.y1)

    def contains(self, obj):
        """Checks if this rectangle contains given rectangle.

        :param obj:
        :rtype: bool

        """
        return obj in self

    def contains_point(self, x, y):
        """Checks if this rectangle contains the given point.

        :param x:
        :type x: int
        :param y:
        :type y: int
        :rtype: bool

        """
        return bool(self.x0 <= x <= self.x1 and \
                    self.y0 <= y <= self.y1)

    def intersects(self, obj):
        """Checks if this rectangle and the given rectangle intersect.

        :param obj:
        :rtype: bool

        """
        cdef Rect o
        if isinstance(obj, Rect):
            o = <Rect>obj
        elif isinstance(obj, (tuple, list)) and len(obj) == 2:
            o = Rect(pos=obj)
        else:
            o = Rect(obj)

        return _spans_intersect(self.x0, self._w, o.x0, o._w) and \
               _spans_intersect(self.y0, self._h, o.y0, o._h)

    @DEPRECATED("1.14", "Use intersects() instead")
    def intercepts(self, obj):
        return self.intersects(obj)

    def clip(self, obj):
        """Returns a new Rect that represents current cropped inside parameter.

        :param obj:
        :rtype: Rect

        """
        cdef Rect o
        cdef int left, right, top, bottom, width, height
        if isinstance(obj, Rect):
            o = obj
        else:
            o = Rect(obj)

        left = o.left
        right = o.right
        top = o.top
        bottom = o.bottom

        if left < self.x0:
            left = self.x0
        if right > self.x1:
            right = self.x1
        if top < self.y0:
            top = self.y0
        if bottom > self.y1:
            bottom = self.y1

        width = right - left
        height = bottom - top
        if width > 0 and height > 0:
            return Rect(left, top, width, height)
        else:
            return Rect(0, 0, 0, 0)

    def union(self, obj):
        """Returns a new Rect that covers both rectangles.

        :param obj:
        :rtype: Rect

        """
        cdef Rect o
        cdef int left, right, top, bottom
        if isinstance(obj, Rect):
            o = obj
        else:
            o = Rect(obj)

        left = o.left
        right = o.right
        top = o.top
        bottom = o.bottom

        if left > self.x0:
            left = self.x0
        if right < self.x1:
            right = self.x1
        if top > self.y0:
            top = self.y0
        if bottom < self.y1:
            bottom = self.y1

        return Rect(left, top, right - left, bottom - top)

    def clamp(self, obj):
        """Returns a new Rect that represents current moved inside given
        parameter.

        If given rectangle is smaller, it'll be centered.

        :param obj:
        :rtype: Rect

        """
        cdef Rect o, ret
        cdef int left, right, top, bottom, width, height

        ret = Rect(self)
        if isinstance(obj, Rect):
            o = obj
        else:
            o = Rect(obj)

        width = o.width
        height = o.height

        if self._w > width:
            ret.center_x = o.center_x
        else:
            left = o.left
            right = o.right
            if ret.x0 < left:
                ret.left = left
            elif ret.x1 > right:
                ret.right = right

        if self._h > height:
            ret.center_y = o.center_y
        else:
            top = o.top
            bottom = o.bottom
            if ret.y0 < top:
                ret.top = top
            elif ret.y1 > bottom:
                ret.bottom = bottom

        return ret

    def move_by(self, int offset_x, int offset_y):
        """Returns a new Rect that represents current moved by given offsets.

        :param offset_x:
        :type offset_x: int
        :param offset_y:
        :type offset_y: int
        :rtype: Rect

        """
        return Rect(self.x0 + offset_x, self.y0 + offset_y, self._w, self._h)

    def inflate(self, int amount_w, int amount_h):
        """Returns a new Rect that represents current inflated by given amount.

        :param amount_x:
        :type amount_x: int
        :param amount_y:
        :type amount_y: int
        :rtype: Rect

        """
        return Rect(self.x0, self.y0, self._w + amount_w, self._h + amount_h)
