from __future__ import annotations

import random
import numpy as np

from .tkcoords import convert_axes_coordinates
from .tkstate import state


class tkMovablePointController:
    def __init__(self, events):
        self.events = events
        self.state = state(fig=None, gco=None)
        self.state.add = self.add

    def add(self, ax, x, y, marker="o", picker=15, color="red"):
        line, = ax.plot(x, y, marker, markerfacecolor=color, markeredgecolor=color, picker=picker)
        return line

    def register(self, fig):
        self.state.fig = fig
        def motion(event):
            if self.state.gco is None or event.xdata is None or event.ydata is None:
                return
            self.state.gco.set_data([event.xdata], [event.ydata]); fig.canvas.draw_idle()
        def onpick(event): self.state.gco = event.artist
        def release(event): self.state.gco = None
        self.events.connect(fig, "motion_notify_event", motion, key=("move-points-motion", id(fig)))
        self.events.connect(fig, "pick_event", onpick, key=("move-points-pick", id(fig)))
        self.events.connect(fig, "button_release_event", release, key=("move-points-release", id(fig)))


class tkMovableTextController:
    def __init__(self, events):
        self.events = events
        self.state = state(fig=None, text_inf=[], is_active=False)
        self.state.add_text = self.add_text
        self.state.add_annotation = self.add_annotation
        self.state.activate = self.activate

    @staticmethod
    def _get_xy(x=None, y=None, x_list=None, y_list=None, x_target=None, frac=None, xlim=None, ylim=None):
        if x_list is None or y_list is None:
            return x, y
        xs = np.asarray(x_list, dtype=float).ravel(); ys = np.asarray(y_list, dtype=float).ravel()
        valid = np.isfinite(xs) & np.isfinite(ys)
        xs, ys = xs[valid], ys[valid]
        if xs.size == 0:
            return None, None
        order = np.argsort(xs); xs, ys = xs[order], ys[order]
        lo, hi = float(xs.min()), float(xs.max())
        if xlim is not None:
            lo, hi = max(lo, xlim[0]), min(hi, xlim[1])
        if x_target is None:
            x_target = lo + (hi - lo) * (random.random() if frac is None else frac)
        return float(x_target), float(np.interp(x_target, xs, ys))

    def add_text(self, ax, ax_ref, x=None, y=None, x_list=None, y_list=None, x_offset=0,
                 x_target=None, frac=None, xlim=None, ylim=None, text="no name", fontsize=10,
                 ha="center", va="center", color="black", fc="w", ec="none", alpha=0.5):
        x, y = self._get_xy(x, y, x_list, y_list, x_target, frac, xlim, ylim)
        if x is None or y is None: return None
        artist = ax.text(x + x_offset, y, text, fontsize=fontsize, ha=ha, va=va, color=color,
                         bbox=dict(facecolor=fc, alpha=alpha, pad=0.5, edgecolor=ec, boxstyle="round"))
        self.state.text_inf.append([ax, ax_ref, artist, False, "text"])
        return artist

    def add_annotation(self, ax, ax_ref, x=None, y=None, x_list=None, y_list=None, x_offset=0,
                       x_target=None, frac=None, xlim=None, ylim=None, text="no name", fontsize=10,
                       ha="center", va="center", color="black", fc="w", ec="none", alpha=0.5):
        x, y = self._get_xy(x, y, x_list, y_list, x_target, frac, xlim, ylim)
        if x is None or y is None: return None
        artist = ax.annotate(text, xy=(x + x_offset, y), xytext=(0, 0), textcoords="offset points",
                             fontsize=fontsize, ha=ha, va=va, color=color,
                             bbox=dict(facecolor=fc, alpha=alpha, pad=0.5, edgecolor=ec, boxstyle="round"))
        self.state.text_inf.append([ax, ax_ref, artist, False, "annotate"])
        return artist

    def activate(self, flag=True): self.state.is_active = bool(flag)

    def register(self, fig=None, activate=False):
        if fig is not None: self.state.fig = fig
        fig = self.state.fig
        if fig is None: raise ValueError("figure is required")
        self.activate(activate)
        def on_click(event):
            for inf in self.state.text_inf:
                contains, _ = inf[2].contains(event)
                if contains: inf[3] = True; break
        def on_release(event):
            for inf in self.state.text_inf: inf[3] = False
        def on_move(event):
            if not self.state.is_active or event.button != 1 or event.xdata is None or event.ydata is None: return
            for ax, ax_ref, artist, dragging, kind in self.state.text_inf:
                if not dragging: continue
                x, y = convert_axes_coordinates(event.xdata, event.ydata, event.inaxes or ax_ref, ax)
                if x is None: return
                if kind == "text": artist.set_position((x, y))
                else: artist.xy = (x, y); artist.set_position((0, 0))
                fig.canvas.draw_idle(); return
        self.events.connect(fig, "button_press_event", on_click, key=("move-text-press", id(fig)))
        self.events.connect(fig, "button_release_event", on_release, key=("move-text-release", id(fig)))
        self.events.connect(fig, "motion_notify_event", on_move, key=("move-text-motion", id(fig)))
