from typing import TYPE_CHECKING, Callable

from qtpy.QtCore import QPoint
from qtpy.QtGui import QCursor
from qtpy.QtWidgets import QApplication

from . import patchcanvas
from patshared import (
    PortMode, PortType, BoxLayoutMode,
    PortgroupMem, BoxPos)
from .patchcanvas import PortMode, PortType, BoxLayoutMode
from .patchcanvas.proto_callbacker import ProtoCallbacker
from .bases.port import Port
from .dialogs.port_info_dialog import CanvasPortInfoDialog
from .menus.port_menu import PoMenu, ConnectMenu
from .menus.group_menu import GroupMenu
from .cancel_mng import CancellableAction, CancelOp

if TYPE_CHECKING:
    from .patchbay_manager import PatchbayManager


_translate = QApplication.translate


class Callbacker(ProtoCallbacker):
    '''manage actions coming from patchcanvas.
    Each protected method implements the action to run
    when the action happens.'''

    def __init__(self, manager: 'PatchbayManager'):
        self.mng = manager
        self.patchcanvas = patchcanvas

    def group_info(self, group_id: int):
        ...

    def group_rename(
            self, group_id: int, pretty_name: str, save_in_jack: bool):
        ...

    def group_pos_modified(self, group_id: int):
        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return
        group.save_current_position()

    def group_splitted(self, group_id: int):
        group = self.mng.get_group_from_id(group_id)
        if group is not None:
            group.current_position.set_splitted(True)
            group.save_current_position()

    def group_joined(self, group_id: int):
        group = self.mng.get_group_from_id(group_id)
        if group is not None:
            group.current_position.set_splitted(False)
            group.save_current_position()

    def group_move(self, group_id: int, port_mode: PortMode, x: int, y: int):
        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return
        group.current_position.boxes[port_mode].pos = (x, y)
        group.save_current_position()

    def boxes_moved(self, *pos_tuples: tuple[int, PortMode, int, int]):
        with CancellableAction(self.mng, CancelOp.VIEW) as a:
            if len(pos_tuples) >= 1:
                a.name = _translate('undo', 'Move %i boxes') % len(pos_tuples)

            for group_id, port_mode, x, y in pos_tuples:
                group = self.mng.get_group_from_id(group_id)
                if group is None:
                    continue

                if len(pos_tuples) == 1:
                    a.name = _translate('undo', 'Move %s') \
                        % group.cnv_name

                group.current_position.boxes[port_mode].pos = (x, y)
                group.set_group_position(
                    group.current_position, PortMode.NULL, PortMode.NULL)
                group.save_current_position()
                patchcanvas.repulse_from_group(group_id, port_mode)

    def group_box_pos_changed(
            self, group_id: int, port_mode: PortMode, box_pos: BoxPos):
        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return

        group.current_position.boxes[port_mode].eat(box_pos)

        group.save_current_position()

    def group_wrap(
            self, group_id: int, splitted_mode: PortMode, yesno: bool):
        group = self.mng.get_group_from_id(group_id)
        if group is not None:
            with CancellableAction(self.mng, CancelOp.VIEW) as a:
                if yesno:
                    a.name = _translate('undo', 'Wrap "%s"') \
                        % group.cnv_name
                else:
                    a.name = _translate('undo', 'Unwrap "%s"') \
                        % group.cnv_name
                group.wrap_box(splitted_mode, yesno)

    def group_layout_change(self, group_id: int, port_mode: PortMode,
                            layout_mode: BoxLayoutMode):
        group = self.mng.get_group_from_id(group_id)
        if group is not None:
            group.set_layout_mode(port_mode, layout_mode)

    def group_selected(self, group_id: int, port_mode: PortMode):
        ...

    def group_hide_box(self, group_id: int, port_mode: PortMode):
        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return

        self.mng.set_group_hidden_sides(group_id, port_mode)

    def group_menu_call(self, group_id: int, port_mode: PortMode):
        group = self.mng.get_group_from_id(group_id)
        if group is not None:
            menu = GroupMenu(self.mng, group, port_mode)
            menu.exec(QCursor().pos())

    def portgroup_add(self, group_id: int, port_mode: PortMode,
                       port_type: PortType, port_ids: tuple[int]):
        port_list = list[Port]()
        above_metadatas = False

        for port_id in port_ids:
            port = self.mng.get_port_from_id(group_id, port_id)
            if port is not None:
                if port.mdata_portgroup:
                    above_metadatas = True
                port_list.append(port)

        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return

        # we add a PortgroupMem, manager will add the portgroup with it
        pg_mem = PortgroupMem()
        pg_mem.group_name = group.name
        pg_mem.port_type = port_type
        pg_mem.port_mode = port_mode

        pg_mem.above_metadatas = above_metadatas
        pg_mem.port_names = [p.short_name for p in port_list]

        self.mng.add_portgroup_memory(pg_mem)
        self.mng.save_portgroup_memory(pg_mem)

    def portgroup_remove(self, group_id: int, portgroup_id: int):
        group = self.mng.get_group_from_id(group_id)
        if group is None:
            return

        for portgroup in group.portgroups:
            if portgroup.portgroup_id == portgroup_id:
                for port in portgroup.ports:
                    # save a fake portgroup with one port only
                    # it will be considered as a forced mono port
                    # (no stereo detection)
                    pg_mem = PortgroupMem()
                    pg_mem.group_name = group.name
                    pg_mem.port_type = port.type
                    pg_mem.port_mode = portgroup.port_mode
                    pg_mem.above_metadatas = bool(port.mdata_portgroup)
                    pg_mem.port_names = [port.short_name]
                    self.mng.add_portgroup_memory(pg_mem)
                    self.mng.save_portgroup_memory(pg_mem)
                break

    def port_info(self, group_id: int, port_id: int):
        port = self.mng.get_port_from_id(group_id, port_id)
        if port is None:
            return

        dialog = CanvasPortInfoDialog(self.mng.main_win)
        dialog.set_port(port)
        dialog.show()

    def port_rename(self, group_id: int, port_id: int,
                     pretty_name: str, save_in_jack: bool):
        ...

    def ports_connect(self, group_out_id: int, port_out_id: int,
                       group_in_id: int, port_in_id: int):
        ...

    def ports_disconnect(self, connection_id: int):
        ...

    def port_menu_call(self, group_id: int, port_id: int, connect_only: bool,
                        x: int, y: int):
        port = self.mng.get_port_from_id(group_id, port_id)
        if port is None:
            return

        if connect_only:
            menu = ConnectMenu(self.mng, port)
        else:
            menu = PoMenu(self.mng, port)
        menu.exec(QPoint(x, y))

    def portgroup_menu_call(self, group_id: int, portgrp_id: int, connect_only: bool,
                             x: int, y: int):
        for group in self.mng.groups:
            if group.group_id != group_id:
                continue

            for portgroup in group.portgroups:
                if portgroup.portgroup_id == portgrp_id:
                    break
            else:
                continue
            break
        else:
            return

        if connect_only:
            menu = ConnectMenu(self.mng, portgroup)
        else:
            menu = PoMenu(self.mng, portgroup)
        menu.exec(QPoint(x, y))

    def plugin_clone(self, plugin_id: int):
        ...

    def plugin_edit(self, plugin_id: int):
        ...

    def plugin_rename(self, plugin_id: int):
        ...

    def plugin_replace(self, plugin_id: int):
        ...

    def plugin_remove(self, plugin_id: int):
        ...

    def plugin_show_ui(self, plugin_id: int):
        ...

    def inline_display(self):
        ...

    def bg_right_click(
            self, x: int, y: int, scene_x: float, scene_y: float,
            selected_boxes: dict[int, PortMode]):
        if self.mng.canvas_menu is not None:
            self.mng.canvas_menu.remember_scene_pos(scene_x, scene_y)
            self.mng.canvas_menu.set_selected_boxes(selected_boxes)
            self.mng.canvas_menu.exec(QPoint(x, y))

    def bg_double_click(self):
        self.mng.sg.full_screen_toggle_wanted.emit()

    def client_show_gui(self, group_id: int, visible: int):
        ...

    def theme_changed(self, theme_ref: str):
        if self.mng.options_dialog is not None:
            self.mng.options_dialog.set_theme(theme_ref)

        patchcanvas.redraw_all_groups(theme_change=True)

    def animation_finished(self):
        self.mng.animation_finished()