from math import pi, sqrt, cos, sin
from cmath import polar

from .tools import timer
from . import colors

import itertools
import random
import math
import copy

average = mean = lambda x: sum(x) / len(x)

digits = 2  # Number of digits of precision of the objects when displayed


class Point:
    """Representation of a point that can be displayed on screen."""

    @classmethod
    def sum(cls, points, **kwargs):
        """Return the points which components are the respectives sums of the
        components of the given points."""
        p = Point.null(**kwargs)
        for point in points:
            p += point
        return p

    @classmethod
    def average(cls, points):
        """Return the point which position is the average of
        the position of the given points"""
        return Point.sum(points) / len(points)

    @classmethod
    def origin(cls, d=2, **kwargs):
        """Return the origin."""
        return cls([0 for i in range(d)], **kwargs)

    null = neutral = zero = origin

    @classmethod
    def random(cls, d=2, borns=[-1, 1], **kwargs):
        """Create a random point using optional minimum and maximum."""
        components = [random.uniform(*borns) for i in range(d)]
        return cls(*components, **kwargs)

    @staticmethod
    def distance(p1, p2):
        """Return the distance between the two points."""
        return math.hypot(p1.x - p2.x, p1.y - p2.y)

    @staticmethod
    def turnPoints(angles, points):
        """Turn the points around themselves."""
        l = len(points)
        for i in range(l - 1):
            points[i].turn(angles[i], points[i + 1 :])

    @staticmethod
    def showPoints(surface, points):
        """Show the points on the surface."""
        for point in points:
            point.show(surface)

    @staticmethod
    def closest(point, points):
        """Return the point of the list of points that is the closest to the given point."""
        pts = [(Point.distance(point, p), p) for p in points]
        pts.sort()
        return pts[0][1]

    @staticmethod
    def farthest(point, points):
        """Return the point of the list of points that is the farthest to the given point."""
        # print(point)
        pts = [(Point.distance(point, p), p) for p in points]
        pts.sort()
        # print(" ".join(map(lambda pt: str(pt[0])[:5]+","+str(pt[1]), pts)))
        return pts[-1][1]

    @classmethod
    def createFromVector(cls, vector):
        """Create a point from a vector."""
        return cls(vector.x, vector.y)

    def __init__(
        self,
        *components,
        mode=0,
        size=[0.1, 0.1],
        width=1,
        radius=0.02,
        fill=False,
        color=colors.WHITE,
        conversion=True
    ):
        """Create a point using its components and optional radius, fill, color and conversion."""
        if components != ():
            if type(components[0]) == list:
                components = components[0]
        self.components = list(components)
        self.mode = mode
        self.size = size
        self.width = width
        self.radius = radius
        self.fill = fill
        self.color = color
        self.conversion = conversion

    def __hash__(self):
        """Return a single number using the hash of its attributes."""
        hash(self.components)

    def set(self, point):
        """Set the components of the point to the components of an other given point."""
        self.components = [c for c in point]

    def __len__(self):
        """Return the number of components of the point."""
        return len(self.components)

    def setX(self, value):
        """Set the x component."""
        self.components[0] = value

    def getX(self):
        """Return the x component."""
        return self.components[0]

    def delX(self):
        """Delete the x component and so shifting to a new one."""
        del self.components[0]

    def setY(self, value):
        """Set the y component."""
        self.components[1] = value

    def getY(self):
        """Return the y component."""
        return self.components[1]

    def delY(self):
        """Delete the y component."""
        del self.components[1]

    x = property(
        getX, setX, delX, "Allow the user to manipulate the x component easily."
    )
    y = property(
        getY, setY, delY, "Allow the user to manipulate the y component easily."
    )

    def __eq__(self, other):
        """Determine if two points are equals by comparing its components."""
        return abs(self - other) < 10e-10

    def __ne__(self, other):
        """Determine if two points are unequals by comparing its components."""
        return tuple(self) != tuple(other)

    def __rmul__(self, r):
        """Multiplication by a scalar like vectors."""
        return Point(*[r * c for c in self.components])

    def __truediv__(self, r):
        """Divide a points by a scalar."""
        return Point(*[c / r for c in self.components])

    def __contains__(self, other):
        """Determine if an object is in the point."""
        x, y = other
        return self.radius >= sqrt((x - self.x) ** 2 + (y - self.y) ** 2)

    def __getitem__(self, index):
        """Return x or y value using given index."""
        return self.components[index]

    def __setitem__(self, index, value):
        """Change x or y value using given index and value."""
        self.components[index] = value

    def __abs__(self):
        """Return the distance of the point to the origin."""
        return Vector.createFromPoint(self).norm

    def __tuple__(self):
        """Return the components in tuple form."""
        return tuple(self.components)

    def __list__(self):
        """Return the components."""
        return self.components

    def rotate(self, angle=pi, point=None):
        """Rotate the point using the angle and the center of rotation.
        Uses the origin for the center of rotation by default."""
        if not point:
            point = Point.origin(d=self.dimension)
        v = Vector.createFromTwoPoints(point, self)
        v.rotate(angle)
        self.components = v(point).components

    def turn(self, angle=pi, points=[]):
        """Turn the points around itself."""
        for point in points:
            point.rotate(angle, self)

    def move(self, *step):
        """Move the point using given step."""
        self.x += step[0]
        self.y += step[1]

    def around(self, point, distance):
        """Determine if a given point is in a radius 'distance' of the point."""
        return self.distance(point) <= distance

    def showCross(self, window, color=None, size=None, width=None, conversion=None):
        """Show the point under the form of a cross using the window."""
        if not color:
            color = self.color
        if not size:
            size = self.size
        if not width:
            width = self.width
        if not conversion:
            conversion = self.conversion
        x, y = self
        sx, sy = size
        xmin = x - sx / 2
        ymin = y - sy / 2
        xmax = x + sx / 2
        ymax = y + sy / 2
        window.draw.line(
            window.screen, color, [xmin, ymin], [xmax, ymax], width, conversion
        )
        window.draw.line(
            window.screen, color, [xmin, ymax], [xmax, ymin], width, conversion
        )

    def showCircle(self, window, color=None, radius=None, fill=None, conversion=None):
        """Show a point under the form of a circle using the window."""
        if not color:
            color = self.color
        if not radius:
            radius = self.radius
        if not fill:
            fill = self.fill
        if not conversion:
            conversion = self.conversion
        window.draw.circle(
            window.screen, color, [self.x, self.y], radius, fill, conversion
        )

    def show(
        self,
        window,
        color=None,
        mode=None,
        fill=None,
        radius=None,
        size=None,
        width=None,
        conversion=None,
    ):
        """Show the point on the window."""
        if not mode:
            mode = self.mode
        if mode == 0 or mode == "circle":
            self.showCircle(window, color, radius, fill, conversion)
        if mode == 1 or mode == "cross":
            self.showCross(window, color, size, width, conversion)

    def showText(self, context, text, size=1, color=colors.WHITE, conversion=True):
        """Show the text next to the point on the window."""
        context.print(text, self.components, size, color=color, conversion=conversion)

    def __add__(self, other):
        """Add two points."""
        return Point([c1 + c2 for (c1, c2) in zip(self, other)])

    def __iadd__(self, other):
        """Add a point to the actual point."""
        self.components = [c1 + c2 for (c1, c2) in zip(self, other)]
        return self

    __radd__ = __add__

    def __sub__(self, other):
        """Add a point to the actual point."""
        return Point([c1 - c2 for (c1, c2) in zip(self, other)])

    def __isub__(self, other):
        """Add a point to the actual point."""
        self.components = [c1 - c2 for (c1, c2) in zip(self, other)]
        return self

    __rsub__ = __sub__

    def __sub__(self, other):
        """Substract the components of 2 objects."""
        return Point(self.x - other[0], self.y - other[1])

    def __ge__(self, other):
        """Determine if a point is farther to the origin."""
        return self.x ** 2 + self.y ** 2 >= other.x ** 2 + other.y ** 2

    def __gt__(self, other):
        """Determine if a point is farther to the origin."""
        return self.x ** 2 + self.y ** 2 > other.x ** 2 + other.y ** 2

    def __le__(self, other):
        """Determine if a point is the nearest to the origin."""
        return self.x ** 2 + self.y ** 2 <= other.x ** 2 + other.y ** 2

    def __lt__(self, other):
        """Determine if a point is the nearest to the origin."""
        return self.x ** 2 + self.y ** 2 < other.x ** 2 + other.y ** 2

    def __iter__(self):
        """Iterate the points of the form."""
        self.iterator = 0
        return self

    def __next__(self):
        """Return the next point threw an iteration."""
        if self.iterator < len(self.components):
            self.iterator += 1
            return self.components[self.iterator - 1]
        else:
            raise StopIteration

    def truncate(self):
        """Truncate the position of the point by making the x and y components integers."""
        for i in range(self.dimension):
            self.components[i] = int(self.components[i])

    def __str__(self):
        """Return the string representation of a point."""
        return "p(" + ",".join([str(round(c, digits)) for c in self.components]) + ")"

    def getPosition(self):
        """Return the components."""
        return self.components

    def setPosition(self, position):
        """Set the components."""
        self.components = position

    def getDimension(self):
        """Return the dimension of the point."""
        return len(self.components)

    def setDimension(self, dimension):
        """Set the dimension of the point by setting to 0 the new components."""
        self.components = self.components[:dimension]
        self.components += [0 for i in range(dimension - len(self.components))]

    def delDimension(self):
        """Delete the components of the points."""
        self.components = []

    position = property(
        getPosition,
        setPosition,
        "Same as component although only component should be used.",
    )
    dimension = property(
        getDimension,
        setDimension,
        delDimension,
        "Representation of the dimension point which is the length of the components.",
    )


