from sympy.core.basic import Basic, S, C, sympify
from sympy.simplify import simplify
from sympy.geometry.exceptions import GeometryError
from entity import GeometryEntity
from point import Point
from ellipse import Circle
from line import Line, Segment


class Polygon(GeometryEntity):
    """
    A simple polygon in space. Can be constructed from a sequence or list
    of points.

    Notes:
    ======
        - Polygons are treated as closed paths rather than 2D areas so
          some calculations can be be negative or positive (e.g., area)
          based on the orientation of the points.
    """

    def __new__(cls, *args, **kwargs):
        vertices = GeometryEntity.extract_entities(args, remove_duplicates=False)
        if len(vertices) < 3:
            raise GeometryError("Polygon.__new__ requires at least three points")

        for p in vertices:
            if not isinstance(p, Point):
                raise GeometryError("Polygon.__new__ requires points")

        if len(vertices) == 3:
            return Triangle(*vertices, **kwargs)
        return GeometryEntity.__new__(cls, *vertices, **kwargs)

    @property
    def area(self):
        """
        Returns the area of the polygon.

        Notes:
        ======
            - The area calculation can be positive or negative based on
              the orientation of the points.
        """
        area = 0
        for ind in xrange(-1, len(self.vertices)-1):
            pi = self.vertices[ind]
            pii = self.vertices[ind+1]
            area += pi[0]*pii[1]-pii[0]*pi[1]
        return simplify(area) / 2

    @property
    def angles(self):
        """
        Returns a dictionary of {point: angle} entries containing the
        measure of all the internal angles of this polygon formed at
        each vertex.

        Examples:
        ======
            >>> from sympy.geometry import *
            >>> p1,p2,p3,p4 = map(Point, [(0,0), (1,0), (5,1), (0,1)])
            >>> poly = Polygon(p1, p2, p3, p4)
            >>> poly.angles[p1]
            pi/2
            >>> poly.angles[p2]
            acos(-4*17**(1/2)/17)
        """
        def tarea(a, b, c):
            return (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])

        def isright(a, b, c):
            return bool(tarea(a, b, c) <= 0)

        # Determine orientation of points
        cw = isright(self.vertices[-1], self.vertices[0], self.vertices[1])


        ret = {}
        for i in xrange(0, len(self.vertices)):
            a,b,c = self.vertices[i-2], self.vertices[i-1], self.vertices[i]
            ang = Line.angle_between(Line(b, a), Line(b, c))
            if cw ^ isright(a, b, c):
                ret[b] = 2*S.Pi - ang
            else:
                ret[b] = ang
        return ret

    @property
    def perimeter(self):
        """Returns the perimeter of the polygon."""
        p = 0
        for ind in xrange(-1, len(self.vertices)-1):
            p += Point.distance(self.vertices[ind], self.vertices[ind+1])
        return simplify(p)

    @property
    def vertices(self):
        """The vertices that define the polygon."""
        return self[:]

    @property
    def centroid(self):
        """The centroid of the polygon."""
        A = 1 / (6*self.area)
        cx,cy = 0,0
        for ind in xrange(-1, len(self.vertices)-1):
            pi = self.vertices[ind]
            pii = self.vertices[ind+1]
            v = pi[0]*pii[1]-pii[0]*pi[1]
            cx += v*(pi[0] + pii[0])
            cy += v*(pi[1] + pii[1])
        return Point(simplify(A*cx), simplify(A*cy))

    @property
    def sides(self):
        """A list of the segments that form the sides fo the polygon."""
        res = []
        for ind in xrange(0, len(self.vertices)-1):
            res.append( Segment(self.vertices[ind], self.vertices[ind+1]) )
        res.append( Segment(self.vertices[-1], self.vertices[0]) )
        return res

    def is_convex(self):
        """Returns True if this polygon is convex, False otherwise."""
        def tarea(a, b, c):
            return (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])

        def isright(a, b, c):
            return bool(tarea(a, b, c) <= 0)

        # Determine orientation of points
        cw = isright(self.vertices[-2], self.vertices[-1], self.vertices[0])



        for i in xrange(0, len(self.vertices)):
            if cw ^ isright(self.vertices[i-2], self.vertices[i-1], self.vertices[i]):
                return False

        return True

    def intersection(self, o):
        res = []
        for side in self.sides:
            inter = GeometryEntity.do_intersection(side, o)
            if inter is not None:
                res.extend(inter)
        return res

    def __eq__(self, o):
        if not isinstance(o, Polygon):
            return False

        # Find indices of points that are the same as the first point
        # in the other polygon
        n1,n2 = len(self.vertices), len(o.vertices)
        start_indices = []
        for ind in xrange(0, n1):
            if self.vertices[ind] == o.vertices[0]:
                start_indices.append(ind)

        if len(start_indices) == 0:
            return False

        # Check vertices clockwise and counterclockwise for equality
        imax = max(n1, n2)
        for start_ind in start_indices:
            i = start_ind

            # Check to see what orientation we should check
            dir = 0
            if self.vertices[(i + 1) % n1] == o.vertices[1]:
                dir = 1
            elif self.vertices[(i - 1) % n1] == o.vertices[1]:
                dir = -1

            # If either point to the left or right if the first point
            # is value (i.e., dir is nonzero) then check in that direction
            if dir != 0:
                areEqual = True
                for ind in xrange(2, imax):
                    if self.vertices[(i + dir*ind) % n1] != o.vertices[ind % n2]:
                        areEqual = False
                        break
                if areEqual: return True

        return False

    def __contains__(self, o):
        if isinstance(o, Polygon):
            return self == o
        elif isinstance(o, Segment):
            return o in self.sides
        elif isinstance(o, Point):
            if o in self.vertices:
                return True
            for side in self.sides:
                if o in side:
                    return True
            return False
        else:
            return False


