<filename>fourier_drawing/interpolation.py
"""
The goal of this program is to make an interpolation of the points of any
curve in order to make it a parametric function.


There are 2 main types of interpolations:
- Polynomial Interpolation:
- Bezier Interpolation:
"""

# Polynomial Interpolation
from .polynomial import Polynomial

# Pseudo Bezier Interpolation
from .curves import BezierCurve
from .abstract import Line, Point

# additional imports for showing the interpolations more easily
from .curves import Trajectory
from . import colors

# Deprectated function
def directInterpolation(points, t):
    x = [pt[0] for pt in points]
    y = [pt[1] for pt in points]
    plx = Polynomial.createFromInterpolation(range(len(x)), x)
    ply = Polynomial.createFromInterpolation(range(len(y)), y)
    l = len(pts)
    mst = 100

    # Creation of the points
    # pts=[(random.uniform(xmin,xmax),random.uniform(ymin,ymax)) for i in range(n)]

    # # """ Tests:
    # # Calculations for n points in dimension 2:
    #
    # x=[pt[0] for pt in pts]
    # y=[pt[1] for pt in pts]
    # plx=Polynomial.createFromInterpolation(range(len(x)),x)
    # ply=Polynomial.createFromInterpolation(range(len(y)),y)
    # print(plx)
    # print(ply)
    # l=len(pts)
    # mst=100
    # npts=[(plx(l*st/mst),ply(l*st/mst)) for st in range(mst)]
    # grapher=Grapher(context,functions=[plx,ply])
    # """


class Interpolation:
    """Base class of all interpolations."""

    def __init__(self, points, create=True):
        """Create the interpolation using a list of n-dimensional points."""
        self.points = points
        if create:
            self.create()

    def create(self):
        """Create the necessary attributes for the interpolation.
        This method is to be overloaded."""
        pass

    def sample(self, n):
        """Make a sample of the interpolation for n points."""
        m = n - 1
        return [self(t / m) for t in range(n)]

    def __str__(self):
        """Return the string representation of the interpolation."""
        return "Itp(" + ",".join(map(str, self.points)) + ")"


class ExtraInterpolation(Interpolation):
    """Extra features for interpolations."""

    def getSegments(self):
        """Return the segments formed by the points."""
        return [
            Segment(self.points[i], self.points[i + 1])
            for i in range(len(self.points) - 1)
        ]

    def getTrajectory(self):
        """Return the trajectory associated with the points of the interpolation."""
        return Trajectory.createFromTuples(self.points)

    def getPointsType(self):
        """Return the points under a point type instead of just a tuple type."""
        return [Point(*p) for p in self.points]

    def getPoint(self, t, **kwargs):
        """Return the point of position t in the parametric function."""
        return Point(*self(t), **kwargs)

    segments = property(getSegments)
    trajectory = property(getTrajectory)
    points_type = property(getPointsType)


class VisualInterpolation(Interpolation):
    """Visual features for interpolation, this class allows to show an
    interpolation on a graphical context."""

    def __init__(
        self,
        points,
        color=colors.RED,
        point_color=colors.LIGHTRED,
        trajectory_color=colors.GREEN,
        **kwargs
    ):
        """Create a visual interpolation."""
        self.color = color
        self.point_color = point_color
        self.trajectory_color = trajectory_color
        Interpolation.__init__(self, points, **kwargs)

    def show(self, context, n, color=None):
        """Show the polynomial interpolation by sampling the function using:
        -n: the number of points
        -context:the context in which the interpolation is shown."""
        if not color:
            color = self.color
        points = self.sample(n)
        context.draw.lines(context.screen, color, points, width=1, connected=False)

    def showTrajectory(self, context, color=None):
        """Show the trajectory defined by the points of the interpolation."""
        if not color:
            color = self.trajectory_color
        Trajectory.createFromTuples(self.points).show(context, color=color)


