import logging
from collections import defaultdict
from typing import Type, TypeVar, List, Optional

import wx

logger = logging.getLogger(__name__)

_TwxWindow = TypeVar("_TwxWindow", bound=wx.Window)


def change_foreground_color_to_dimmed(w: wx.Window, fgc: Optional[wx.Colour] = None):
    if fgc is None:
        fgc = w.GetForegroundColour()
    w.SetForegroundColour(fgc.MakeDisabled(208 if fgc.GetLuminance() <= 0.5 else 48))


def change_font_to_bold(w: wx.Window, size_coefficient: float = 1.75):
    # FIXME: There is a bug under GTK, where the new extent of the widget is not properly updated immediately
    #  after changing the font size (of wx.StaticText at least). It doesn't manifest consistently either.
    f: wx.Font = w.GetFont()
    f.SetPointSize(int(round(f.GetPointSize() * size_coefficient, 1)))
    f.SetWeight(wx.FONTWEIGHT_BOLD)
    w.SetFont(f)


class Signal(object):
    """
    Simple interface to track callback targets for this object.
    """

    def __init__(self):
        self._slots = set()

    def _add_slot(self, slot):
        self._slots.add(slot)

    def _remove_slots(self, slots):
        self._slots.difference_update(slots)

    def emit(self, *args, **kwargs):
        for slot in list(self._slots):
            if slot in self._slots:  # skip revoked slots by the signal itself
                self._emit(slot, *args, **kwargs)

    @classmethod
    def _emit(cls, slot, *args, **kwargs):
        try:
            slot(*args, **kwargs)
        except Exception:
            logger.exception("Error while emitting signal to slot: %s", slot)


class EventSignal(Signal):
    def __init__(self):
        self._triggered = False
        Signal.__init__(self)

    def is_set(self):
        return self._triggered

    def _add_slot(self, slot):
        Signal._add_slot(self, slot)
        if self._triggered:
            self._emit(slot)

    def set(self):
        self._triggered = True
        self.emit()

    def clear(self):
        self._triggered = False


class Slots(object):
    """
    Simple interface to track callback targets on this object.
    """

    def __init__(self):
        self._binds = defaultdict(set)
        self.Bind(wx.EVT_WINDOW_DESTROY, self._on_destroy, self)

    def bind_signal(self, signal: Signal, slot):
        """
        Create a signal-slot connection.
        """
        self._binds[signal].add(slot)
        signal._add_slot(slot)

    def _on_destroy(self, event):
        """
        Call this from Destroy().
        """
        for signal, slots in self._binds.items():
            signal._remove_slots(slots)


def find_children_of_type(parent: wx.Window, cls: Type[_TwxWindow]) -> List[_TwxWindow]:
    c = []
    for w in parent.GetChildren():
        if isinstance(w, cls):
            c.append(w)
        else:
            c.extend(find_children_of_type(w, cls))
    return c


def find_last_children_of_type(parent: wx.Window, cls: Type[_TwxWindow]) -> List[_TwxWindow]:
    c = []
    for w in parent.GetChildren():
        cs = find_last_children_of_type(w, cls)
        if cs:
            c.extend(cs)
        elif isinstance(w, cls):
            c.append(w)
    return c
