# Created By: Virgil Dupras
# Created On: 2011-08-05
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html

from sys import maxsize as INF
from math import sqrt

VERY_SMALL = 0.0000001


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return "<Point {:2.2f}, {:2.2f}>".format(*self)

    def __iter__(self):
        yield self.x
        yield self.y

    def distance_to(self, other):
        return Line(self, other).length()


class Line:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

    def __repr__(self):
        return "<Line {}, {}>".format(*self)

    def __iter__(self):
        yield self.p1
        yield self.p2

    def dx(self):
        return self.p2.x - self.p1.x

    def dy(self):
        return self.p2.y - self.p1.y

    def length(self):
        return sqrt(self.dx() ** 2 + self.dy() ** 2)

    def slope(self):
        if self.dx() == 0:
            return INF if self.dy() > 0 else -INF
        else:
            return self.dy() / self.dx()

    def intersection_point(self, other):
        # with help from http://paulbourke.net/geometry/lineline2d/
        if abs(self.slope() - other.slope()) < VERY_SMALL:
            # parallel. Even if coincident, we return nothing
            return None

        A, B = self
        C, D = other

        denom = (D.y - C.y) * (B.x - A.x) - (D.x - C.x) * (B.y - A.y)
        if denom == 0:
            return None
        numera = (D.x - C.x) * (A.y - C.y) - (D.y - C.y) * (A.x - C.x)
        numerb = (B.x - A.x) * (A.y - C.y) - (B.y - A.y) * (A.x - C.x)

        mua = numera / denom
        mub = numerb / denom
        if (0 <= mua <= 1) and (0 <= mub <= 1):
            x = A.x + mua * (B.x - A.x)
            y = A.y + mua * (B.y - A.y)
            return Point(x, y)
        else:
            return None


class Rect:
    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.w
        yield self.h

    def __repr__(self):
        return "<Rect {:2.2f}, {:2.2f}, {:2.2f}, {:2.2f}>".format(*self)

    @classmethod
    def from_center(cls, center, width, height):
        x = center.x - width / 2
        y = center.y - height / 2
        return cls(x, y, width, height)

    @classmethod
    def from_corners(cls, pt1, pt2):
        x1, y1 = pt1
        x2, y2 = pt2
        return cls(min(x1, x2), min(y1, y2), abs(x1 - x2), abs(y1 - y2))

    def center(self):
        return Point(self.x + self.w / 2, self.y + self.h / 2)

    def contains_point(self, point):
        x, y = point
        (x1, y1), (x2, y2) = self.corners()
        return (x1 <= x <= x2) and (y1 <= y <= y2)

    def contains_rect(self, rect):
        pt1, pt2 = rect.corners()
        return self.contains_point(pt1) and self.contains_point(pt2)

    def corners(self):
        return Point(self.x, self.y), Point(self.x + self.w, self.y + self.h)

    def intersects(self, other):
        r1pt1, r1pt2 = self.corners()
        r2pt1, r2pt2 = other.corners()
        if r1pt1.x < r2pt1.x:
            xinter = r1pt2.x >= r2pt1.x
        else:
            xinter = r2pt2.x >= r1pt1.x
        if not xinter:
            return False
        if r1pt1.y < r2pt1.y:
            yinter = r1pt2.y >= r2pt1.y
        else:
            yinter = r2pt2.y >= r1pt1.y
        return yinter

    def lines(self):
        pt1, pt4 = self.corners()
        pt2 = Point(pt4.x, pt1.y)
        pt3 = Point(pt1.x, pt4.y)
        l1 = Line(pt1, pt2)
        l2 = Line(pt2, pt4)
        l3 = Line(pt3, pt4)
        l4 = Line(pt1, pt3)
        return l1, l2, l3, l4

    def scaled_rect(self, dx, dy):
        """Returns a rect that has the same borders at self, but grown/shrunk by dx/dy on each side.
        """
        x, y, w, h = self
        x -= dx
        y -= dy
        w += dx * 2
        h += dy * 2
        return Rect(x, y, w, h)

    def united(self, other):
        """Returns the bounding rectangle of this rectangle and `other`.
        """
        # ul=upper left lr=lower right
        ulcorner1, lrcorner1 = self.corners()
        ulcorner2, lrcorner2 = other.corners()
        corner1 = Point(min(ulcorner1.x, ulcorner2.x), min(ulcorner1.y, ulcorner2.y))
        corner2 = Point(max(lrcorner1.x, lrcorner2.x), max(lrcorner1.y, lrcorner2.y))
        return Rect.from_corners(corner1, corner2)

    # --- Properties
    @property
    def top(self):
        return self.y

    @top.setter
    def top(self, value):
        self.y = value

    @property
    def bottom(self):
        return self.y + self.h

    @bottom.setter
    def bottom(self, value):
        self.y = value - self.h

    @property
    def left(self):
        return self.x

    @left.setter
    def left(self, value):
        self.x = value

    @property
    def right(self):
        return self.x + self.w

    @right.setter
    def right(self, value):
        self.x = value - self.w

    @property
    def width(self):
        return self.w

    @width.setter
    def width(self, value):
        self.w = value

    @property
    def height(self):
        return self.h

    @height.setter
    def height(self, value):
        self.h = value
