'''
Created on 25-Apr-2021

@author: bthakur
'''
import numpy as np
from sympy import pi, cos, sin, sqrt, UnevaluatedExpr

class Movement:
    @classmethod
    def polar(cls, prevMovement, dr, dtheta,
              name = None, label = None, statement = None,
              label_suffix = 'auto'):
        if dr == 0:
            raise Exception("Movement.polar: dr cannot be zero.")
            pass

        mv = Movement()
        mv.prevMovement = prevMovement
        mv.isPolar = True
        mv.dr = dr
        mv.dtheta = dtheta
        mv.name = name
        mv.label = label
        mv.statement = statement

        if mv.prevMovement is None:
            mv.x1 = 0
            mv.y1 = 0
            mv.move_label = mv.name
        else:
            mv.x1 = mv.prevMovement.x2
            mv.y1 = mv.prevMovement.y2
            mv.move_label = f"{mv.prevMovement.name}{mv.name}"

        mv.dx = round(mv.dr * cos(mv.dtheta), 2)
        mv.dy = round(mv.dr * sin(mv.dtheta), 2)

        mv.dx_expr = mv.dr * cos(UnevaluatedExpr(dtheta))
        mv.dy_expr = mv.dr * sin(UnevaluatedExpr(dtheta))

        mv.x2 = round(mv.x1 + mv.dx, 2)
        mv.y2 = round(mv.y1 + mv.dy, 2)

        mv.move_cal_str = f"({mv.x1} + {mv.dr} * cos({mv.dtheta}), {mv.y1} + {mv.dr} * sin({mv.dtheta})) = ({mv.x1} + {mv.dx}, {mv.y1} + {mv.dy}) = ({mv.x1 + mv.dx}, {mv.y1 + mv.dy})"


        if mv.name is None:
            mv.name = f"({mv.x2}, {mv.y2})"

        if mv.label is None:
            mv.label = f"{mv.name}({mv.x2}, {mv.y2})"
        elif label_suffix == 'auto':
            mv.label = f"{mv.label}({mv.x2}, {mv.y2})"
            pass

        if mv.statement is None:
            mv.statement = f"Go {mv.dr} at angle {mv.dtheta}"
            pass
        return mv
        pass

    @classmethod
    def cartesian(cls, prevMovement, dx, dy,
                  name = None, label = None, statement = None, label_suffix = 'auto'):
        if dx == 0 and dy == 0:
            raise Exception("Movement.cartesian: dx and dy both cannot be zero.")
            pass

        mv = Movement()
        mv.prevMovement = prevMovement
        mv.isPolar = False
        mv.dx = dx
        mv.dy = dy
        mv.name = name
        mv.label = label
        mv.statement = statement

        if mv.prevMovement is None:
            mv.x1 = 0
            mv.y1 = 0
            mv.move_label = mv.name
        else:
            mv.x1 = mv.prevMovement.x2
            mv.y1 = mv.prevMovement.y2
            mv.move_label = f"{mv.prevMovement.name}{mv.name}"

        mv.x2 = mv.x1 + mv.dx
        mv.y2 = mv.y1 + mv.dy

        mv.move_cal_str = f"({mv.x1} + {mv.dx}, {mv.y1} + {mv.dy}) = ({mv.x1 + mv.dx}, {mv.y1 + mv.dy})"

        mv.dtheta = np.arctan2(mv.dx, mv.dy)

        if mv.name is None:
            mv.name = f"({mv.x2}, {mv.y2})"

        if mv.label is None:
            mv.label = f"{mv.name}({mv.x2}, {mv.y2})"
        elif label_suffix == 'auto':
            mv.label = f"{mv.label}({mv.x2}, {mv.y2})"
            pass

        if mv.dx > 0:
            dirx = "right"
        else:
            dirx = "left"

        if mv.dy > 0:
            diry = "up"
        else:
            diry = "down"

        if mv.statement is None:
            if mv.dx != 0 and mv.dy != 0:
                mv.statement = f"Go {abs(mv.dx)} {dirx} and {abs(mv.dy)} {diry}"
                pass
            elif mv.dx != 0:
                mv.statement = f"Go {abs(mv.dx)} {dirx}"
                pass
            elif mv.dy != 0:
                mv.statement = f"Go {abs(mv.dy)} {diry}"
                pass
            pass

        mv.dx_expr = UnevaluatedExpr(mv.dx)
        mv.dy_expr = UnevaluatedExpr(mv.dy)

        return mv
        pass

    @classmethod
    def p1p2(cls, x1, y1, x2, y2, prevMovement = None,
                  name = None, label = None, statement = None, label_suffix = 'auto'):
        if x1 == x2 and y1 == y2:
            raise Exception("Both points cannot be same")
            pass

        mv = Movement()
        mv.prevMovement = prevMovement
        mv.isPolar = False

        mv.x1 = x1
        mv.y1 = y1

        mv.x2 = x2
        mv.y2 = y2

        mv.dx = mv.x2 - mv.x1
        mv.dy = mv.y2 - mv.y1

        mv.name = name
        mv.label = label
        mv.statement = statement

        if mv.prevMovement is None:
            mv.move_label = mv.name
        else:
            mv.move_label = f"{mv.prevMovement.name}{mv.name}"

        mv.move_cal_str = f"({mv.x1} + {mv.dx}, {mv.y1} + {mv.dy}) = ({mv.x1 + mv.dx}, {mv.y1 + mv.dy})"

        mv.dtheta = np.arctan2(mv.dx, mv.dy)

        if mv.name is None:
            mv.name = f"({mv.x2}, {mv.y2})"

        if mv.label is None:
            mv.label = f"{mv.name}({mv.x2}, {mv.y2})"
        elif label_suffix == 'auto':
            mv.label = f"{mv.label}({mv.x2}, {mv.y2})"
            pass

        if mv.dx > 0:
            dirx = "right"
        else:
            dirx = "left"

        if mv.dy > 0:
            diry = "up"
        else:
            diry = "down"

        if mv.statement is None:
            if mv.dx != 0 and mv.dy != 0:
                mv.statement = f"Go {abs(mv.dx)} {dirx} and {abs(mv.dy)} {diry}"
                pass
            elif mv.dx != 0:
                mv.statement = f"Go {abs(mv.dx)} {dirx}"
                pass
            elif mv.dy != 0:
                mv.statement = f"Go {abs(mv.dy)} {diry}"
                pass
            pass

        mv.dx_expr = UnevaluatedExpr(mv.dx)
        mv.dy_expr = UnevaluatedExpr(mv.dy)

        return mv
        pass

    def __init__(self):
        self.prevMovement = None
        self.isPolar = None

        self.x1 = None
        self.y1 = None
        self.x2 = None
        self.y2 = None

        self.dx = None
        self.dy = None
        self.dr = None

        self.dx_expr = None
        self.dy_expr = None


        self.dtheta = None
        self.name = None
        self.label = None
        self.move_label = None
        self.statement = None
        pass

    def get_length_expr(self):
        if self.x1 < 0:
            sX = f"{self.x2} + ({abs(self.x1)})"
        else:
            sX = f"{self.x2} - {self.x1}"

        if self.y1 < 0:
            sY = f"{self.y2} + ({abs(self.y1)})"
        else:
            sY = f"{self.y2} - {self.y1}"
        return f"sqrt( ({sX})**2 + ({sY})**2 )"
        pass

    def get_length(self):
        sum_sqr = (self.x2 - self.x1)**2 + (self.y2 - self.y1)**2
        sum_sqr_root = float(sqrt(sum_sqr))
        return round(sum_sqr_root, 2)

    def length_legend(self):
        length = self.get_length()
        if not self.prevMovement is None:
            legend = f"""plt.gca().legend('{self.move_label} = {length}', fontsize = 5)
                    """
        else:
            legend = ""

        return legend
        pass


    def plot_line_code(self, color = 'black'):
        code = f"""
        plt.plot((x, xprev), (y, yprev), color = '{color}', label = '{self.move_label} = {self.get_length()}')
        """
        return code.strip()
        pass


    def plot_x2_y2_code(self, color = 'black', markersize = 7):
        code = f"""
                # save current point as prev and calculate new point
                xprev, yprev = x, y
                x = x + sympify({self.dx_expr})  # {self.x2}
                y = y + sympify({self.dy_expr})  # {self.y2}
                plt.plot(x, y, marker = 'o', color = '{color}', markersize = {markersize})
        """
        return code.strip()
        pass

    def annonate_x2_y2_code(self, deltaX, deltaY):
        code = f"""
        plt.gca().annotate('{self.label})', xy=(x + {deltaX}, y + {deltaY}), xycoords='data', fontsize=7)
        """
        return code.strip()
        pass



    def get_length_latex_expr(self, prefix = None, suffix = None):
        step1 = self._get_length_latex_expr_step1(prefix, suffix)
        step2 = self._get_length_latex_expr_step2(prefix, suffix)
        step3 = self._get_length_latex_expr_step3(prefix, suffix)
        step4 = self._get_length_latex_expr_step4(prefix, suffix)
        step5 = self._get_length_latex_expr_step5(prefix, suffix)
        return f"""$${step1}$$ $${step2}$$ $${step3} $$ $${step4} $$ $${step5} $$"""
        pass

    def _get_length_latex_expr_step1(self, prefix = None, suffix = None):
        if self.x1 < 0:
            sX = f"{self.x2} - (- {abs(self.x1)})"
        else:
            sX = f"{self.x2} - {self.x1}"

        if self.y1 < 0:
            sY = f"{self.y2} - (- {abs(self.y1)})"
        else:
            sY = f"{self.y2} - {self.y1}"

        content = f"\sqrt{{ {{({sX})}}^2 + {{({sY})}}^2 }}"
        if not prefix is None:
            content = f"{prefix}{content}"

        if not suffix is None:
            content = f"{content}{suffix}"

        #return f"${content}$"
        return f"{content}"
        pass

    def _get_length_latex_expr_step2(self, prefix = None, suffix = None):
        sX = f"{self.x2 - self.x1}"
        sY = f"{self.y2 - self.y1}"

        content = f"\sqrt{{ {{({sX})}}^2 + {{({sY})}}^2 }}"
        if not prefix is None:
            content = f"{prefix}{content}"

        if not suffix is None:
            content = f"{content}{suffix}"

        #return f"${content}$"
        return f"{content}"
        pass

    def _get_length_latex_expr_step3(self, prefix = None, suffix = None):
        sX = f"{round((self.x2 - self.x1)**2, 2)}"
        sY = f"{round((self.y2 - self.y1)**2, 2)}"

        content = f"\sqrt{{ {sX} + {sY} }}"
        if not prefix is None:
            content = f"{prefix}{content}"

        if not suffix is None:
            content = f"{content}{suffix}"

        #return f"${content}$"
        return f"{content}"
        pass

    def _get_length_latex_expr_step4(self, prefix = None, suffix = None):
        content = f"\sqrt{{{round((self.x2 - self.x1)**2 + (self.y2 - self.y1)**2, 2)}}}"
        if not prefix is None:
            content = f"{prefix}{content}"

        if not suffix is None:
            content = f"{content}{suffix}"

        #return f"${content}$"
        return f"{content}"
        pass

    def _get_length_latex_expr_step5(self, prefix = None, suffix = None):
        length = self.get_length()

        content = f"{length}"
        if not prefix is None:
            content = f"{prefix}{content}"

        if not suffix is None:
            content = f"{content}{suffix}"

        #return f"${content}$"
        return f"{content}"
        pass



    def __str__(self):
        s = type(self).__name__
        s = s + "\n\t" + "isPolar:" + str(self.isPolar)
        if not self.prevMovement is None:
            s = s + "\n\t" + "previous move: True"

        s = s + "\n\t" + "move:" + f"({self.x1}, {self.y1})->({self.x2}, {self.y2})"
        s = s + "\n\t" + "dx:" + str(self.dx)
        s = s + "\n\t" + "dy:" + str(self.dy)
        s = s + "\n\t" + "dr:" + str(self.dr)
        s = s + "\n\t" + "dtheta:" + str(self.dtheta)
        s = s + "\n\t" + "name:" + str(self.name)
        s = s + "\n\t" + "label:" + str(self.label)
        s = s + "\n\t" + "statement:" + str(self.statement)

        s = s + "\n\t" + "dx_expr:" + str(self.dx_expr)
        s = s + "\n\t" + "dy_expr:" + str(self.dy_expr)

        return s
        pass

    pass