"""."""

import copy

import matplotlib.pyplot as plt
import numpy as np

from matplotlib.backend_bases import MouseEvent, PickEvent
from matplotlib.container import BarContainer
from matplotlib.patches import Circle, Polygon
from scipy.spatial.transform import Rotation

from association_studio.core.mid_box import Box
from association_studio.score_driver.metric_labels import get_labels
from association_studio.score_driver.score_driver import ScoreDriver

from .metric_mode import MetricMode
from .show_coo_mode import ShowCooMode


class AssociationStudioBackend:
    def __init__(self) -> None:
        self.score_driver = ScoreDriver()
        self.show_coo_mode = ShowCooMode.TRANSLATION_AND_ROTATION
        self.metric_mode = MetricMode(self.score_driver.metric_names)
        self.score_driver.set_association_metric(self.metric_mode.metric)
        self.box_ref = Box(self.score_driver.ref_1z[0])
        t_box = 0.3, 0.7, 0.0, 0.928476691, 0.0, 0.0, -0.371390676, 4.0, 2.0, 1.0
        self.box_probe = Box(np.array(t_box))
        self.box_probe_start_pose_change = copy.deepcopy(self.box_probe)
        bcc = self.box_ref.mid_corners()[:2, :].T
        self.ref_poly = Polygon(bcc, edgecolor='k', facecolor='yellow', closed=True)
        self.ref_circle = Circle(bcc[2:].mean(axis=0), 0.35, edgecolor='k', facecolor='yellow')

        bcc = self.box_probe.mid_corners()[:2, :].T
        self.probe_poly = Polygon(bcc, edgecolor='b', facecolor='none', closed=True, picker=True)
        trk_xy = bcc[2:].mean(axis=0)
        self.probe_circle = Circle(trk_xy, 0.35, edgecolor='b', facecolor='w', picker=True)
        self.score_driver.compute_score(self.box_probe.vec_z)
        self._rotate_30_quat = np.array((0.9659258262890683, 0.0, 0.0, 0.25881904510252074))

    def next_show_coo_mode(self) -> None:
        self.show_coo_mode = ShowCooMode(self.show_coo_mode.next())

    def next_metric(self) -> None:
        self.metric_mode.next()
        self.score_driver.set_association_metric(self.metric_mode.metric)
        self.score_driver.compute_score(self.box_probe.vec_z)

    def move_probe_to_origin(self) -> None:
        self.box_probe = copy.deepcopy(self.box_ref)
        self.upd_scores_and_probe()

    def rotate_probe_30(self) -> None:
        self.box_probe.rotate(self._rotate_30_quat)
        self.upd_scores_and_probe()

    def set_unit_size_ref(self) -> None:
        self.box_ref.set_size(1.0, 1.0, 1.0)
        self.upd_scores_and_probe()

    def set_size_probe(self, width: float, length: float) -> None:
        self.box_probe.set_size(width, length, 1.0)
        self.upd_scores_and_probe()

    def start_pose_change(self) -> None:
        self.box_probe_start_pose_change = copy.deepcopy(self.box_probe)

    def change_trk_pose(self, pick: PickEvent, event: MouseEvent) -> None:
        me = pick.mouseevent

        if pick.artist == self.probe_poly:
            translation = np.array((event.xdata - me.xdata, event.ydata - me.ydata, 0.0))
            self.box_probe = copy.deepcopy(self.box_probe_start_pose_change)
            self.box_probe.translate(translation)
        elif pick.artist == self.probe_circle:
            self.box_probe = copy.deepcopy(self.box_probe_start_pose_change)
            c = self.box_probe.vec_z[:3]
            a = np.array((event.xdata - c[0], event.ydata - c[1], 0.0))
            b = np.array((me.xdata - c[0], me.ydata - c[1], 0.0))
            q_ab = Rotation.align_vectors(a, b)[0].as_quat(scalar_first=True)
            self.box_probe.rotate(q_ab)

        self.upd_scores_and_probe()

    def upd_scores_and_probe(self) -> None:
        self.score_driver.set_ref_z(self.box_ref.vec_z)
        self.score_driver.compute_score(self.box_probe.vec_z)
        self.upd_probe_patches()
        self.upd_ref_patches()

    def upd_probe_patches(self) -> None:
        bcc = self.box_probe.mid_corners()[:2, :].T
        self.probe_poly.set_xy(bcc)
        self.probe_circle.set_center(bcc[2:].mean(axis=0))

    def upd_ref_patches(self) -> None:
        bcc = self.box_ref.mid_corners()[:2, :].T
        self.ref_poly.set_xy(bcc)
        self.ref_circle.set_center(bcc[2:].mean(axis=0))

    def get_title(self) -> str:
        return f'{self.metric_mode.metric} {self.score_driver.score:5.4f}'

    def get_text(self) -> str:
        return self.show_coo_mode.format(self.box_probe)

    def get_horizontal_bar_container(self, ax_metrics: plt.Axes) -> BarContainer:
        scores = self.score_driver.scores
        names = get_labels(self.score_driver.metric_names)
        return ax_metrics.barh(range(scores.shape[0]), scores, tick_label=names)

    def update_horizontal_bar_container(self, bar: BarContainer) -> None:
        for bar, new_h in zip(bar, self.score_driver.scores):
            bar.set_width(new_h)