class Direction:
    """Base class of lines and segments."""

    pass


class Vector:
    @classmethod
    def null(cls, d=2):
        """Return the null vector."""
        return cls(*[0 for i in range(d)])

    neutral = zero = null

    @classmethod
    def random(cls, d=2, borns=[-1, 1], **kwargs):
        """Create a random vector using optional min and max."""
        components = [random.uniform(*borns) for i in range(d)]
        return cls(*components, **kwargs)

    @classmethod
    def sum(cls, vectors):
        """Return the vector that correspond to the sum of all the vectors."""
        result = cls.null()
        for vector in vectors:
            result += vector
        return result

    @classmethod
    def average(cls, vectors):
        """Return the vector that correspond to the mean of all the vectors."""
        return cls.sum(vectors) / len(vectors)

    mean = average

    @classmethod
    def collinear(cls, *vectors, e=10e-10):
        """Determine if all the vectors are colinear."""
        l = len(vectors)
        if l == 2:
            v1 = vectors[0]
            v2 = vectors[1]
            return abs(v1.x * v2.y - v1.y - v2.x) < e
        else:
            for i in range(l):
                for j in range(i + 1, l):
                    if not cls.collinear(vectors[i], vectors[j]):
                        return False
            return True

    @classmethod
    def sameDirection(cls, *vectors, e=10e-10):
        """Determine if all the vectors are in the same direction."""
        l = len(vectors)
        if l == 2:
            v1 = vectors[0]
            v2 = vectors[1]
            return (abs(v1.angle - v2.angle) % (2 * math.pi)) < e
        else:
            for i in range(l):
                for j in range(i + 1, l):
                    if not cls.sameDirection(vectors[i], vectors[j]):
                        return False
            return True

    @classmethod
    def createFromPolar(cls, norm, angle, **kwargs):
        """Create a vector using norm and angle from polar coordonnates."""
        x, y = cls.cartesian([norm, angle])
        return cls(x, y, **kwargs)

    @classmethod
    def createFromSegment(cls, segment, **kwargs):
        """Create a vector from a segment."""
        return cls.createFromTwoPoints(segment.p1, segment.p2, **kwargs)

    @classmethod
    def createFromTwoPoints(cls, point1, point2, **kwargs):
        """Create a vector from 2 points."""
        return cls([c2 - c1 for (c1, c2) in zip(point1, point2)], **kwargs)

    @classmethod
    def createFromTwoTuples(cls, tuple1, tuple2, **kwargs):
        """Create a vector from 2 tuples."""
        return cls([c2 - c1 for (c1, c2) in zip(tuple1, tuple2)], **kwargs)

    @classmethod
    def createFromPoint(cls, point, **kwargs):
        """Create a vector from a single point."""
        return cls(point.x, point.y, **kwargs)

    @classmethod
    def createFromLine(cls, line, **kwargs):
        """Create a vector from a line."""
        angle = line.angle
        x, y = cls.cartesian([1, angle])
        return cls(x, y, **kwargs)

    @staticmethod
    def polar(position):
        """Return the polar position [norm,angle] using cartesian position [x,y]."""
        return list(polar(complex(position[0], position[1])))

    @staticmethod
    def cartesian(position):
        """Return the cartesian position [x,y] using polar position [norm,angle]."""
        return [position[0] * cos(position[1]), position[0] * sin(position[1])]

    def __init__(self, *components, color=colors.WHITE, width=1, arrow=[0.1, 0.5]):
        """Create a vector."""
        if components:
            if isinstance(components[0], list):
                components = components[0]
        self.components = list(components)
        self.color = color
        self.width = width
        self.arrow = arrow

    def set(self, v):
        """Set a vector to the values of another without changing its color, with or arrow."""
        self.components = v.components

    def setNull(self):
        """Set the components of the vector to zero."""
        self.components = [0 for i in range(len(self.components))]

    # X component
    def setX(self, value):
        """Set the x component."""
        self.components[0] = value

    def getX(self):
        """Return the x component."""
        return self.components[0]

    def delX(self):
        """Delete the x component and so shifting to a new one."""
        del self.components[0]

    # Y component
    def setY(self, value):
        """Set the y component."""
        self.components[1] = value

    def getY(self):
        """Return the y component."""
        return self.components[1]

    def delY(self):
        """Delete the y component."""
        del self.components[1]

    # Angle
    def getAngle(self):
        """Return the angle of a vector in polar coordinates."""
        x, y = self.components
        return math.atan2(y, x)

    def setAngle(self, value):
        """Change the angle of the points without changing its norm."""
        n, a = Vector.polar(self.components)
        self.components = Vector.cartesian([n, value])

    def delAngle(self):
        """Set to zero the angle of the vector."""
        self.setAngle(0)

    # Norm
    def getNorm(self, norm=lambda l: (sum(map(lambda x: x ** 2, l))) ** (1 / 2)):
        """Return the euclidian norm of the vector by default."""
        return norm(self.components)

    def setNorm(self, value):
        """Change the angle of the points without changing its norm."""
        n, a = Vector.polar(self.components)
        self.components = Vector.cartesian([value, a])

    # Position
    def getPosition(self):
        """Return the components."""
        return self.components

    def setPosition(self, position):
        """Set the components."""
        self.components = position

    def delPosition(self):
        """Set the vector to the null vector."""
        self.components = [0 for i in range(len(self.components))]

    x = property(
        getX, setX, delX, doc="Allow the user to manipulate the x component easily."
    )
    y = property(
        getY, setY, delY, doc="Allow the user to manipulate the y component easily."
    )
    norm = property(
        getNorm,
        setNorm,
        doc="Allow the user to manipulate the norm of the vector easily.",
    )
    angle = property(
        getAngle,
        setAngle,
        doc="Allow the user to manipulate the angle of the vector easily.",
    )
    position = property(getPosition, setPosition, doc="Same as components.")

    def limit(self, n):
        """Limit the norm of the vector by n."""
        if self.norm > n:
            self.norm = n

    def show(self, context, p=Point.neutral(), color=None, width=None):
        """Show the vector."""
        if not color:
            color = self.color
        if not width:
            width = self.width
        q = self(p)
        v = -self * self.arrow[0]  # wtf
        v1 = v % self.arrow[1]
        v2 = v % -self.arrow[1]
        a = v1(q)
        b = v2(q)
        context.draw.line(context.screen, color, p.components, q.components, width)
        context.draw.line(context.screen, color, q.components, a.components, width)
        context.draw.line(context.screen, color, q.components, b.components, width)

    def showFromTuple(self, context, t=(0, 0), **kwargs):
        """Show a vector from a tuple."""
        p = Point(*t)
        self.show(context, p, **kwargs)

    def showText(self, surface, point, text, color=None, size=20):
        """Show the text next to the vector."""
        if not color:
            color = self.color
        v = self / 2
        point = v(point)
        surface.print(text, tuple(point), color=color, size=size)

    def __len__(self):
        """Return the number of components."""
        return len(self.components)

    def __iter__(self):
        """Iterate the points of the form."""
        self.iterator = 0
        return self

    def __next__(self):
        """Return the next point threw an iteration."""
        if self.iterator < len(self.components):
            self.iterator += 1
            return self.components[self.iterator - 1]
        else:
            raise StopIteration

    def __neg__(self):
        """Return the negative vector."""
        return Vector([-c for c in self.components])

    def colinear(self, other, e=10e-10):
        """Return if two vectors are colinear."""
        return abs(self.x * other.y - self.y * other.x) < e

    __floordiv__ = colinear

    def scalar(self, other):
        """Return the scalar product between two vectors."""
        return self.x * other.x + self.y * other.y

    def cross(self, other):
        """Determine if a vector crosses another using dot product."""
        return self.scalar(other) == 0

    def __mul__(self, factor):
        """Multiply a vector by a given factor."""
        if type(factor) == int or type(factor) == float:
            return Vector([c * factor for c in self.components])
        else:
            raise NotImplementedError
            raise Exception(
                "Type "
                + str(type(factor))
                + " is not valid. Expected float or int types."
            )

    __imul__ = (
        __rmul__
    ) = __mul__  # Allow front extern multiplication using back extern multiplication with scalars

    def __truediv__(self, factor):
        """Multiply a vector by a given factor."""
        if type(factor) == Vector:
            raise NotImplementedError
        else:
            return Vector([c / factor for c in self.components])

    def __itruediv__(self, factor):
        """Divide the components of a vector by a given factor."""
        self.components = [c / factor for c in self.components]
        return self

    def __add__(self, other):
        """Add two vector together."""
        return Vector([c1 + c2 for (c1, c2) in zip(self.components, other.components)])

    def __sub__(self, other):
        """Sub two vector together."""
        return Vector([c1 - c2 for (c1, c2) in zip(self.components, other.components)])

    def __iadd__(self, other):
        """Add a vector to another."""
        self.components = [
            c1 + c2 for (c1, c2) in zip(self.components, other.components)
        ]
        return self

    def __isub__(self, other):
        """Substract a vector to another."""
        self.components = [
            c1 - c2 for (c1, c2) in zip(self.components, other.components)
        ]
        return self

    __radd__ = __add__
    __rsub__ = __sub__

    def rotate(self, angle):
        """Rotate a vector using the angle of rotation."""
        n, a = Vector.polar([self.x, self.y])
        a += angle
        self.x = n * cos(a)
        self.y = n * sin(a)

    def __mod__(self, angle):
        """Return the rotated vector using the angle of rotation."""
        n, a = Vector.polar([self.x, self.y])
        a += angle
        return Vector(
            n * cos(a), n * sin(a), color=self.color, width=self.width, arrow=self.arrow
        )

    __imod__ = __mod__

    def __getitem__(self, index):
        """Return x or y value using given index."""
        return self.position[index]

    def __setitem__(self, index, value):
        """Change x or y value using given index and value."""
        self.position[index] = value

    def __call__(self, *points):
        """Return points by applying the vector on those."""
        if points != ():
            if type(points[0]) == list:
                points = points[0]
        if len(points) == 0:
            raise Exception(
                "A vector can only be applied to a point or a list of points."
            )
        elif len(points) == 1:
            return points[0] + self
        else:
            return [point + self for point in points]

    def applyToPoint(self, point):
        """Return the point after applying the vector to it."""
        return self + point

    def applyToPoints(self, points):
        """Return the points after applying the vector to those."""
        return [point + self for point in points]

    def __xor__(self, other):
        """Return the angle between two vectors."""
        return self.angle - other.angle

    def __invert__(self):
        """Return the unit vector."""
        a = self.angle
        x, y = Vector.cartesian([1, a])
        return Vector(x, y)

    unit = property(__invert__)

    def __str__(self):
        """Return a string description of the vector."""
        return "v(" + ",".join([str(round(c, digits)) for c in self.components]) + ")"

    def __tuple__(self):
        """Return the components in tuple form."""
        return tuple(self.components)

    def __list__(self):
        """Return the components."""
        return self.components


