from .abstract import Point,Segment,Form,Line

import random
from . import colors
import itertools

class Trajectory:
    @classmethod
    def random(cls,n=5,**kwargs):
        """Create a random trajectory of n points."""
        points=[Point.random() for i in range(n)]
        return cls(points,**kwargs)

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

    def __init__(self,points,segment_color=colors.WHITE,point_color=colors.WHITE):
        """Create a trajectory using the list of points."""
        self.points=points
        self.segment_color=segment_color
        self.point_color=point_color

    def __call__(self,t):
        """Evaluate the trajectory by t between 0 and 1."""
        if len(self.points)<=1:
            raise Exception("It is impossible to interpolate without at least 2 points.")
        length=self.length
        ns=len(self.segments)
        tsl=0
        for i in range(ns):
            s=Segment(self.points[i],self.points[i+1])
            if t*length-tsl<=s.length:
                st=(t*length-tsl)/s.length
                return tuple(s(st).components)
            tsl+=s.length
        return tuple(s(1)) #Its a fix that isn't always true

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

    def show(self,surface,**kwargs):
        """Show the trajectory on the surface."""
        self.showPoints(surface,**kwargs)
        self.showSegments(surface,**kwargs)

    def showPoints(self,surface,color=None):
        """Show the point on the surface."""
        if not color: color=self.point_color
        for point in self.points:
            point.show(surface,color=color)

    def showSegments(self,surface,color=None):
        """Show the segments on the surface."""
        if not color: color=self.segment_color
        for segment in self.segments:
            segment.show(surface,color=color)

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

    def getLength(self):
        """Return the length of the trajectory."""
        return sum([segment.length for segment in self.segments])

    def sample(self,n,include=True):
        """Sample n points of the trajectory at equal distance.
        It is also possible to include the last one if wanted."""
        return [self(i/n) for i in range(n+int(include))]

    def fastSample(self,n,include=True):
        """Sample n points of the trajectory at equal distance but fast.
        It is also possible to include the last one if wanted."""
        length=self.length
        ns=len(self.segments)
        k=0
        l=[]
        tsl=0
        for i in range(ns):
            s=Segment(self.points[i],self.points[i+1])
            if t*length-tsl<=s.length:
                st=(t*length-tsl)/s.length
                l.append(tuple(s(st).components))
                k+=1
                t=k/n
            tsl+=s.length
        if include:
            l.append(self.points[-1])
        return l

    def sampleSegments(self,n,include=True):
        """Sample n points for each segment of the trajectory."""
        points=[]
        segments=self.segments
        for i in range(len(segments)):
            points+=segments[i].sample(n,include=False)
        if include and len(self.points)>1: points.append(self.points[-1])
        return points

    def getCenter(self):
        """Return the center (or middle) of the trajectory."""
        return self(1/2)

    def setCenter(self):
        """Set the center (or middle) of the trajectory."""
        center=self.center
        for n in range(len(self.points)):
            self.points[n]-=center

    segments=property(getSegments)
    length=property(getLength)
    center=property(getCenter)

class BezierCurve:
    def __init__(self,points,point_color=colors.WHITE,segment_color=colors.WHITE):
        """Create a bezier curve object using 3 points."""
        self.points=points
        self.segment_color=segment_color
        self.point_color=point_color

    def __call__(self,t):
        """Return the point."""
        points=self.points
        if len(points)==0:
            return None
        elif len(points)==1:
            return points[0]
        elif len(points)==2:
            segment=Segment(points[0],points[1])
            point=segment.center()
            return point
        elif len(points)==3:
            p1,p2,p3=points
            s1=Segment(p1,p2)
            s2=Segment(p2,p3)
            ps1=s1(t)
            ps2=s2(t)
            s=Segment(ps1,ps2)
            return s(t)
        else:
            while len(points)>3:
                segments=[Segment(points[i],points[i+1]) for i in range(len(points)-1)]
                points=[segment(t) for segment in segments]
            b=BezierCurve(points)
            return b(t)

    def show(self,surface,p=50):
        """Show the bezier curve on the surface."""
        points=[self(i/p) for i in range(p+1)]
        segments=[Segment(points[i],points[i+1]) for i in range(p)]
        self.showPoints(surface,points)
        self.showSegments(surface,segments)

    def getConstruction(self,t):
        """Return the construction segments of the form."""
        points=self.points
        construction=[]
        while len(points)>=2:
            segments=[Segment(points[i],points[i+1]) for i in range(len(points)-1)]
            points=[segment(t) for segment in segments]
            construction.append(segments)
        return construction

    def showConstruction(self,surface,t):
        """Show the construction of the form at the t position."""
        construction=self.getConstruction(t)
        l=len(construction)
        for i in range(l):
            k=255*(i+1)/l
            color=(0,0,k)
            for segment in construction[i]:
                #print(segment)
                segment.show(surface,color=color,width=2)

    def showPoints(self,surface,points,color=None):
        """Show the points on the surface."""
        if not color: color=self.point_color
        for point in points:
            point.show(surface,color=color,radius=0.01)

    def showSegments(self,surface,segments,color=None):
        """Show the segments on the surface."""
        if not color: color=self.segment_color
        for segment in segments:
            segment.show(surface,color=color,width=2)

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

    segments=property(getSegments)

class BezierForm(Form):
    def show(self,context):
        """Show the curved form on the screen."""
        self.bezier.show(context)

    @property
    def bezier(self):
        """Return the bezier curve of the form."""
        return BezierCurve(self.points+[self.points[0]])



class Arrow(BezierCurve):
    """Join 2 objects with an arrow."""
    def __init__(self,points,point_color=colors.WHITE,segment_color=colors.WHITE,vector_color=colors.WHITE):
        super().__init__(self,points,point_color,segment_color,vector_color)

    def show(self,surface,p=50):
        """Show the arrow on the screen."""
        points=[self(i/p) for i in range(p)] #p=p+1 ?
        segments=[Segment(points[i],points[i+1]) for i in range(p-1)]
        vectors=[Vector(points[i],points[i+1]) for i in range(0,p-1,5)]
        self.showPoints(surface,points)
        self.showSegments(surface,segments)
        self.showVectors(surface,vectors)

    def showVectors(self,surface,vectors,color=None):
        """Show the vectors on the surface."""
        if not color: color=self.vector_color
        for vector in vectors:
            vector.show(surface,color=color)




if __name__=="__main__":
    from mycontext import Surface
    surface=Surface(name="Curves demonstration")
    l=10
    points=[Point(2*x,random.randint(-5,5)) for x in range(l)]
    t=Trajectory(points,segment_color=colors.GREEN)
    b=BezierCurve(points,segment_color=colors.RED)
    #st=t.sampleSegments(3)
    #t1=Trajectory(st,segment_color=colors.BLUE)
    #print(b.points[-1])
    #print(b(1))
    #print(b.segments[-1])
    #c=CurvedForm(points)
    n=0
    ncp=50 #number construction points

    while surface.open:
        surface.check()
        surface.control()
        surface.clear()
        surface.show()

        Point.turnPoints([1/1000 for i in range(l)],points)
        n=(n+1)%(ncp+1)
        b.showConstruction(surface,n/ncp)

        t.show(surface)
        b.show(surface)

        p1=b(n/ncp)
        p2=Point(*t(n/ncp))

        #l1=Line.createFromTwoPoints(p1,p2)

        p1.show(surface,color=colors.YELLOW,radius=0.1,fill=True)
        p2.show(surface,color=colors.YELLOW,radius=0.1,fill=True)
        #l1.show(surface,color=colors.LIGHTGREY)


        surface.flip()
