import math

from manim import Dot, Axes, MathTex, Scene, Create

import manim as mm
from manim.typing import Vector3DLike

from .construction import (DotConstruction, LineConstruction,
                           style, LabelConstruction)
from euclid_geometry.line import Line
from euclid_geometry.point import Point

TOP_LAYER = 100

class IoAdapter:
    r"""
    output the calculated euclidian objects in a Manim ``Axes``
    """

    def __init__(self, axes):
        self._grid = axes

    @staticmethod
    def make_axis(x_range, y_range, length_ratio=1, add_coordinates=False, tips=False)-> Axes:
        """
        :param length_ratio: ratio of (unit in geometry) / (nanim length unit)
        :param x_range: Range  ``[x_min, x_max]`` of x-axis
        :param y_range: Range
        :param add_coordinates: Add coordinates to axis
        """
        x_length = x_range[1] - x_range[0]
        y_length = y_range[1] - y_range[0]
        _x_range = [*x_range]
        print(_x_range, x_length)
        _y_range = [*y_range]
        print(_y_range, y_length)
        grid = mm.NumberPlane(
            x_range=[*x_range, 1],x_length=x_length * length_ratio,
            y_range=[*y_range, 1],y_length=y_length * length_ratio,
            x_axis_config=style["axis"],
            y_axis_config=style["axis"],
            tips=tips
        )
        if add_coordinates:
            grid.add_coordinates()
        return grid


    @property
    def grid(self):
        return self._grid

    def make_dot(self, p:Point, **kwargs) -> DotConstruction:
        """
        make a Manim Dot, which can be used in a Scene.

        :param p:
        :param kwargs:
        :return:
        """
        default_style = style["dot"]
        dot_config = default_style | kwargs
        dot = Dot(self._grid.coords_to_point(p.re, p.im), **dot_config )
        dot.z_index = TOP_LAYER
        return DotConstruction(Create(dot), dot, p)

    def make_point_label(self, p:Point, label:str, dir:Vector3DLike = mm.DOWN, **kwargs)-> LabelConstruction:
        """
        Label a point (TODO)

        :param p:
        :param label:
        :param dir:
        :param kwargs:
        :return:
        """
        dot = self._grid.coords_to_point(p.re, p.im)
        style_option = style["label"] | kwargs
        l = MathTex(f"{label}", **style_option)
        l.next_to(dot, direction=dir)
        return LabelConstruction( Create(l), l, p, label )

    def label_point(self, p:Point, label:str, s: Scene, dir:Vector3DLike = mm.DOWN, **kwargs):
        mm_label = self.make_point_label(p, label, dir, **kwargs)
        s.add(mm_label.result)

    def draw_point(self, p:Point, s:Scene, **kwargs):
        """
        draws a point p in senne s respect to self.grid.
        :param p:
        :param s:
        :param kwargs:
        :return:
        """
        dot = self.make_dot(p, **kwargs)
        s.add(dot.result)

    def make_line(self, l: Line, **kwargs):
        config = style["cline"] | kwargs
        config = config.get("me", {})
        add = config.get("add", 0.2)
        add_a = config.get("add_a", add)
        add_b = config.get("add_b", add_a)
        # pa und extension in (pb,pa) directionf
        _pa = l.report(-add_a*l.length)
        pa = self._grid.coords_to_point(_pa.re, _pa.im)
        pa_dot = self.make_dot(l.pa)
        # pb and extension in (pa,pb) direction
        _pb = l.report(add_b * l.length, l.pb)
        pb = self._grid.coords_to_point(_pb.re, _pb.im)
        pb_dot = self.make_dot(l.pb)

        applied_style = style["cline"] | kwargs
        del applied_style["me"]
        line = mm.Line(pa, pb, **applied_style)
        return LineConstruction(Create(line), line, pa_dot, pb_dot)

    def make_segment(self, l: Line, **kwargs):
        config = kwargs | {
            "me": {"add": 0, "add_a": 0, "add_b": 0}
        }
        return self.make_line(l, **config)

    def draw_line(self, l:Line, s:Scene, **kwargs):
        """shortcut to draw a line through two points into scene

        :param l: Line to be drawn
        :param s: scene, into which the line is drawn
        :param kwargs: options to forward to line construction
        """
        line_construction = self.make_line(l, **kwargs)
        s.add(line_construction.pa.result, line_construction.pb.result)
        s.add(line_construction.result)

    def make_line_label(self, l:Line, label:str, pos:float=0.5, dir:Vector3DLike=mm.DOWN, rotate:float|None=None, **kwargs):
        r"""
        :param l: Line to be labeled
        :param label: the label text, is set in a pair of $
        :param pos: relative position on the line (0=l.pa, 1=l.pb)
        :param dir: relative direction on the line
        :param rotate: in degree
        :param kwargs:
        :return:
        """
        _anchor_point = l.point(pos)
        anchor_point = self._grid.coords_to_point(_anchor_point.re, _anchor_point.im)
        style_option = style["label"] | kwargs
        mn_l = MathTex(f"{label}", **style_option)
        mn_l.next_to(anchor_point, direction=dir)
        if rotate is not None:
            mn_l.rotate(rotate)
        else:
            mn_l.rotate( math.degrees(l.slope) )
        return LabelConstruction(Create(mn_l), mn_l, _anchor_point, label)


    def label_line(self, l:Line, label:str, s:Scene, pos:float=0.5, dir:Vector3DLike=mm.DOWN, rotate:float|None=None, **kwargs):
        label_construction = self.make_line_label(l, label, pos, dir, rotate, **kwargs)
        s.add(label_construction.result)