class Segment(Direction):
    @classmethod
    def null(cls):
        """Return the segment whoose points are both the origin."""
        return cls(*[Point.origin() for i in range(2)])

    @classmethod
    def random(cls, d=2, borns=[-1, 1], **kwargs):
        """Create a random segment."""
        p1 = Point.random(d, borns)
        p2 = Point.random(d, borns)
        return cls(*[p1, p2], **kwargs)

    @classmethod
    def createFromTuples(cls, *tps, **kwargs):
        """Create a segment using tuples and optional arguments."""
        pts = [Point(*tp) for tp in tps]
        return cls(*pts, **kwargs)

    def __init__(self, *points, width=1, color=colors.WHITE, conversion=True):
        """Create the segment using 2 points, width and color."""
        if (
            len(points) > 0
        ):  # Extracting the points arguments under the same list format
            if type(points[0]) == list:
                points = points[0]
        if len(points) == 1:
            points = points[0]
        if len(points) != 2:
            raise Exception("A segment must have 2 points.")
        self.points = list(points)
        self.width = width
        self.color = color
        self.conversion = conversion

    def __str__(self):
        """Return the string representation of a segment."""
        return "s(" + str(self.p1) + "," + str(self.p2) + ")"

    def __call__(self, t=1 / 2):
        """Return the point C of the segment so that Segment(p1,C)=t*Segment(p1,p2)."""
        return (t * self.vector)(self.p1)

    def sample(self, n, include=True):
        """Sample n points of the segment.
        It is also possible to include the last point if wanted."""
        return [self(t / n) for t in range(n + int(include))]

    __rmul__ = __imul__ = __mul__ = lambda self, t: Segment(self.p1, self(t))

    def getCenter(self):
        """Return the center of the segment in the general case."""
        return Point.average(self.points)

    def setCenter(self, np):
        """Set the center of the segment."""
        p = self.getCenter()
        v = Vector.createFromTwoPoints(p, np)
        for i in range(len(self.points)):
            self.points[i] = v(self.points[i])

    def getAngle(self):
        """Return the angle of the segment."""
        return self.vector.angle

    def setAngle(self, angle):
        """Set the angle of the segment."""
        self.vector.angle = angle

    def show(self, context, color=None, width=None, conversion=None):
        """Show the segment using window."""
        if color is None:
            color = self.color
        if width is None:
            width = self.width
        if conversion is None:
            conversion = self.conversion
        context.draw.line(
            context.screen, color, self.p1, self.p2, width, conversion=conversion
        )

    def showInBorders(self, window, color=None, width=None):
        """Show the segment within the boundaries of the window."""
        # It it really slow and it doesn't work as expected.
        xmin, ymin, xmax, ymax = window.getCorners()
        p = [Point(xmin, ymin), Point(xmax, ymin), Point(xmax, ymax), Point(xmin, ymax)]
        f = Form(p)
        if (self.p1 in f) and (self.p2 in f):
            window.draw.line(
                window.screen,
                color,
                [self.p1.x, self.p1.y],
                [self.p2.x, self.p2.y],
                width,
            )
        elif (self.p1 in f) and not (self.p2 in f):
            v = Vector.createFromTwoPoints(self.p1, self.p2)
            hl = HalfLine(self.p1, v.angle)
            p = f.crossHalfLine(hl)
            if p:
                print(len(p))
                p = p[0]
                window.draw.line(
                    window.screen, color, [self.p1.x, self.p1.y], [p.x, p.y], width
                )
        elif not (self.p1 in f) and (self.p2 in f):
            v = Vector.createFromTwoPoints(self.p2, self.p1)
            hl = HalfLine(self.p2, v.angle)
            p = f.crossHalfLine(hl)
            if p:
                print(len(p))
                p = p[0]
                window.draw.line(
                    window.screen, color, [p.x, p.y], [self.p2.x, self.p2.y], width
                )
        else:
            ps = f.crossSegment(self)
            if len(ps) == 2:
                p1, p2 = ps
                window.draw.line(
                    window.screen, color, [p1.x, p1.y], [p2.x, p2.y], width
                )

    def __contains__(self, point, e=1e-10):
        """Determine if a point is in a segment."""
        if point == self.p1:
            return True
        v1 = Vector.createFromTwoPoints(self.p1, point)
        v2 = self.getVector()
        return (abs(v1.angle - v2.angle) % (2 * math.pi) < e) and (v1.norm <= v2.norm)

    def __len__(self):
        """Return the number of points."""
        return len(self.points)

    def __iter__(self):
        """Iterate the points of the form."""
        self.iterator = 0
        return self

    def __next__(self):
        """Return the next point through an iteration."""
        if self.iterator < len(self.points):
            self.iterator += 1
            return self.points[self.iterator - 1]
        else:
            raise StopIteration

    def __getitem__(self, index):
        """Return the point corresponding to the index given."""
        return [self.points][index]

    def __setitem__(self, index, value):
        """Change the value the point corresponding value and index given."""
        self.points[index] = value

    def getLine(self, **kwargs):
        """Return the line through the end points of the segment."""
        return Line(self.p1, self.angle, **kwargs)

    def getVector(self):
        """Return the vector that goes from p1 to p2."""
        return Vector.createFromTwoPoints(self.p1, self.p2)

    def setVector(self, vector):
        """Set the vector that goes from p1 to p2."""
        self.p2 = vector(self.p1)

    def getLength(self):
        """Return the length of the segment."""
        return self.vector.norm

    def setLength(self, length):
        """Set the length of the segment."""
        self.vector.norm = length

    def rotate(self, angle, point=None):
        """Rotate the segment using an angle and an optional point of rotation."""
        if not point:
            point = self.middle
        self.p1.rotate(angle, point)
        self.p2.rotate(angle, point)

    def __or__(self, other):
        """Return bool for (2 segments are crossing)."""
        if isinstance(other, Segment):
            return self.crossSegment(other)
        elif isinstance(other, Line):
            return self.crossLine(other)
        elif isinstance(other, HalfLine):
            return other.crossSegment(self)
        elif isinstance(other, Form):
            return other.crossSegment(self)
        else:
            raise TypeError(
                "The collisions {}|{} are not dealt with.".format(
                    type(self), type(other)
                )
            )

    def getXmin(self):
        """Return the minimum of x components of the 2 end points."""
        return min(self.p1.x, self.p2.x)

    def getYmin(self):
        """Return the minimum of y components of the 2 ends points."""
        return min(self.p1.y, self.p2.y)

    def getXmax(self):
        """Return the maximum of x components of the 2 end points."""
        return max(self.p1.x, self.p2.x)

    def getYmax(self):
        """Return the maximum of y components of the 2 end points."""
        return max(self.p1.y, self.p2.y)

    def getMinimum(self):
        """Return the lower point"""
        v1 = Vector(*self.p1)
        v2 = Vector(*self.p2)
        if v1.angle < v2.angle:
            return self.p1
        else:
            return self.p2

    def getMaximum(self):
        """Return the upper point."""
        v1 = Vector(*self.p1)
        v2 = Vector(*self.p2)
        if v1.angle >= v2.angle:
            return self.p1
        else:
            return self.p2

    def getCorners(self):
        """Return the minimum and maximum of x and y components of the 2 end points."""
        return self.minimum.components + self.maximum.components

    def parallel(self, other):
        """Determine if the line is parallel to another object (line or segment)."""
        return other.angle == self.angle

    def cross(self, other, **kwargs):
        if isinstance(other, Segment):
            return self.crossSegment(other, **kwargs)
        elif isinstance(other, Line):
            return other.crossSegment(self, **kwargs)
        else:
            raise TypeError(
                "The collisions {}|{} are not dealt with.".format(
                    type(self), type(other)
                )
            )

    def crossSegment(self, other, e=1e-14):
        """Return the intersection point of the segment with another segment."""
        sl = self.getLine()
        ol = other.getLine()
        point = sl.crossLine(ol)
        if point is not None:
            if self.__contains__(point, e) and other.__contains__(point, e):
                return point

    def crossLine(self, other):
        """Return the intersection point of the segment with a line."""
        if self.parallel(other):
            return None
        line = self.getLine()
        point = other.crossLine(line)
        if point is not None:
            if point in self and point in other:
                return point

    def getP1(self):
        """Return the first point of the segment."""
        return self.points[0]

    def setP1(self, p1):
        """Set the first point of the segment."""
        self.points[0] = p1

    def getP2(self):
        """Return the second point of the segment."""
        return self.points[1]

    def setP2(self, p2):
        """Set the second point of the segment."""
        self.points[1] = p2

    p1 = property(getP1, setP1, doc="First point of the segment.")
    p2 = property(getP2, setP2, doc="Second point of the segment.")
    middle = center = property(getCenter, setCenter, doc="Center of the segment.")
    vector = property(getVector, setVector, doc="Unit vector of the segment.")
    angle = property(getAngle, setAngle, doc="Angle of the segment.")
    length = property(getLength, setLength, doc="Length of the segment.")
    line = property(getLine, doc="Line passing by both extremities.")
    xmin = property(getXmin, doc="xmin")
    ymin = property(getYmin, doc="ymin")
    xmax = property(getXmax, doc="xmax")
    ymax = property(getYmax, doc="ymax")
    minimum = property(getMinimum, doc="Point at the right")
    maximum = property(getMaximum, doc="Point at the left")


