from __future__ import annotations

from typing import TypeVar, Generic, Union

from itertools import chain
from functools import wraps, partial

from fidget.core.plaintext_adapter import high_priority

from fidget.backend.QtWidgets import QCheckBox, QHBoxLayout, QWidget, QApplication
from fidget.backend.QtCore import QObject, QEvent, __backend__

from fidget.core import Fidget, PlaintextPrintError, PlaintextParseError, FidgetTemplate
from fidget.core.__util__ import first_valid

from fidget.widgets.idiomatic_inner import SingleFidgetWrapper
from fidget.widgets.__util__ import only_valid, is_trivial_printer

if __backend__.__name__ == 'PySide2':
    import shiboken2 as shiboken
elif __backend__.__name__ == 'PySide6':
    import shiboken6 as shiboken
else:
    shiboken = None

T = TypeVar('T')
C = TypeVar('C')


class FidgetOptional(Generic[T, C], SingleFidgetWrapper[T, Union[T, C]]):
    """
    A Fidget wrapper that allows an inner Fidget to be disabled, setting the value to None or another singleton
    """
    singleton_names = {
        None: frozenset(['none']),
        NotImplemented: frozenset(['notimplemented', 'not implemented']),
        ...: frozenset(['...', 'ellipsis']),
        (): frozenset(['()']),
        0: frozenset(['0']),
        '': frozenset(['""', "''"])
    }

    class MouseWarden(QObject):
        """
        An event filter that enables the disabled inner widget when clicked
        """
        def __init__(self, *args, target, dispatch, **kwargs):
            super().__init__(*args, **kwargs)
            self.target = target
            self.dispatch = dispatch

        def eventFilter(self, obj, event):
            if obj.isWidgetType() and event.type() == QEvent.MouseButtonPress \
                    and self.target in obj.window().findChildren(QWidget) \
                    and self.target.underMouse() and not self.target.isEnabled():
                self.dispatch()
                obj.setFocus()

            if shiboken is not None and not shiboken.isValid(obj):
                return False
            return super().eventFilter(obj, event)

    def __init__(self, inner_template: FidgetTemplate[T] = None, default_state=False, layout_cls=None,
                 none_value: C = None,
                 **kwargs):
        """
        :param inner_template: the template to wrap
        :param default_state: whether the default value of the widget will be enabled
        :param layout_cls: the layout class
        :param none_value: the value to set when the widget is disabled
        :param kwargs: forwarded to Fidget
        """

        inner_template = only_valid(inner_template=inner_template, INNER_TEMPLATE=self.INNER_TEMPLATE, _self=self).template_of()

        inner_template.extract_default(sink=kwargs, upper_space=type(self))

        super().__init__(inner_template.title, **kwargs)

        self.inner_template = inner_template

        self.inner: Fidget[T] = None
        self.not_none_checkbox: QCheckBox = None
        self.warden: FidgetOptional.MouseWarden = None

        self.none_value = none_value
        none_names = self.singleton_names.get(self.none_value)
        if none_names:
            @high_priority
            def NoneParser(s: str):
                if s.lower() in none_names:
                    return self.none_value
                raise PlaintextParseError(f'this parser only accepts {next(iter(none_names))}')
            self.none_parser = NoneParser
        else:
            self.none_parser = None

        self.init_ui(layout_cls)

        self.not_none_checkbox.setChecked(default_state)

    INNER_TEMPLATE: FidgetTemplate[T] = None
    LAYOUT_CLS = QHBoxLayout

    def init_ui(self, layout_cls=None):
        super().init_ui()
        layout_cls = first_valid(layout_cls=layout_cls, LAYOUT_CLS=self.LAYOUT_CLS, _self=self)

        layout = layout_cls(self)

        with self.setup_provided(layout):
            self.not_none_checkbox = QCheckBox()
            self.not_none_checkbox.toggled.connect(self._not_none_changed)
            layout.addWidget(self.not_none_checkbox)

            self.inner = self.inner_template()
            self.inner.on_change.connect(self.change_value)
            self.inner.setEnabled(False)

            layout.addWidget(self.inner)

        for p in chain(self.inner.provided_pre(),
                       self.inner.provided_post()):
            p.hide()

        self.warden = self.MouseWarden(target=self.inner, dispatch=partial(self.not_none_checkbox.setChecked, True))
        QApplication.instance().installEventFilter(self.warden)

        return layout

    def parse(self):
        if self.not_none_checkbox.isChecked():
            return self.inner.maybe_parse()
        return self.none_value

    def validate(self, v):
        super().validate(v)
        if v is not self.none_value:
            self.inner.maybe_validate(v)

    def plaintext_printers(self):
        def printer_wrapper(ip):
            @wraps(ip)
            def wrapper(v):
                if v is self.none_value:
                    raise PlaintextPrintError(f'this printer cannot handle {v!r}')
                return ip(v)

            return wrapper

        yield from super().plaintext_printers()
        for ip in self.inner.plaintext_printers():
            if is_trivial_printer(ip):
                continue

            yield printer_wrapper(ip)

    def plaintext_parsers(self):
        yield from super().plaintext_parsers()
        yield from self.inner.plaintext_parsers()
        if self.none_parser:
            yield self.none_parser

    def _fill(self, v):
        if v is self.none_value:
            self.not_none_checkbox.setChecked(False)
        else:
            self.not_none_checkbox.setChecked(True)
            self.inner.fill(v)

    @property
    def fill(self):
        return self.inner.fill and self._fill

    def _not_none_changed(self, new_state):
        enable = new_state != 0
        self.inner.setEnabled(enable)
        self.change_value()