class RegularPolygon(Polygon):
    """
    A polygon which is regular. Such a polygon has all internal angles
    equal and all sides the same length.

    Usage:
    ======
        The polygon is constructed from a center point, a radius value,
        and the number of sides. The radius value is the distance from
        the center to a vertex.

    Example:
    ========
        >>> RegularPolygon(Point(0, 0), 5, 5)
        RegularPolygon(Point(0, 0), 5, 5)
    """

    def __new__(self, c, r, n, **kwargs):
        r = sympify(r)
        if not isinstance(c, Point):
            raise GeometryError("RegularPolygon.__new__ requires c to be a Point instance")
        if not isinstance(r, Basic):
            raise GeometryError("RegularPolygon.__new__ requires r to be a number or Basic instance")
        if n < 3:
            raise GeometryError("RegularPolygon.__new__ requires n >= 3")

        obj = GeometryEntity.__new__(self, c, r, n, **kwargs)
        return obj

    @property
    def vertices(self):
        points = []
        c, r, n = self[:]
        v = 2*S.Pi/n
        for k in xrange(0, n):
            points.append( Point(c[0] + r*C.cos(k*v), c[1] + r*C.sin(k*v)) )
        return points

    @property
    def center(self):
        """
        Returns the center of the regular polygon (i.e., the center of the
        circumscribing circle).
        """
        return self.__getitem__(0)

    @property
    def radius(self):
        """
        Returns the radius of the regular polygon (i.e., the radius of the
        circumscribing circle).
        """
        return self.__getitem__(1)

    @property
    def apothem(self):
        """
        Returns the apothem/inradius of the regular polygon (i.e., the
        radius of the inscribed circle).
        """
        n = self.__getitem__(2)
        return self.radius * C.cos(S.Pi/n)

    @property
    def interior_angle(self):
        """Returns the measure of the interior angles."""
        n = self.__getitem__(2)
        return (n-2)*S.Pi/n

    @property
    def exterior_angle(self):
        """Returns the measure of the exterior angles."""
        n = self.__getitem__(2)
        return 2*S.Pi/n

    @property
    def circumcircle(self):
        """Returns a Circle instance describing the circumcircle."""
        return Circle(self.center, self.radius)

    @property
    def incircle(self):
        """Returns a Circle instance describing the inscribed circle."""
        return Circle(self.center, self.apothem)

    @property
    def angles(self):
        ret = {}
        ang = self.interior_angle
        for v in self.vertices:
            ret[v] = ang
        return ret