class Line(Direction):
    @classmethod
    def random(cls, borns=[-1, 1], angle_borns=[-math.pi, math.pi], **kwargs):
        """Return a random line."""
        point = Point.random(borns=borns)
        angle = random.uniform(*angle_borns)
        return cls(point, angle, **kwargs)

    @classmethod
    def createFromPointAndVector(cls, point, vector, **kwargs):
        """Create a line using a point and a vector with optional features."""
        return cls(point, vector.angle, **kwargs)

    @classmethod
    def createFromTwoPoints(cls, point1, point2, **kwargs):
        """Create a line using two points with optional features."""
        vector = Vector.createFromTwoPoints(point1, point2)
        return cls(point1, vector.angle, **kwargs)

    def __init__(self, point, angle, width=1, color=colors.WHITE, correct=True):
        """Create the line using a point and a vector with optional width and color.
        The line is uses a unique system of components [neighbour point, angle].
        The neighbour point is the nearest point to (0,0) that belongs to the line.
        The angle is the orientated angle between the line itself and another line parallel
        to the x-axis and crossing the neighbour point. Its range is [-pi/2,pi/2[ which makes it unique."""
        if correct:
            self.angle = angle
            self.point = point
        else:
            self._angle = angle
            self._point = point
        self.width = width
        self.color = color

    def __str__(self, precision=2):
        """Return a string representation of the line."""
        return (
            "l(a="
            + str(round(self.slope, precision))
            + ",b="
            + str(round(self.ordinate, precision))
            + ")"
        )

    def __call__(self, x):
        """Evaluate the line like a linear function in cartesian coordinates."""
        return self.slope * x + self.ordinate

    def __eq__(self, l):
        """Determine if two lines are the same."""
        return l.point == self.point and l.angle == self.angle

    def getCompleteCartesianCoordonnates(self):
        """Return a,b,c according to the cartesian equation of the line: ax+by+c=0."""
        v = self.vector
        p1 = self.point
        p2 = v(self.point)
        if v.x == 0:
            a = 1
            b = 0
            c = -p1.x
        else:
            a = -(p1.y - p2.y) / (p1.x - p2.x)
            b = 1
            c = -(a * p1.x + b * p1.y)
        return a, b, c

    def getReducedCartesianCoordonnates(self):
        """Return a,b according to the reduced cartesian equation of the line: y=ax+b."""
        return self.slope, self.ordinate

    def getAngle(self):
        """Return the angle of the line."""
        return self._angle

    def setAngle(self, angle):
        """Set the angle of the line."""
        self._angle = (angle + math.pi / 2) % math.pi - math.pi / 2
        # self._angle = angle % (2*math.pi)

    angle = property(
        getAngle,
        setAngle,
        doc="Representation of the angle of the line after correction.",
    )

    def rotate(self, angle, point=Point(0, 0)):
        """Rotate the line."""
        self.angle += angle  # Incomplete

    def getPoint(self):
        """Return the neighbour point."""
        return self._point

    def setPoint(self, point):
        """Set the neighbour point to another one."""
        self._point = point
        self._point = self.projectPoint(Point.origin(point.dimension))

    point = property(getPoint, setPoint, doc="Neighbour point of the line.")

    def getUnitVector(self):
        """Return the unit vector of the line."""
        return Vector.createFromPolar(1, self.angle)

    def setUnitVector(self, vector):
        """Set the unit vector of the line."""
        self.angle = vector.angle

    def getNormalVector(self):
        """Return the normal vector of the line."""
        vector = self.unit_vector
        vector.rotate(math.pi / 2)
        return vector

    def setNormalVector(self, vector):
        """Set the normal vector of the line."""
        self.angle = vector.angle + math.pi / 2

    def getSlope(self):
        """Return the slope of the line."""
        return math.tan(self.angle)

    def setSlope(self, slope):
        """Set the slope of the line by changing its angle and point."""
        self.angle = math.atan(slope)

    def getOrdinate(self):
        """Return the ordinate of the line."""
        return self.point.y - self.slope * self.point.x

    def setOrdinate(self, ordinate):
        """Set the ordinate of the line by changing its position."""
        if abs(self.angle) == math.pi / 2:
            raise ValueError(
                "Impossible to set an ordinate because the line is parallel to the y axis. "
            )
        self.point.y += ordinate

    def getFunction(self):
        """Return the affine function that correspond to the line."""
        return lambda x: self.slope * x + self.ordinate

    def setFunction(self, function):
        """Set the function of the line by changing its slope and ordinate."""
        self.ordinate = function(0)
        self.slope = function(1) - function(0)

    def getReciproque(self):
        """Return the reciproque of the affine function that correspond to the line."""
        return lambda y: (y - self.ordinate) / self.slope

    def setReciproque(self, reciproque):
        """Return the reciproque function of the affine function."""
        x0, x1 = reciproque(0), reciproque(1)
        self.slope = 1 / (x1 - x0)
        self.ordinate = -x0 * self.slope

    def evaluate(self, x):
        """Evaluate the line as a affine function."""
        return self.function(x)

    def devaluate(self, y):
        """Evaluate the reciproque function of the affine funtion of the line."""
        return self.reciproque(y)

    def cross(self, other):
        """Return the point of intersection between the line and another object."""
        if isinstance(other, Line):
            return self.crossLine(other)
        elif isinstance(other, Segment):
            return self.crossSegment(other)
        elif isinstance(other, HalfLine):
            return other.crossLine(self)
        elif isinstance(other, Form):
            return other.crossLine(self)
        else:
            raise TypeError(
                "The collisions {}|{} are not dealt with.".format(
                    type(self), type(other)
                )
            )

    __or__ = cross

    def crossSegment(self, other, e=1e-14, **kwargs):
        """Return the point of intersection between a segment and the line."""
        # Determine the point of intersection between the line of the given segment ang the line
        line = other.getLine()
        point = self.crossLine(line)
        if point is None:
            return None
        x, y = point
        # Determine if the point of intersection belongs to both the segment and the line
        if (
            other.xmin - e <= point.x <= other.xmax + e
            and other.ymin - e <= y <= other.ymax + e
        ):
            return Point(x, y, **kwargs)
        # By default if nothing is returned the function returns None

    def crossLine(self, other):
        """Return the point of intersection between two lines with vectors
        calculation. Works in all cases even with vertical lines."""
        a, b = self.point
        c, d = other.point
        m, n = self.vector
        o, p = other.vector
        if n * o == m * p:  # The lines are parallels
            return None
        elif self.angle == -math.pi / 2:
            return Point(a, d)
        elif other.angle == -math.pi / 2:
            return Point(b, c)
        else:
            x = (a * n * o - b * m * o - c * m * p + d * m * o) / (n * o - m * p)
            y = (x - a) * n / m + b
            return Point(x, y)

    def parallel(self, other):
        """Determine if the line is parallel to another object (line or segment)."""
        return other.angle == self.angle

    def __contains__(self, point, e=10e-10):
        """Determine if a point belongs to the line."""
        v1 = self.vector
        v2 = Vector.createFromTwoPoints(self.point, point)
        return v1.colinear(v2, e)

    def getHeight(self, point):
        """Return the height line between the line and a point."""
        return Line(point, self.normal_vector.angle)

    def distanceFromPoint(self, point):
        """Return the distance between a point and the line."""
        return Vector.createFromTwoPoints(
            point, self.crossLine(self.getHeight(point))
        ).norm

    def projectPoint(self, point):
        """Return the projection of the point on the line."""
        vector = self.normal_vector
        angle = vector.angle
        line = Line(point, angle, correct=False)
        projection = self.crossLine(line)
        return projection

    def projectPoints(self, points):
        """Return the projections of the points on the line."""
        return [self.projectPoint(point) for point in points]

    def projectSegment(self, segment):
        """Return the projection of a segment on the line."""
        points = self.projectPoints(segment.points)
        return Segment(*points, segment.width, segment.color)

    def getSegmentWithinCorners(self, corners):
        """Return the segment made of the points of the line which are in the area
        delimited by the corners."""
        xmin, ymin, xmax, ymax = corners
        p1 = Point(xmin, ymin)
        p2 = Point(xmax, ymin)
        p3 = Point(xmax, ymax)
        p4 = Point(xmin, ymax)
        s1 = Segment(p1, p2)
        s2 = Segment(p2, p3)
        s3 = Segment(p3, p4)
        s4 = Segment(p4, p1)
        pts = []
        for s in [s1, s2, s3, s4]:
            cross = self.crossSegment(s)
            if cross:
                pts.append(cross)
        if len(pts) == 2:
            return Segment(*pts)

    def getPointsWithinCorners(self, corners):
        """Return the segment made of the points of the line which are in the area
        delimited by the corners."""
        xmin, ymin, xmax, ymax = corners
        p1 = Point(xmin, ymin)
        p2 = Point(xmax, ymin)
        p3 = Point(xmax, ymax)
        p4 = Point(xmin, ymax)
        v1 = Vector(p1, p2)
        v2 = Vector(p2, p3)
        v3 = Vector(p3, p4)
        v4 = Vector(p4, p1)
        l1 = Line.createFromPointAndVector(p1, v1)
        l2 = Line.createFromPointAndVector(p2, v2)
        l3 = Line.createFromPointAndVector(p3, v3)
        l4 = Line.createFromPointAndVector(p4, v4)
        lines = [l1, l3]
        points = []
        for line in lines:
            cross = self.crossLine(line)
            if cross:
                points.append(cross)
        if not points:
            lines = [l2, l4]
            for line in lines:
                cross = self.crossLine(line)
                if cross:
                    points.append(cross)
        return points

    def show(self, context, width=None, color=None):
        """Show the line on the context by drawing a segment greater than
        the size of the context."""
        if not color:
            color = self.color
        if not width:
            width = self.width
        s = max(context.size)
        u = min(context.units)
        n = s / u
        p = Point(*context.point())
        p = self.projectPoint(p)
        v1 = Vector.createFromPolar(n, self.angle)
        v2 = -v1
        s = Segment(v1(p), v2(p), color=color, width=width, conversion=True)
        s.show(context)

    def showWithinCorners(self, context, width=None, color=None):
        if not color:
            color = self.color
        if not width:
            width = self.width
        s = self.getSegmentWithinCorners(context.corners)
        s.show(context)

    vector = unit_vector = property(
        getUnitVector, setUnitVector, doc="Unit vector of the line."
    )
    normal_vector = property(
        getNormalVector, setNormalVector, doc="Normal vector of the line."
    )
    slope = property(getSlope, setSlope, doc="Slope of the line in cartesian system.")
    ordinate = property(
        getOrdinate, setOrdinate, doc="Ordinate of the line in cartesian system."
    )
    function = property(
        getFunction, setFunction, doc="Linear function of the line in cartesian system."
    )
    reciproque = property(
        getReciproque,
        setReciproque,
        doc="Reciproque of the function of the line in cartesian system.",
    )


