'''
Created on 25-Apr-2021

@author: bthakur
'''
import numpy as np
from sympy import pi
import random
from ._movement import Movement
from xv.km.kelements import (KPrintElement, KPrintHelper)

class Movements:
    _MAX_WALKS = 7
    _NAMES = 'ABCDEFGHIJKLMNPQRSTUVWXYZ'
    _COLORS = ['indigo', 'blue', 'green', 'red', 'cyan', 'magenta', 
                    'violet', 'black', 'orange', 'black', 'grey']  
    _MARKERS = ['o', 'v', '^', '<', '>', '1', '2', '3', '4', 
                     '8', 's', 'p', 'P', '*', 'h', 'H', 'X', 'D', 'd', ]
    @classmethod
    def random_movements(cls, size = None, isPolar = None):
        if size is None:
            size = random.randint(2, Movements._MAX_WALKS)
        mvs = Movements()
        while size > 0:
            mvs.append_random(isPolar)
            size = size - 1
            pass
        return mvs
        pass
    
    def __init__(self):
        self._movements = []
        self.lastAnnotationX = 0
        self.lastAnnotationY = 0
        pass
    
    def append(self, movement):
        self._movements.append(movement)
        pass

    def append_random(self, isPolar = None):
        if isPolar is None:
            isPolar = random.choice([True, False])
        
        if isPolar:
            self.append_random_polar()
        else:
            self.append_random_cartesian()
        pass

    def append_random_cartesian(self):
        while True:
            dx = random.randint(1, 10) * random.choice([-1, 0, 1])
            dy = random.randint(1, 10) * random.choice([-1, 0, 1])
            if dx == 0 and dy == 0:
                continue
        
            movement = Movement.cartesian(self.getLastMovement(), 
                                          dx, dy, 
                                          name = self._getNewName())
            self._movements.append(movement)
            return
        pass


    def append_random_polar(self):
        dr = random.randint(1, 10) 
        
        random_theta_factor = random.randint(1, 24)
        theta_m_str = random.choice([
                [0, 'to the East'],
                [1 * pi/4, 'to the North-East'],
                [2 * pi/4, 'to the North'],
                [3 * pi/4, 'to the North-West'],
                [4 * pi/4, 'to the West'],
                [5 * pi/4, 'to the South-West'],
                [6 * pi/4, 'to the South'],
                [7 * pi/4, 'to the South-East'],

                [random_theta_factor * pi/6, 
                 f'at angle of {random_theta_factor * 30} degrees'],
            ])
        dtheta, statement = theta_m_str
        statement = f"Go {dr} {statement}"
        movement = Movement.polar(self.getLastMovement(), 
                                  dr, dtheta, 
                                  statement = statement, 
                                  name = self._getNewName())
        self._movements.append(movement)
        pass
    
    def _getNewName(self):
        return Movements._NAMES[len(self._movements)] 
        pass

    def getLastMovement(self):
        if self._movements:
            return self._movements[-1]
        return None
    
    def get_xy_range(self, adj_factor = 0.1):
        xs = []
        ys = []
        for mv in self._movements:
            xs.append(mv.x2)
            ys.append(mv.y2)
            pass
        
        minx = min(xs)
        maxx = max(xs)
        miny = min(ys)
        maxy = max(ys)

        xRange = maxx - minx
        yRange = maxy - miny
        
        if xRange > yRange:
            rf = int((xRange - yRange) / 2) + 1
            miny = miny - rf
            maxy = maxy + rf
            pass
        elif xRange < yRange:
            rf = int((yRange - xRange) / 2) + 1
            minx = minx - rf
            maxx = maxx + rf
            pass
        
        range_adj = max(xRange, yRange) * adj_factor
        
        minx = int(minx - range_adj) - 1
        maxx = int(maxx + range_adj) + 1
        miny = int(miny - range_adj) - 1
        maxy = int(maxy + range_adj) + 1
        minmax = [minx, maxx, miny, maxy]
        return minmax
        pass


    def get_movements_code(self):
        markersize = 5
        custom_code = f"""
        from sympy import sympify, sin, cos, pi
        
        #set default x and y to origin
        x, y = (0, 0)
                    """
        for idx, mv in enumerate(self._movements):
            if idx < len(self._movements) - 1:
                deltaX3 = self._movements[idx+1].dx
                deltaY3 = self._movements[idx+1].dy
            else:
                deltaX3 = 0
                deltaY3 = 0

            minmax = self.get_xy_range()
            deltaX, deltaY = self._get_label_deltas(
                mv.x1, mv.y1, mv.x2, mv.y2, deltaX3, deltaY3, minmax)
            
            plot_x2_y2_code = mv.plot_x2_y2_code(color = f'{Movements._COLORS[idx]}')
            annonate_x2_y2_code = mv.annonate_x2_y2_code(deltaX, deltaY)

            custom_code = f"""{custom_code.strip()}

                # {mv.statement.strip()}
                {plot_x2_y2_code}
                        
                # annotate point
                {annonate_x2_y2_code}
                """

            if idx == 0:
                continue

            plot_line_code = mv.plot_line_code(color = f'{Movements._COLORS[idx]}')
            custom_code = f"""{custom_code.strip()}
                        
                        #plot line from the prev point to the current point
                        {plot_line_code}
                        """
            pass

        custom_code = f"""{custom_code.strip()}
        
        #plot lengths as legend
        plt.legend()
        
        #optional code
        plt.xlim({minmax[0]}, {minmax[1]})
        plt.ylim({minmax[2]}, {minmax[3]})

        #set uniform units for x and y axes 
        plt.gca().set_aspect('equal', adjustable='box')
        """
        
        return custom_code
        pass


    def _get_label_deltas(self, x1, y1, x2, y2, deltaX3, deltaY3, minmax):
        #minDeltaX = round(abs(minmax[1] - minmax[0]), 2) * .03
        #minDeltaY = round(abs(minmax[3] - minmax[2]), 2) * .03
        
        minDeltaX = round(abs(minmax[1] - minmax[0]) * .03, 2)
        minDeltaY = round(abs(minmax[3] - minmax[2]) * .03, 2)
        
        deltaX2 = x2 - x1
        deltaY2 = y2 - y1
        deltaY = 0
        #if lines are almost horizontal, we need deltaY
        if abs(deltaY2) < minDeltaY and abs(deltaY3) < minDeltaY:
            deltaY = minDeltaY + self.lastAnnotationY
        elif abs(deltaY2) < minDeltaY or abs(deltaY3) < minDeltaY:
            if abs(deltaY3) > 0:
                deltaY = -minDeltaY
            else:
                deltaY = minDeltaY
        else:
            deltaY = -round(minDeltaY/2, 2)
            pass
        
        deltaX = 0
        #if lines are almost vertical, we need deltaX
        if abs(deltaX2) < minDeltaX or abs(deltaX3) < minDeltaX:
            deltaX = minDeltaX
        else:
            deltaX = round(minDeltaX/2, 2)
            pass
        self.lastAnnotationX = deltaX
        self.lastAnnotationY = deltaY
        return deltaX, deltaY
        pass
    
    def printMovementLengths(self):
        if self._movements is None:
            print("No movement data found")
            return None
        
        content = ""
        for idx, mv in enumerate(self._movements):
            if idx == 0:
                continue

            length_latex = mv.get_length_latex_expr(prefix = '= ')
            point_html = KPrintElement(length_latex, 'text/html').print()
            content = f"""{content}
            <h3>{mv.move_label}</h3>{point_html}
            
            """
            pass
            
        kph = KPrintHelper()
        return kph.print(content)
    
        
    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n <= len(self._movements) - 1:
            result = self._movements[self.n]
            self.n = self.n + 1
            return result
        else:
            raise StopIteration    

    def __str__(self):
        s = f"movements = {len(self._movements)}"
        for mv in self._movements:
            s = s + "\n" + str(mv) 
            pass
        return s
        pass

    def __repr__(self):
        s = f"movements = {len(self._movements)}"
        for mv in self._movements:
            s = s + "\n" + str(mv) 
            pass
        return s
        pass
    
    pass