class Triangle(Polygon):
    """Any 3-sided polygon."""
    def __new__(cls, *args, **kwargs):
        vertices = GeometryEntity.extract_entities(args, remove_duplicates=False)
        if len(vertices) != 3:
            raise GeometryError("Triangle.__new__ requires three points")

        for p in vertices:
            if not isinstance(p, Point):
                raise GeometryError("Triangle.__new__ requires three points")

        return GeometryEntity.__new__(cls, *vertices, **kwargs)

    @property
    def vertices(self):
        return self[:]

    def is_similar(t1, t2):
        if not isinstance(t2, Polygon) or len(t2) != 3:
            return False

        s1_1, s1_2, s1_3 = [side.length for side in t1.sides]
        s2 = [side.length for side in t2.sides]
        def _are_similar(u1, u2, u3, v1, v2, v3):
            e1 = simplify(u1/v1)
            e2 = simplify(u2/v2)
            e3 = simplify(u3/v3)
            return bool(e1 == e2) and bool(e2 == e3)

        # There's only 6 permutations, so write them out
        return _are_similar(s1_1, s1_2, s1_3, *s2) or \
               _are_similar(s1_1, s1_3, s1_2, *s2) or \
               _are_similar(s1_2, s1_1, s1_3, *s2) or \
               _are_similar(s1_2, s1_3, s1_1, *s2) or \
               _are_similar(s1_3, s1_1, s1_2, *s2) or \
               _are_similar(s1_3, s1_2, s1_1, *s2)

    def is_equilateral(self):
        """Returns True if the triangle is equilateral, False otherwise."""
        s = self.sides
        return bool(s[0].length == s[1].length) and bool(s[1].length == s[2].length)

    def is_right(self):
        """Returns True if the triangle is right-angled, False otherwise."""
        s = self.sides
        return Segment.is_perpendicular(s[0], s[1]) or \
               Segment.is_perpendicular(s[1], s[2]) or \
               Segment.is_perpendicular(s[0], s[2])

    @property
    def altitudes(self):
        """
        The altitudes of the triangle in a dictionary where the key
        is the vertex and the value is the altitude at that point.

        Example:
        ========
            >>> p1,p2,p3 = Point(0,0), Point(1,0), Point(0,1)
            >>> t = Triangle(p1, p2, p3)
            >>> t.altitudes[p1]
            Segment(Point(0, 0), Point(1/2, 1/2))
        """
        s = self.sides
        v = self.vertices
        return {v[0]: s[1].perpendicular_segment(v[0]),
                v[1]: s[2].perpendicular_segment(v[1]),
                v[2]: s[0].perpendicular_segment(v[2])}

    @property
    def orthocenter(self):
        """The orthocenter of the triangle."""
        a = self.altitudes
        return GeometryEntity.intersect(a[1], a[2])[0]

    @property
    def circumcenter(self):
        """The circumcenter of the triangle."""
        a,b,c = [x.perpendicular_bisector() for x in self.sides]
        return GeometryEntity.do_intersection(a, b)[0]

    @property
    def circumradius(self):
        """The radius of the circumcircle of the triangle."""
        return Point.distance(self.circumcenter, self.vertices[0])

    @property
    def circumcircle(self):
        """The circumcircle of the triangle."""
        return Circle(self.circumcenter, self.circumradius)

    @property
    def bisectors(self):
        """
        The angle bisectors of the triangle in a dictionary where the
        key is the vertex and the value is the bisector at that point.

        Example:
        ========
            >>> p1,p2,p3 = Point(0,0), Point(1,0), Point(0,1)
            >>> t = Triangle(p1, p2, p3)

            >>> from sympy import sqrt
            >>> t.bisectors[p2] == Segment(Point(0, sqrt(2)-1), Point(1, 0))
            True
        """
        s = self.sides
        v = self.vertices
        c = self.incenter
        l1 = Segment(v[0], GeometryEntity.do_intersection(Line(v[0], c), s[1])[0])
        l2 = Segment(v[1], GeometryEntity.do_intersection(Line(v[1], c), s[2])[0])
        l3 = Segment(v[2], GeometryEntity.do_intersection(Line(v[2], c), s[0])[0])
        return {v[0]: l1, v[1]: l2, v[2]: l3}

    @property
    def incenter(self):
        """The incenter of the triangle."""
        s = self.sides
        v = self.vertices
        A,B,C = v[0],v[1],v[2]
        a,b,c = s[1].length,s[2].length,s[0].length
        x = simplify( (a*A[0] + b*B[0] + c*C[0]) / (a+b+c) )
        y = simplify( (a*A[1] + b*B[1] + c*C[1]) / (a+b+c) )
        return Point(x, y)

    @property
    def inradius(self):
        """The inradius of the triangle."""
        return simplify(self.area / self.perimeter)

    @property
    def incircle(self):
        """The incircle of the triangle."""
        return Circle(self.incenter, self.inradius)

    @property
    def medians(self):
        """
        The medians of the triangle in a dictionary where the key
        is the vertex and the value is the median at that point.

        Example:
        ========
            >>> p1,p2,p3 = Point(0,0), Point(1,0), Point(0,1)
            >>> t = Triangle(p1, p2, p3)
            >>> t.medians[p1]
            Segment(Point(0, 0), Point(1/2, 1/2))
        """
        s = self.sides
        v = self.vertices
        return {v[0]: Segment(s[1].midpoint, v[0]),
                v[1]: Segment(s[2].midpoint, v[1]),
                v[2]: Segment(s[0].midpoint, v[2])}

    @property
    def medial(self):
        """
        The medial triangle of the triangle, the triangle which is formed
        from the midpoints of the three sides.
        """
        s = self.sides
        return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint)

    #@property
    #def excircles(self):
    #    """Returns a list of the three excircles for this triangle."""
    #    pass