class HalfLine(Line):
    def createFromLine(line):
        """Create a half line."""
        return HalfLine(line.point, line.angle)

    def __init__(self, point, angle, color=colors.WHITE, width=1):
        """Create a half line."""
        super().__init__(point, angle, color=color, width=width, correct=False)

    def getLine(self, correct=True):
        """Return the line that correspond to the half line."""
        return Line(self.point, self.angle, correct=correct)

    def getPoint(self):
        """Return the point of the half line."""
        return self.point

    def setPoint(self, point):
        """Set the point of the half line."""
        self.point = point

    def show(self, context, width=None, color=None):
        """Show the line on the surface."""
        if not color:
            color = self.color
        if not width:
            width = self.width
        xmin, ymin, xmax, ymax = context.corners
        form = Form.createFromTuples(
            [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
        )
        points = form.crossHalfLine(self)
        points += [self.point] * (2 - len(points))
        if len(points) > 0:
            context.draw.line(context.screen, color, points[0], points[1], width)

    def __contains__(self, point, e=10e-10):
        """Determine if a point is in the half line."""
        v1 = self.vector
        v2 = Vector.createFromTwoPoints(self.point, point)
        return abs(v1.angle - v2.angle) < e

    def cross(self, other):
        """Return the intersection point between the half line and another object."""
        if isinstance(other, Line):
            return self.crossLine(other)
        if isinstance(other, Segment):
            return self.crossSegment(other)
        if isinstance(other, HalfLine):
            return self.crossHalfLine(other)
        if isinstance(other, Form):
            return other.crossHalfLine(self)

    __or__ = cross

    def crossHalfLine(self, other):
        """Return the point of intersection of the half line with another."""
        ml = self.getLine(correct=False)
        ol = other.getLine(correct=False)
        point = ml.crossLine(ol)
        if point:
            if (point in self) and (point in other):
                return point

    def crossLine(self, other):
        """Return the point of intersection of the half line with a line."""
        ml = self.getLine(correct=False)
        point = ml.crossLine(other)
        if point:
            if (point in self) and (point in other):
                return point

    def crossSegment(self, other):
        """Return the point of intersection of the half line with a segment."""
        ml = self.getLine(correct=False)
        ol = other.getLine(correct=False)
        point = ml.crossLine(ol)
        if point:
            if (point in self) and (point in other):
                return point

    def __str__(self):
        """Return the string representation of a half line."""
        return "hl(" + str(self.point) + "," + str(self.angle) + ")"


class Form:
    @classmethod
    def random(cls, n=random.randint(5, 10), d=2, borns=[-1, 1], **kwargs):
        """Create a random form using the number of points 'n', the dimension
        of the points 'p' and their borns with optional arguments."""
        points = [Point.random(d=d, borns=borns) for i in range(n)]
        form = cls(points, **kwargs)
        form.makeSparse()
        return form

    def anyCrossing(forms):
        """Determine if any of the forms are crossing."""
        if len(forms) == 1:
            forms = forms[0]
        l = len(forms)
        for i in range(l):
            for j in range(i + 1, l):
                if forms[i].crossForm(forms[j]):
                    return True
        return False

    def allCrossing(forms):
        """Determine if all the forms are crossing."""
        if len(forms) == 1:
            forms = forms[0]
        l = len(forms)
        for i in range(l):
            for j in range(i + 1, l):
                if not forms[i].crossForm(forms[j]):
                    return False
        return True

    def cross(form1, form2):
        """Return the points of intersection between the crossing forms."""
        for point in form1.points:
            if point in form2:
                return True
        for point in form2.points:
            if point in form1:
                return True
        return

    @classmethod
    def intersectionTwoForms(cls, form1, form2):
        """Return the form which is the intersection of two forms."""
        if form1 == None:
            return form2
        if form2 == None:
            return form1
        if form1 == form2 == None:
            return None
        points = form1.crossForm(form2)
        if not points:
            return None
        for point in form1.points:
            if point in form2:
                points.append(point)
        for point in form2.points:
            if point in form1:
                points.append(point)
        form = cls(points)
        form.makeSparse()
        return form

    @classmethod
    def intersection(cls, forms):
        """Return the form which is the intersection of all the forms."""
        result = forms[0]
        for form in forms[1:]:
            result = cls.intersectionTwoForms(result, form)
        return result

    @classmethod
    def unionTwoForms(cls, form1, form2):
        """Return the union of two forms."""
        intersection_points = set(form1.crossForm(form2))
        if intersection_points:
            all_points = set(form1.points + form2.points)
            points = all_points.intersection(intersection_points)
            return [cls(points)]
        else:
            return [form1, form2]

    @classmethod
    def union(cls, forms):
        """Return the union of all forms."""
        """This function must be recursive."""
        if len(forms) == 2:
            return cls.unionTwoForms(forms[0], forms[1])
        else:
            pass

        result = forms[0]
        for form in forms[1:]:
            result.extend(cls.union(form, result))
        return result

    @classmethod
    def createFromTuples(cls, tps, conversion=True, radius=0.01, **kwargs):
        """Create a form from the tuples 'tps' and some optional arguments."""
        pts = [Point(*t, conversion=conversion, radius=0.01) for t in tps]
        return cls(pts, **kwargs)

    def __init__(
        self,
        points,
        fill=False,
        point_mode=0,
        point_size=[0.01, 0.01],
        point_radius=0.01,
        point_width=1,
        point_fill=False,
        side_width=1,
        color=None,
        point_color=colors.WHITE,
        side_color=colors.WHITE,
        area_color=colors.WHITE,
        cross_point_color=colors.WHITE,
        cross_point_radius=0.01,
        cross_point_mode=0,
        cross_point_width=1,
        cross_point_size=[0.1, 0.1],
        point_show=True,
        side_show=True,
        area_show=False,
    ):
        """Create the form object using points."""
        self.points = points

        self.point_mode = point_mode
        self.point_size = point_size
        self.point_width = point_width
        self.point_radius = point_radius
        self.point_color = point_color or color
        self.point_show = point_show
        self.point_fill = point_fill

        self.side_width = side_width
        self.side_color = side_color or color
        self.side_show = side_show

        self.area_color = area_color or color
        self.area_show = area_show or fill

        self.cross_point_color = cross_point_color
        self.cross_point_radius = cross_point_radius
        self.cross_point_mode = cross_point_mode
        self.cross_point_width = cross_point_width
        self.cross_point_size = cross_point_size

    def __str__(self):
        """Return the string representation of the form."""
        return "f(" + ",".join([str(p) for p in self.points]) + ")"

    def setFill(self, fill):
        """Set the form to fill its area when shown."""
        self.area_show = fill

    def getFill(self):
        """Return if the area is filled."""
        return self.area_show

    fill = property(
        getFill,
        setFill,
        doc="Allow the user to manipulate easily if the area is filled.",
    )

    def __iadd__(self, point):
        """Add a point to the form."""
        self.points.append(point)
        return self

    def __isub__(self, point):
        """Remove a point to the form."""
        self.points.remove(point)
        return self

    def __mul__(self, n):
        """Return a bigger form."""
        vectors = [n * Vector(*(p - self.center)) for p in self.points]
        return Form([vectors[i](self.points[i]) for i in range(len(self.points))])

    def __imul__(self, n):
        """Return a bigger form."""
        vectors = [n * Vector(*(p - self.center)) for p in self.points]
        self.points = [vectors[i](self.points[i]) for i in range(len(self.points))]
        return self

    __rmul__ = __mul__

    def __iter__(self):
        """Iterate the points of the form."""
        self.iterator = 0
        return self

    def __next__(self):
        """Return the next point threw an iteration."""
        if self.iterator < len(self.points):
            iterator = self.iterator
            self.iterator += 1
            return self.points[iterator]
        else:
            raise StopIteration

    def __eq__(self, other):
        """Determine if 2 forms are the same which check the equalities of their components."""
        return sorted(self.points) == sorted(other.points)

    def getCenter(self):
        return Point.average(self.points)

    def setCenter(self, center):
        """Set the center of the form."""
        p = center - self.center
        for i in range(len(self.points)):
            self.points[i] += p

    def getCentroid(self):
        """Return the point of the center.
        This only works for 2 dimensional forms obviously."""
        if len(self.points) == 0:
            # None
            return None
        elif len(self.points) == 1:
            # Same point
            return self.points[0]
        elif len(self.points) == 2:
            # Middle of a segment
            return Segment(*self.points).middle
        elif len(self.points) == 3:
            # Intersection point of 2 medians
            return Point.average(self.points)
        else:
            # Geometric decomposition to compute centroids (wikipedia)
            n = len(self.points)  # n is the number of points
            # There are n-2 forms
            forms = [
                Form([self.points[0]] + self.points[i : i + 2]) for i in range(1, n - 1)
            ]
            # So n-2 centroids and areas, except if some of the points are one upon another, no area is null
            centroids = [form.center for form in forms]
            areas = [form.area for form in forms]
            # we compute the average centroid weighted by the areas
            weighted_centroid = Point.sum([a * c for (c, a) in zip(centroids, areas)])
            centroid = weighted_centroid / sum(areas)
            return centroid

    def setCentroid(self, center):
        """Set the center of the form."""
        p = center - self.centroid
        for i in range(len(self.points)):
            self.points[i] += p

    def recenter(self, point=(0, 0)):
        """Recenter a form using the new center point."""
        self.center = Point(*point)

    def enlarge(self, n=2):
        """Enlarge the form by a factor of n."""
        c = self.center
        for i in range(len(self.points)):
            v = Vector.createFromTwoPoints(c, self.points[i])
            self.points[i].set((n * v)(c))

    def spread(self, n=2):
        """Take away the form by a factor of n."""
        for point in self.points:
            point *= n

    def getSegments(self):
        """"Return the list of the form sides."""
        l = len(self.points)
        return [
            Segment(
                self.points[i % l],
                self.points[(i + 1) % l],
                color=self.side_color,
                width=self.side_width,
            )
            for i in range(l)
        ]

    def setSegments(self, segments):
        """Set the segments of the form by setting its points to new values."""
        for point, segment in zip(self.points, segments):
            point.set(segment.p1)

    def getVectors(self):
        """Return the list of the form vectors."""
        l = len(self.points)
        return [
            Vector.createFromTwoPoints(
                self.points[i % l],
                self.points[(i + 1) % l],
                color=self.side_color,
                width=self.side_width,
            )
            for i in range(l)
        ]

    def setVectors(self, vectors):
        """Return the list of the form vectors."""
        l = len(self.points)
        for point, vector in zip(self.points, vectors):
            point.set(vector.components)

    def showAll(self, surface, **kwargs):
        """Show the form using a window."""
        # ,window,point_color=None,side_color=None,area_color=None,side_width=None,point_radius=None,color=None,fill=None,point_show=None,side_show=None
        if not "point_show" in kwargs:
            kwargs["point_show"] = self.point_show
        if not "side_show" in kwargs:
            kwargs["side_show"] = self.side_show
        if not "area_show" in kwargs:
            kwargs["area_show"] = self.area_show
        if kwargs["area_show"]:
            self.showAllArea(surface, **kwargs)
        if kwargs["side_show"]:
            self.showAllSegments(surface, **kwargs)
        if kwargs["point_show"]:
            self.showAllPoints(surface, **kwargs)

    def showFast(self, surface, point=None, segment=None, area=None):
        """Show the form using the surface and optional objects to show."""
        if point:
            self.showPoints(surface)
        if segment:
            self.showSegments(surface)
        if area:
            self.showArea(surface)

    def show(self, surface):
        """Show the form using the surface and optional objects to show."""
        if self.area_show:
            self.showArea(surface)
        if self.side_show:
            self.showSegments(surface)
        if self.point_show:
            self.showPoints(surface)

    def showFastArea(self, surface, color=None):
        """Show the area of the form using optional parameters such as the area
        of the color."""
        if not color:
            color = self.area_color
        ps = [tuple(p) for p in self.points]
        if len(ps) > 1:
            surface.draw.polygon(surface.screen, color, ps, False)

    def showAllArea(self, surface, **kwargs):
        """Show the area of the form using optional parameters such as the area
        of the color. This function is slower than the previous one because it
        checks if the dictionary or attributes contains the area_color."""
        if not "area_color" in kwargs:
            kwargs["area_color"] = self.area_color
        ps = [tuple(p) for p in self.points]
        if len(ps) > 1:
            surface.draw.polygon(surface.screen, kwargs["area_color"], ps, False)

    def showArea(self, surface):
        """Show the area of the form."""
        ps = [tuple(p) for p in self.points]
        if len(ps) > 1:
            surface.draw.polygon(surface.screen, self.area_color, ps, False)

    def showPoints(self, surface):
        """Show the points."""
        for point in self.points:
            point.show(surface)

    def showFastPoints(
        self,
        surface,
        color=None,
        mode=None,
        radius=None,
        size=None,
        width=None,
        fill=None,
    ):
        """Show the points of the form using optional parameters."""
        if color is None:
            color = self.point_color
        if radius is None:
            radius = self.point_radius
        if mode is None:
            mode = self.point_mode
        if size is None:
            size = self.point_size
        if width is None:
            width = self.point_width
        if fill is None:
            fill = self.point_fill
        for point in self.points:
            point.show(surface, color, mode, fill, radius, size, width)

    def showAllPoints(self, surface, **kwargs):
        """Show the points of the form using optional parameters.
        This method is slower than the previous one because it checks if the
        dictionary of attributes contains the arguments."""
        if not "point_color" in kwargs:
            kwargs["point_color"] = self.point_color
        if not "point_radius" in kwargs:
            kwargs["point_radius"] = self.point_radius
        if not "point_mode" in kwargs:
            kwargs["point_mode"] = self.point_mode
        if not "point_size" in kwargs:
            kwargs["point_size"] = self.point_size
        if not "point_width" in kwargs:
            kwargs["point_width"] = self.point_width
        if not "point_fill" in kwargs:
            kwargs["point_fill"] = self.point_fill
        for point in self.points:
            point.show(
                surface,
                color=kwargs["point_color"],
                mode=kwargs["point_mode"],
                fill=kwargs["point_fill"],
                radius=kwargs["point_radius"],
                size=kwargs["point_size"],
                width=kwargs["point_width"],
            )

    @timer
    def showFastSegments(self, context, color=None, width=None):
        """Show the segments of the form."""
        if not color:
            color = self.segment_color
        if not width:
            width = self.segment_width
        for segment in self.segments:
            segment.show(context, color, width)

    def showSegments(self, surface):
        """Show the segments without its parameters."""
        for segment in self.segments:
            segment.show(surface)

    def showAllSegments(self, surface, **kwargs):
        """Show the segments of the form."""
        if not "side_color" in kwargs:
            kwargs["side_color"] = self.side_color
        if not "side_width" in kwargs:
            kwargs["side_width"] = self.side_width
        for segment in self.segments:
            segment.show(
                surface, color=kwargs["side_color"], width=kwargs["side_width"]
            )

    def showFastCrossPoints(
        self, surface, color=None, mode=None, radius=None, width=None, size=None
    ):
        """Show the intersection points of the form crossing itself."""
        points = self.crossSelf()
        if not color:
            color = self.cross_point_color
        if not mode:
            mode = self.cross_point_mode
        if not radius:
            radius = self.cross_point_radius
        if not width:
            width = self.cross_point_width
        if not size:
            size = self.cross_point_size
        for point in points:
            point.show(
                surface, color=color, mode=mode, radius=radius, width=width, size=size
            )

    def showCrossPoints(self, surface):
        """Show the intersection points of the form crossing itself."""
        for point in self.cross_points:
            point.show(surface)

    def cross(self, other):
        """Return the points of intersections with the form and another object."""
        if isinstance(other, Form):
            return self.crossForm(other)
        elif isinstance(other, Segment):
            return self.crossSegment(other)
        elif isinstance(other, Line):
            return self.crossLine(other)
        elif isinstance(other, HalfLine):
            return self.crossHalfLine(other)
        else:
            raise TypeError(
                "The collisions {}|{} are not dealt with.".format(
                    type(self), type(other)
                )
            )

    __or__ = cross

    def crossForm(self, other):
        """Return the bool: (2 sides are crossing)."""
        points = []
        for s1 in self.sides:
            for s2 in other.sides:
                point = s1.crossSegment(s2)
                if point:
                    points.append(point)
        return points

    def crossDirection(self, other):
        """Return the list of the points of intersection between the form and a segment or a line."""
        points = []
        for side in self.sides:
            cross = side | other
            if cross:
                points.append(cross)
        return points

    def crossHalfLine(self, other):
        """Return the list of points of intersection in order between the form and a half line."""
        points = []
        for segment in self.segments:
            cross = other.crossSegment(segment)
            if cross:
                points.append(cross)
        hp = other.point
        objects = [(p, Point.distance(p, hp)) for p in points]
        objects = sorted(objects, key=lambda x: x[1])
        return [p for (p, v) in objects]

    def crossLine(self, other):
        """Return the list of the points of intersection between the form and a line."""
        points = []
        for segment in self.segments:
            cross = segment.crossLine(other)
            if cross:
                points.append(cross)
        return points

    def crossSegment(self, other):
        """Return the list of the points of intersection between the form and a segment."""
        points = []
        for side in self.sides:
            point = side.crossSegment(other)
            if point:
                points.append(point)
        return points

    def crossSelf(self, e=1e-10):
        """Return the list of the points of intersections between the form and itself."""
        results = []
        l = len(self.segments)
        for i in range(l):
            for j in range(i + 1, l):
                point = self.segments[i].crossSegment(self.segments[j])
                if point:
                    if point in self.points:
                        results.append(point)
        return results

    def convex(self):
        """Return the bool (the form is convex)."""
        x, y = self.center
        angles = []
        l = len(self.points)
        for i in range(l - 1):
            A = self.points[(i + l - 1) % l]
            B = self.points[i % l]
            C = self.points[(i + 1) % l]
            u = Vector.createFromTwoPoints(A, B)
            v = Vector.createFromTwoPoints(C, B)
            angle = v ^ u
            if angle > pi:
                return True
        return False

    def getSparse(
        self,
    ):  # as opposed to makeSparse which keeps the same form and return nothing
        """Return the form with the most sparse points."""
        return copy.deepcopy(self.makeSparse())

    def makeSparse(self):
        """Change the form into the one with the most sparse points."""
        center = self.center
        l = []
        for point in self.points:
            angle = Vector.createFromTwoPoints(point, center).angle
            l.append((angle, copy.deepcopy(point)))
        l = sorted(l, key=lambda x: x[0])
        for i in range(len(l)):
            self.points[i].set(l[i][1])

    def __contains__(self, point):
        """Return the boolean: (the point is in the form)."""
        h = HalfLine(point, 0)
        ps = self.crossHalfLine(h)
        return len(ps) % 2 == 1

    def rotate(self, angle, point=None):
        """Rotate the form by rotating its points from the center of rotation.
        Use center of the shape as default center of rotation."""
        # Actually not working
        if not point:
            point = self.center
        for i in range(len(self.points)):
            self.points[i].rotate(angle, point)

    def move(self, step):
        """Move the object by moving all its points using step."""
        for point in self.points:
            l = min(len(step), len(point.position))
            for i in range(l):
                point.position[i] = step[i]

    def addPoint(self, point):
        """Add a point to the form."""
        self.points.append(point)

    def addPoints(self, points):
        """Add points to the form."""
        self.points.extend(points)

    append = addPoint
    extend = addPoints

    def removePoint(self, point):
        """Remove a point to the form."""
        self.points.remove(point)

    __remove__ = removePoint

    def __getitem__(self, index):
        """Return the point of index index."""
        return self.points[index]

    def __setitem__(self, index, value):
        """Change the points of a form."""
        self.points[index] = value

    @property
    def perimeter(self):
        """Return the perimeter of the form."""
        return sum([s.length for s in self.segments])

    @property  # This can only be a getter
    def area(self):
        """Return the area of the form using its own points.
        General case in 2d only for now..."""
        l = len(self.points)
        if (
            l < 3
        ):  # The form has no point, is a single point or a segment, so it has no area.
            return 0
        elif l == 3:  # The form is a triangle, so we can calculate its area.
            a, b, c = [Vector.createFromSegment(segment) for segment in self.sides]
            A = (
                1
                / 4
                * sqrt(
                    4 * a.norm ** 2 * b.norm ** 2
                    - (a.norm ** 2 + b.norm ** 2 - c.norm ** 2) ** 2
                )
            )
            return A
        else:  # The form has more points than 3, so we can cut it in triangles.
            area = 0
            C = self.center
            for i in range(l):
                A = self.points[i]
                B = self.points[(i + 1) % l]
                triangle = Form([A, B, C])
                area += triangle.area
            return area

    @property
    def angles(self):
        ags = []
        vectors = self.vectors
        ag = (-vectors[-1]).angle
        for v in vectors:
            ang = v.angle - ag
            ag = (-v).angle
            ags.append(ang)
        return ags

    @property
    def abs_angles(self):
        ags = []
        vectors = self.vectors
        ag = (-vectors[-1]).angle
        for v in vectors:
            dag = abs(v.angle - ag)
            ang = min(dag, 2 * math.pi - dag)
            ag = (-v).angle
            ags.append(ang)
        return ags

    def getCircleDiameter(self):
        """Return the circle which diameter is the largest segment of the form.."""
        segments = []
        for (i, p1) in enumerate(self.points):
            for p2 in self.points[i + 1 :]:
                segments.append(Segment(p1, p2))
        s = max(segments, key=lambda s: s.length)
        return Circle(*s.middle, radius=s.length / 2)

    def getBornCircle(self):
        distances = []
        for p1 in self.points:
            distance = sum([Point.distance(p1, p2) for p2 in self.points])
            distances.append((distance, p1))
        pt1 = max(distances)[1]
        pt2 = Point.farthest(pt1, self.points)
        distances = []
        for p in self.points:
            if not p in [pt1, pt2]:
                distance = max(Point.distance(p, pt1), Point.distance(p, pt2))
                distances.append((distance, p))
        pt3 = max(distances)[1]
        triangle = Form([pt1, pt2, pt3])
        return triangle.getBornCircleTriangle()

    def getBornCircle2(self):
        if len(self.points) == 3:
            return self.getBornCircleTriangle()
        else:
            circles = []
            for (p1, p2, p3) in itertools.combinations(self.points, 3):
                form = Form([p1, p2, p3])
                circle = form.getBornCircle2()
                circles.append(circle)
            circles.sort(key=lambda circle: circle.radius, reverse=True)
            return circles[0]

    def getBornCircle3(self):
        triangles = self.getSubForms(3)
        f = max(triangles, key=lambda triangle: triangle.perimeter)
        # f = max(triangles, key=lambda triangle: triangle.area)
        if f.obtusangle:
            return f.getCircleDiameter(), f
        else:
            return f.getCircleCircumscribed(), f

    def getBornCircleSlow(self, e=1e-3):
        for (p1, p2) in self.getSubForms(2):
            segment = Segment(p1, p2)
            l = segment.length / 2
            c = segment.middle
            condition = True
            for p in self.points:
                if Point.distance(p, c) + e > l:
                    condition = False
                    break
            if condition:
                return Circle(*c, radius=l)

        for triangle in self.getSubForms(3):
            circle = triangle.getCircleCircumscribed()
            c = circle.center
            l = circle.radius
            condition = True
            for p in self.points:
                if Point.distance(p, c) + e > l:
                    condition = False
                    break
            if condition:
                print(circle)
                return circle

            print("not found")
            circle.border_color = colors.YELLOW
            return circle

    def getSubForms(self, n):
        return list(map(Form, itertools.combinations(self.points, n)))

    @property
    def obtusangle(self):
        for angle in self.abs_angles:
            if 2 * angle >= math.pi:
                return True
        return False

    @property
    def acutangle(self):
        for angle in self.abs_angles:
            if 2 * angle <= math.pi:
                return True
        return False

    def getCircleCircumscribed(self):
        """Return the circumbscribed circle of a triangle."""
        p1, p2, p3 = self.points
        a1 = -(p2.x - p1.x) / (p2.y - p1.y)
        b1 = (p2.x ** 2 - p1.x ** 2 + p2.y ** 2 - p1.y ** 2) / (2 * (p2.y - p1.y))
        a2 = -(p3.x - p2.x) / (p3.y - p2.y)
        b2 = (p3.x ** 2 - p2.x ** 2 + p3.y ** 2 - p2.y ** 2) / (2 * (p3.y - p2.y))
        x = (b1 - b2) / (a2 - a1)
        y = a1 * x + b1
        radius = math.hypot(p1.x - x, p1.y - y)
        return Circle(x, y, radius=radius)

    def __len__(self):
        """Return number of points."""
        return len(self.points)

    def __xor__(self, other):
        """Return the list of forms that are in the union of 2 forms."""
        if type(other) == Form:
            other = [other]
        return Form.union([other, self])

    def __and__(self, other):
        """Return the list of forms that are in the intersection of 2 forms."""
        points = self.crossForm(other)
        points += [point for point in self.points if point in other]
        points += [point for point in other.points if point in self]
        if points:
            return Form(points)

    # Color
    def setColor(self, color):
        """Color the whole form with a new color."""
        self.point_color = color
        self.side_color = color
        self.area_color = color

    def getColor(self):
        """Return the color of the segments because it is the more widely used."""
        return self.side_color

    def setPointColor(self, color):
        """Set the color of the points of the form."""
        for point in self.points:
            point.color = color

    def getPointColor(self):
        """Return the common color of the points."""
        l = [point.color for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The colors of the points must be the same otherwise it makes no sense."
            )

    def setPointMode(self, mode):
        """Set the mode of the points."""
        for point in self.points:
            point.mode = mode

    def getPointMode(self):
        """Return the common mode of the points."""
        l = [point.mode for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The modes of the points must be the same otherwise it makes no sense."
            )

    def setPointFill(self, fill):
        """Set the fill of the points."""
        for point in self.points:
            point.fill = fill

    def getPointFill(self):
        """Return the common fill attribute of the points."""
        l = [point.fill for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The fill attributes of the points must be the same otherwise it makes no sense."
            )

    def setPointRadius(self, radius):
        """Set the radius of the points."""
        for point in self.points:
            point.radius = radius

    def getPointRadius(self):
        """Return the common radius of the points."""
        l = [point.radius for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The radiuses of the points must be the same otherwise it makes no sense."
            )

    def setPointSize(self, size):
        """Set the size of the points."""
        for point in self.points:
            point.size = size

    def getPointSize(self):
        """Return the common size of the points."""
        l = [point.size for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The sizes of the points must be the same otherwise it makes no sense."
            )

    def setPointWidth(self, width):
        """Set teh width of the points."""
        for point in self.points:
            point.width = width

    def getPointWidth(self):
        """Return the common width of the points."""
        l = [point.width for point in self.points]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The widths of the points must be the same otherwise it makes no sense."
            )

    def setSegmentColor(self, color):
        """Set teh color of the segments."""
        for segment in self.segments:
            segment.color = color

    def getSegmentColor(self):
        """Return the common color of the segments."""
        l = [segment.color for segment in self.segments]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The colors of the points must be the same otherwise it makes no sense."
            )

    def setSegmentWidth(self, width):
        """Set the width of the segments."""
        for segment in self.segments:
            segment.width = width

    def getSegmentWidth(self):
        """Return the common width of the segments."""
        l = [segment.width for segment in self.segments]
        if l.count(l[0]) == len(l):
            return l[0]
        else:
            raise ValueError(
                "The widths of the segments must be the same otherwise it makes no sense."
            )

    getCrossPoints = crossSelf

    sides = segments = property(
        getSegments, setSegments, doc="Represents the segments."
    )
    vectors = property(getVectors, setVectors)
    center = property(getCenter, setCenter, doc="Center of the form.")
    centroid = property(getCentroid, setCentroid, doc="Centroid of the form.")
    color = property(getColor, setColor, doc="Segment color.")
    cross_points = property(
        getCrossPoints, doc="Points of intersection of the sides of the form itself."
    )
    point_color = property(
        getPointColor, setPointColor, doc="Set the color of the points."
    )
    point_mode = property(getPointMode, setPointMode, doc="Mode of the points.")
    point_fill = property(
        getPointFill,
        setPointFill,
        doc="Fill attribute of the circles representing the points.",
    )
    point_radius = property(
        getPointRadius,
        setPointRadius,
        doc="Radius of the circles representing the point.",
    )
    point_size = property(
        getPointSize, setPointSize, doc="Size of the cross that represents the point."
    )
    point_width = property(
        getPointWidth,
        setPointWidth,
        doc="Width of the cross that represents the point.",
    )
    segment_color = property(
        getSegmentColor, setSegmentColor, doc="Color of the segments."
    )
    segment_width = property(
        getSegmentWidth, setSegmentWidth, doc="Width of the segments."
    )