class PolynomialInterpolation(VisualInterpolation, ExtraInterpolation):
    """Make an interpolation using the lagrangian polynomial interpolator
    over list of n-dimensional points."""

    def create(self):
        """Create the components useful for the interpolation which are the
        split components and the lagrangian polynomials."""
        self.createComponents()
        self.createPolynomials()

    def createComponents(self):
        """Split its components of each point in separate lists."""
        self.components = [
            [pt[i] for pt in self.points] for i in range(len(self.points[0]))
        ]

    def createPolynomials(self):
        """Create its polynomials using its components."""
        lpts = len(self.points)
        self.polynomials = [
            Polynomial.createFromInterpolation(range(lpts), c) for c in self.components
        ]

    def __call__(self, t):
        """Evaluate the parametric interpolation for the input 't' between 0 and 1."""
        lcps = len(self.components)
        lpts = len(self.points) - 1
        return tuple([self.polynomials[i](lpts * t) for i in range(lcps)])


class PseudoBezierInterpolation(VisualInterpolation):
    """Make an interpolation using the bezier interpolation over a list of
    n-dimensional points."""

    def create(self):
        """Create the lines, the intruding points and the bezier curves."""
        self.createLines()
        self.createIntrudingPoints()
        self.createBezierCurves()

    def createLines(self):
        """Create the lines of the duos of consecutive points."""
        l = len(self.points)
        lines = [
            Line.createFromTwoPoints(self.points[i], self.points[i + 1])
            for i in range(l - 1)
        ]

    def createIntrudingPoints(self):
        """Create the points of intersection formed by some of the lines."""
        l = len(lines)
        intruding_points = [lines[i].crossLine(lines[i + 1]) for i in range(l - 1)]

    def __call__(self, t):
        """Evaluate the parametric interpolation for the input 't' between 0 and 1."""
        l = len(lines)
        intruding_points = [lines[i].crossLine(lines[i + 1]) for i in range(l - 1)]
        bezier_curves = []
        for i in range(l):
            bezier_points = [
                self.points[i],
                self.intruding_points[i],
                self.points[i + 1],
            ]
            b = BezierCurve(bezier_points)
            bezier_curves.append(b)


if __name__ == "__main__":
    # To show the points
    from mycontext import Context
    import colors

    # To create the points
    import random

    context = Context(name="Interpolation Demonstration")

    # Parameters
    h = 1
    w = 1
    xmin, xmax, ymin, ymax = [-w, w, -h, h]  # corners
    l = 5  # number of points
    n = 0  # number of steps
    m = 100  # max number of steps

    # Final version
    # pts=[Point.random() for i in range(10)]
    pts = [Point(2 * x, random.randint(-5, 5)) for x in range(l)]
    interpolation = PolynomialInterpolation([p.components for p in pts])
    interpolation.color
    npts = interpolation.sample(200)

    # Setting the console
    context.console.append(
        [
            "interpolation: " + str(interpolation),
            "trajectory: " + str(interpolation.trajectory),
        ]
    )

    # Main loop of the context
    while context.open:
        # Update the context
        context.check()
        context.control()
        context.clear()
        context.show()

        # Update data components
        Point.turnPoints([1 / 100 / l for i in range(l)], pts)
        # trajectory=Trajectory(pts,colors.GREEN)
        interpolation = PolynomialInterpolation([p.components for p in pts])
        npts = interpolation.sample(200)  # Sample 200 points by interpolation

        # Additional features
        n = (n + 1) % (m + 1)
        c1 = interpolation(n / m)
        pt1 = Point(*c1, radius=0.1, color=colors.lighten(colors.RED, 2), fill=True)
        c2 = interpolation.trajectory(n / m)
        pt2 = Point(*c2, radius=0.1, color=colors.lighten(colors.GREEN, 2), fill=True)

        # Show visual components
        pt1.show(context)
        pt2.show(context)
        interpolation.show(context, 200)
        interpolation.showTrajectory(context)

        # Console
        context.showConsole()

        # Flip the context
        context.flip()
