import uuid
from typing import Dict
from coopgame.gameTemplate import GameTemplate
from coopgame.handleKeyPressedArgs import InputStateHandler, InputAction, InputEvent, InputEventType, basic_key_actions, CallbackPackage
from coopgame.pygame_k_constant_names import PyKeys, PyMouse
from coopgame.linedrawing.pyCurveHandler import PyCurveHandler, CurveSurfaceType
from coopgame.linedrawing.curve_draw_utils import CurveDrawArgs
from coopgame.linedrawing.line_draw_utils import DrawLineArgs
from coopgame.pointdrawing.point_draw_utils import DrawPointArgs
from coopstructs.geometry.curves.curves import Curve, CubicBezier
from coopstructs.geometry.curves.curveBuilder import CurveBuilder
from coopstructs.geometry.rectangles.rectangle import Rectangle
import coopgame.pygamehelpers as help
from cooptools.toggles import BooleanToggleable
import cooptools.matrixManipulation as mm
import numpy as np
from cooptools.colors import Color
import cooptools.os_manip as osm
from coopui.cli.CliAtomicUserInteraction import CliAtomicUserInteraction as cli
from coopstructs.geometry.curves.curve_factory import curves_from_dict
from cooptools.common import flattened_list_of_lists
from coopstructs.geometry.curves.enums import CurveType