class Circle:
    @classmethod
    def random(cls, borns=[-1, 1], radius_borns=[0, 1], **kwargs):
        """Create a random circle."""
        x = random.uniform(*borns)
        y = random.uniform(*borns)
        r = random.uniform(*radius_borns)
        return cls(x, y, radius=r, **kwargs)

    @classmethod
    def createFromPointAndRadius(cls, point, radius, **kwargs):
        """Create a circle from point."""
        return cls(*point, radius=radius, **kwargs)

    def __init__(
        self,
        *args,
        radius,
        fill=False,
        color=colors.WHITE,
        border_color=None,
        area_color=None,
        center_color=None,
        radius_color=None,
        radius_width=1,
        text_color=None,
        text_size=20
    ):
        """Create a circle object using x, y and radius and optional color and width."""
        if len(args) == 1:
            args = args[0]
        self.position = args
        self.radius = radius
        self.fill = fill
        if color:
            if not border_color:
                border_color = color
            if not area_color:
                area_color = color
            if not radius_color:
                radius_color = color
            if not text_color:
                text_color = color
        self.border_color = border_color
        self.area_color = area_color
        self.center_color = center_color
        self.radius_color = radius_color
        self.radius_width = radius_width
        self.text_color = text_color
        self.text_size = text_size

    def __str__(self):
        """Str representation of a circle."""
        return "c(pos:" + str(self.position) + ",rad:" + str(self.radius) + ")"

    def __contains__(self, position):
        """Determine if a point is in a circle."""
        return (
            sum([(c1 - c2) ** 2 for (c1, c2) in zip(self.position, position)])
            <= self.radius
        )

    def getX(self):
        """Return the x component of the circle."""
        return self.position[0]

    def setX(self, value):
        """Set the x component of the circle."""
        self.position[0] = value

    def getY(self):
        """Return the y component of the circle."""
        return self.position[1]

    def setY(self, value):
        """Set the y component of the circle."""
        self.position[1] = value

    def getPoint(self):
        """Return the point that correspond to the center of the circle."""
        return Point(*self.position)

    def setPoint(self, point):
        """Set the center point of the circle by changing the position of the circle."""
        self.position = point.position

    def getR(self):
        """Return the radius."""
        return self.radius

    def setR(self, radius):
        """Set the radius to the given radius."""
        self.radius = radius

    x = property(getX, setX, "Allow the user to manipulate the x component easily.")
    y = property(getY, setY, "Allow the user to manipulate the y component easily.")
    r = property(getR, setR, "Abbreviation of the radius")
    center = point = property(
        getPoint, setPoint, "Allow the user to manipulate the point easily."
    )

    def show(self, window, color=None, border_color=None, area_color=None, fill=None):
        """Show the circle on screen using the window."""
        if color:
            if not area_color:
                area_color = color
            if not border_color:
                border_color = color
        if not border_color:
            border_color = self.border_color
        if not area_color:
            area_color = self.area_color
        if not fill:
            fill = self.fill
        window.draw.circle(
            window.screen, border_color, self.position, self.radius, fill
        )

    def showCenter(self, window, color=None, mode=None):
        """Show the center of the screen."""
        if not color:
            color = self.center_color
        if not mode:
            mode = self.center_mode
        self.center.show(window, mode=mode, color=color)

    def showText(self, window, text, color=None, size=None):
        """Show a text next to the circle."""
        if not color:
            color = self.text_color
        if not size:
            size = self.text_size
        self.center.showText(window, text, color=color, size=size)

    def showRadius(self, window, color=None, width=None):
        """Show the radius of the circle."""
        if not color:
            color = self.radius_color
        if not width:
            width = self.radius_width
        vector = Vector.createFromPolar(self.radius, 0, color=color)
        vector.show(window, self.center, width=width)
        vector.showText(surface, self.center, "radius", size=20)

    def __call__(self, n):
        """Return the main components of the circle."""
        perimeter = 2 * math.pi
        return Point(math.cos(n / perimeter), math.sin(n / perimeter))

    def isCrossingCircle(self, other):
        """Determine if two circles are crossing."""
        vector = Vector.createFromTwoPoints(self.center, other.center)
        return vector.norm < self.radius + other.radius

    def crossCircle(self, other):
        """Return the intersections points of two circles if crossing else
        return None."""
        if self.isCrossingCircle(other):
            s = Segment(self.center, other.center)
            m = s.middle
            n = math.sqrt(self.radius ** 2 - (s.norm / 2) ** 2)
            a = s.angle + math.pi / 2
            v1 = Vector.createFromPolar(n, a)
            v2 = Vector.createFromPolar(n, -a)
            p1 = v1(m)
            p2 = v2(m)
            return [p1, p2]

    def getArea(self):
        """Return the area of a circle using basic geometry."""
        return math.pi * self.radius ** 2

    area = property(getArea)