if __name__ == '__main__':
    from fidget.widgets import *

    app = QApplication([])
    w = FidgetOptional(
        FidgetInt.template('source ovr', placeholder=False, make_title=True, make_indicator=True),
    )
    w.show()
    res = app.exec_()
    print(w.value())
    exit(res)

from __future__ import annotations

from typing import TypeVar, Generic, Union

from itertools import chain
from functools import wraps, partial

from fidget.core.plaintext_adapter import high_priority

from fidget.backend.QtWidgets import QCheckBox, QHBoxLayout, QWidget, QApplication
from fidget.backend.QtCore import QObject, QEvent, __backend__

from fidget.core import Fidget, PlaintextPrintError, PlaintextParseError, FidgetTemplate
from fidget.core.__util__ import first_valid

from fidget.widgets.idiomatic_inner import SingleFidgetWrapper
from fidget.widgets.__util__ import only_valid, is_trivial_printer

if __backend__.__name__ == 'PySide2':
    import shiboken2 as shiboken
elif __backend__.__name__ == 'PySide6':
    import shiboken6 as shiboken
else:
    shiboken = None

T = TypeVar('T')
C = TypeVar('C')


class FidgetOptional(Generic[T, C], SingleFidgetWrapper[T, Union[T, C]]):
    """
    A Fidget wrapper that allows an inner Fidget to be disabled, setting the value to None or another singleton
    """
    singleton_names = {
        None: frozenset(['none']),
        NotImplemented: frozenset(['notimplemented', 'not implemented']),
        ...: frozenset(['...', 'ellipsis']),
        (): frozenset(['()']),
        0: frozenset(['0']),
        '': frozenset(['""', "''"])
    }

    class MouseWarden(QObject):
        """
        An event filter that enables the disabled inner widget when clicked
        """
        def __init__(self, *args, target, dispatch, **kwargs):
            super().__init__(*args, **kwargs)
            self.target = target
            self.dispatch = dispatch

        def eventFilter(self, obj, event):
            if obj.isWidgetType() and event.type() == QEvent.MouseButtonPress \
                    and self.target in obj.window().findChildren(QWidget) \
                    and self.target.underMouse() and not self.target.isEnabled():
                self.dispatch()
                obj.setFocus()

            if shiboken is not None and not shiboken.isValid(obj):
                return False
            return super().eventFilter(obj, event)

    def __init__(self, inner_template: FidgetTemplate[T] = None, default_state=False, layout_cls=None,
                 none_value: C = None,
                 **kwargs):
        """
        :param inner_template: the template to wrap
        :param default_state: whether the default value of the widget will be enabled
        :param layout_cls: the layout class
        :param none_value: the value to set when the widget is disabled
        :param kwargs: forwarded to Fidget
        """

        inner_template = only_valid(inner_template=inner_template, INNER_TEMPLATE=self.INNER_TEMPLATE, _self=self).template_of()

        inner_template.extract_default(sink=kwargs, upper_space=type(self))

        super().__init__(inner_template.title, **kwargs)

        self.inner_template = inner_template

        self.inner: Fidget[T] = None
        self.not_none_checkbox: QCheckBox = None
        self.warden: FidgetOptional.MouseWarden = None

        self.none_value = none_value
        none_names = self.singleton_names.get(self.none_value)
        if none_names:
            @high_priority
            def NoneParser(s: str):
                if s.lower() in none_names:
                    return self.none_value
                raise PlaintextParseError(f'this parser only accepts {next(iter(none_names))}')
            self.none_parser = NoneParser
        else:
            self.none_parser = None

        self.init_ui(layout_cls)

        self.not_none_checkbox.setChecked(default_state)

    INNER_TEMPLATE: FidgetTemplate[T] = None
    LAYOUT_CLS = QHBoxLayout

    def init_ui(self, layout_cls=None):
        super().init_ui()
        layout_cls = first_valid(layout_cls=layout_cls, LAYOUT_CLS=self.LAYOUT_CLS, _self=self)

        layout = layout_cls(self)

        with self.setup_provided(layout):
            self.not_none_checkbox = QCheckBox()
            self.not_none_checkbox.toggled.connect(self._not_none_changed)
            layout.addWidget(self.not_none_checkbox)

            self.inner = self.inner_template()
            self.inner.on_change.connect(self.change_value)
            self.inner.setEnabled(False)

            layout.addWidget(self.inner)

        for p in chain(self.inner.provided_pre(),
                       self.inner.provided_post()):
            p.hide()

        self.warden = self.MouseWarden(target=self.inner, dispatch=partial(self.not_none_checkbox.setChecked, True))
        QApplication.instance().installEventFilter(self.warden)

        return layout

    def parse(self):
        if self.not_none_checkbox.isChecked():
            return self.inner.maybe_parse()
        return self.none_value

    def validate(self, v):
        super().validate(v)
        if v is not self.none_value:
            self.inner.maybe_validate(v)

    def plaintext_printers(self):
        def printer_wrapper(ip):
            @wraps(ip)
            def wrapper(v):
                if v is self.none_value:
                    raise PlaintextPrintError(f'this printer cannot handle {v!r}')
                return ip(v)

            return wrapper

        yield from super().plaintext_printers()
        for ip in self.inner.plaintext_printers():
            if is_trivial_printer(ip):
                continue

            yield printer_wrapper(ip)

    def plaintext_parsers(self):
        yield from super().plaintext_parsers()
        yield from self.inner.plaintext_parsers()
        if self.none_parser:
            yield self.none_parser

    def _fill(self, v):
        if v is self.none_value:
            self.not_none_checkbox.setChecked(False)
        else:
            self.not_none_checkbox.setChecked(True)
            self.inner.fill(v)

    @property
    def fill(self):
        return self.inner.fill and self._fill

    def _not_none_changed(self, new_state):
        enable = new_state != 0
        self.inner.setEnabled(enable)
        self.change_value()


if __name__ == '__main__':
    from fidget.widgets import *

    app = QApplication([])
    w = FidgetOptional(
        FidgetInt.template('source ovr', placeholder=False, make_title=True, make_indicator=True),
    )
    w.show()
    res = app.exec_()
    print(w.value())
    exit(res)

A Fidget wrapper that allows an inner Fidget to be disabled, setting the value to None or another singleton An event filter that enables the disabled inner widget when clicked :param inner_template: the template to wrap         :param default_state: whether the default value of the widget will be enabled         :param layout_cls: the layout class         :param none_value: the value to set when the widget is disabled         :param kwargs: forwarded to Fidget