class CurveGame_Basic(GameTemplate):

    def __init__(self):
        super().__init__(input_state_handler=self.define_input_handler())

        self.curve_handler = PyCurveHandler(
            screen=self.screen,
            get_dynamic_curves_callback=self._get_dynamic_curves,
            get_static_curves_callback=self._get_static_curves
        )
        self._curve_builder = CurveBuilder(id_provider=lambda: str(uuid.uuid4()), default_curve_type=CurveType.CUBICBEZIER)
        self.use_scaler_matrix = BooleanToggleable(default=False)
        self.force_continuity = True
        self.show_arc_curve_approx = BooleanToggleable(default=True)


    def define_input_handler(self):
        return InputStateHandler(
            quit_callback_package=CallbackPackage(down=lambda x: self.quit()),
            debug_callback_package=CallbackPackage(down=lambda x: self.debug_mode_toggle.toggle()),
            fullscreen_callback_package=CallbackPackage(down=lambda x: self.toggle_fullscreen()),
            key_actions=[
                InputAction([InputEvent(PyKeys.K_r, event_type=InputEventType.DOWN)], callback=lambda x: self.curve_handler.redraw()),
                InputAction([InputEvent(PyKeys.K_0, event_type=InputEventType.DOWN)], callback=lambda x: self.curve_handler.toggle_surface([CurveSurfaceType.DYNAMIC_CURVES_ID])),
                InputAction([InputEvent(PyKeys.K_1, event_type=InputEventType.DOWN)], callback=lambda x: self.curve_handler.toggle_surface([CurveSurfaceType.STATIC_CURVES_ID])),
                InputAction([InputEvent(PyKeys.K_2, event_type=InputEventType.DOWN)], callback=lambda x: self.curve_handler.toggle_surface([CurveSurfaceType.DYNAMIC_OVERLAY_ID])),
                InputAction([InputEvent(PyKeys.K_3, event_type=InputEventType.DOWN)], callback=lambda x: self.curve_handler.toggle_surface([CurveSurfaceType.STATIC_OVERLAY_ID])),
                InputAction([InputEvent(PyMouse.LEFT, event_type=InputEventType.DOWN)],callback=lambda x: self.handle_left_mouse_down()),
                InputAction([InputEvent(PyMouse.RIGHT, event_type=InputEventType.DOWN)], callback=lambda x: self.handle_right_mouse_down()),
                InputAction([InputEvent(PyKeys.K_t, event_type=InputEventType.DOWN)], callback=lambda x: self.toggle_curve_type()),
                InputAction([InputEvent(PyKeys.K_s, event_type=InputEventType.DOWN)], callback=lambda x: self.save_curve()),
                InputAction([InputEvent(PyKeys.K_l, event_type=InputEventType.DOWN)], callback=lambda x: self.load_curves()),
                InputAction([InputEvent(PyKeys.K_c, event_type=InputEventType.DOWN)], callback=lambda x: self.show_arc_curve_approx.toggle()),
        ])

    def next_point(self):
        game_area_rect = Rectangle.from_tuple((0, 0, self.screen.get_height(), self.screen.get_width()))
        if self.force_continuity:
            return self._curve_builder.force_continuity(
                help.normal_mouse_position(game_area_rect=game_area_rect, draw_scale_matrix=self.draw_scale_matrix()))
        else:
            return help.normal_mouse_position(draw_scale_matrix=self.draw_scale_matrix(), game_area_rect=game_area_rect)

    def handle_left_mouse_down(self):
        self._curve_builder.add_point(self.next_point())
        self.curve_handler.invalidate()

    def handle_right_mouse_down(self):
        self._curve_builder.remove_point()
        self.curve_handler.invalidate()

    def _get_dynamic_curves(self) -> Dict[Curve,CurveDrawArgs]:
        new_curves = self._curve_builder.curves_from_temp_next_points([self.next_point()])

        curves = {curve: CurveDrawArgs(
            line_args=DrawLineArgs(
                color=Color.LIGHT_BLUE,
                width=2),
            control_line_args=DrawLineArgs(
                color=Color.GREY,
                width=1,
            ),
            control_point_args=DrawPointArgs(
                color=Color.RED,
                radius=5,
                outline_color=Color.YELLOW,
                outline_width=1
            ),
        )
            for curve in new_curves}

        if self.show_arc_curve_approx.value:
            arc_approx = flattened_list_of_lists([CubicBezier.as_circular_arcs(x, tolerance=5) for x in new_curves if type(x) == CubicBezier])

            curves = {**curves,
                **{x : CurveDrawArgs(
                        line_args=DrawLineArgs(
                            color=Color.RED,
                            width=2))
                    for x in arc_approx}}



        return curves

    def _get_static_curves(self) -> Dict[Curve,CurveDrawArgs]:
        curves = {curve: CurveDrawArgs(
            line_args=DrawLineArgs(
                color=Color.GREEN,
                width=2),
            buffer=5,
            buffer_color=Color.DARK_KHAKI
        )
            for curve in self._curve_builder.curves.values()}

        return curves


    def draw(self, frames: int, debug_mode: bool = False):
        self.curve_handler.render(self.screen)
        help.draw_points(
            {self.next_point().as_tuple(): DrawPointArgs(
                outline_color=Color.GREY,
                radius=5
            )},
            surface=self.screen
        )

        help.draw_mouse_pos(
            DrawPointArgs(
                color=Color.GREY,
                outline_color=Color.RED,
                radius=7
            ),
            screen=self.screen
        )



    def draw_scale_matrix(self):
        if self.use_scaler_matrix.value:
            return mm.rotateAroundPointMatrix((self.screen_width/2, self.screen_height / 2),  np.pi/4, np.pi/4)
        else:
            return None

    def toggle_curve_type(self):
        self._curve_builder.toggle_curve_type()

    def save_curve(self):
        self._curve_builder.save_curve()
        self.curve_handler.invalidate()

    def load_curves(self):
        fp = r'C:\Users\tburns\PycharmProjects\coopstructs\tests\testData\curves.json'

        if fp is None:
            fp=cli.request_open_filepath(prompt=f"Select a curves file", title="Curves", filetypes=((osm.FileType.JSON.value, ".json"), ))

        if fp is None:
            return

        data = osm.try_load_json_data_to_dict(file_path=fp)
        if data is None:
            return

        self._curve_builder.curves.clear()
        self._curve_builder.add_curves(curves_from_dict(data))
        self._curve_builder.save_curve()
        self.curve_handler.invalidate()


if __name__ == "__main__":
    g = CurveGame_Basic()
    g.main()