if __name__ == "__main__":
    from mycontext import Context

    surface = Context(name="Abstract Demonstration", fullscreen=True)

    p1 = Point(10, 0, radius=0.05, color=colors.YELLOW)
    p2 = Point(20, 20, radius=0.05, color=colors.YELLOW)
    # origin=Point(0,0)
    origin = Point.origin()

    l1 = HalfLine(origin, math.pi / 4)
    l2 = Line(p1, math.pi / 2, correct=False)
    s1 = Segment(p1, p2)
    print(Point.null())

    while surface.open:
        # Surface specific commands
        surface.check()
        surface.control()
        surface.clear()
        surface.show()

        # Actions
        l1.rotate(0.01, p2)
        l2.rotate(-0.02, p1)
        s1.rotate(0.03)

        p = l1 | l2

        o = Point(0, 0)
        p3 = l2.projectPoint(o)
        f = Form([p1, p2, p3], area_color=colors.RED, fill=True)

        # Show
        surface.draw.window.print("l1.angle: " + str(l1.angle), (10, 10))
        surface.draw.window.print("l2.angle: " + str(l2.angle), (10, 30))
        surface.draw.window.print("f.area: " + str(f.area), (10, 50))

        f.show(surface)
        f.center.show(surface)

        s1.show(surface)

        o.show(surface, color=colors.GREY)
        o.showText(surface, "origin")

        p3.showText(surface, "origin's projection")
        p3.show(surface, color=colors.LIGHTGREY)

        if p:
            p.show(surface, color=colors.RED)
            p.showText(surface, "intersection point", color=colors.RED)

        p1.show(surface)
        p1.showText(surface, "p1")

        p2.show(surface)
        p2.showText(surface, "p2")

        l1.show(surface, color=colors.GREEN)
        l1.point.show(surface, color=colors.LIGHTGREEN, mode="cross", width=3)
        l1.vector.show(surface, l1.point, color=colors.LIGHTGREEN, width=3)

        l2.show(surface, color=colors.BLUE)
        l2.point.show(surface, color=colors.LIGHTBLUE, mode="cross", width=3)
        l2.vector.show(surface, l2.point, color=colors.LIGHTBLUE, width=3)

        # Flipping the screen
        surface.flip()
