"""
Common functions
"""

from PyQt6 import QtWidgets
import string
import random
import sys
import subprocess
import logging
from copy import copy
from collections import OrderedDict


log = logging.getLogger("AVP.Toolkit.Common")


class blockSignals:
    """
    Context manager to temporarily block list of QtWidgets from updating,
    and guarantee restoring the previous state afterwards.
    """

    def __init__(self, widgets):
        if type(widgets) is dict:
            self.widgets = concatDictVals(widgets)
        else:
            self.widgets = widgets if hasattr(widgets, "__iter__") else [widgets]

    def __enter__(self):
        log.verbose(
            "Blocking signals for %s",
            ", ".join([str(w.__class__.__name__) for w in self.widgets]),
        )
        self.oldStates = [w.signalsBlocked() for w in self.widgets]
        for w in self.widgets:
            w.blockSignals(True)

    def __exit__(self, *args):
        log.verbose("Resetting blockSignals to %s", str(bool(sum(self.oldStates))))
        for w, state in zip(self.widgets, self.oldStates):
            w.blockSignals(state)


def concatDictVals(d):
    """Concatenates all values in given dict into one list."""
    key, value = d.popitem()
    d[key] = value
    final = copy(value)
    if type(final) is not list:
        final = [final]
        final.extend([val for val in d.values()])
    else:
        value.extend([item for val in d.values() for item in val])
    return final


def badName(name):
    """Returns whether a name contains non-alphanumeric chars"""
    return any([letter in string.punctuation for letter in name])


def alphabetizeDict(dictionary):
    """Alphabetizes a dict into OrderedDict"""
    return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))


def presetToString(dictionary):
    """Returns string repr of a preset"""
    return repr(alphabetizeDict(dictionary))


def presetFromString(string):
    """Turns a string repr of OrderedDict into a regular dict"""
    return dict(eval(string))


def appendUppercase(lst):
    for form, i in zip(lst, range(len(lst))):
        lst.append(form.upper())
    return lst


def pipeWrapper(func):
    """A decorator to insert proper kwargs into Popen objects."""

    def pipeWrapper(commandList, **kwargs):
        if sys.platform == "win32":
            # Stop CMD window from appearing on Windows
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            kwargs["startupinfo"] = startupinfo

        if "bufsize" not in kwargs:
            kwargs["bufsize"] = 10**8
        if "stdin" not in kwargs:
            kwargs["stdin"] = subprocess.DEVNULL
        return func(commandList, **kwargs)

    return pipeWrapper


@pipeWrapper
def checkOutput(commandList, **kwargs):
    return subprocess.check_output(commandList, **kwargs)


def disableWhenEncoding(func):
    def decorator(self, *args, **kwargs):
        if self.encoding:
            return
        else:
            return func(self, *args, **kwargs)

    return decorator


def disableWhenOpeningProject(func):
    def decorator(self, *args, **kwargs):
        if self.core.openingProject:
            return
        else:
            return func(self, *args, **kwargs)

    return decorator


def rgbFromString(string):
    """Turns an RGB string like "255, 255, 255" into a tuple"""
    try:
        tup = tuple([int(i) for i in string.split(",")])
        if len(tup) != 3:
            raise ValueError
        for i in tup:
            if i > 255 or i < 0:
                raise ValueError
        return tup
    except Exception as e:
        log.warning(
            "Could not parse '%s' as a color (encountered %s).",
            string,
            type(e).__name__,
        )
        return (255, 255, 255)


def formatTraceback(tb=None):
    import traceback

    if tb is None:
        import sys

        tb = sys.exc_info()[2]
    return "Traceback:\n%s" % "\n".join(traceback.format_tb(tb))


def connectWidget(widget, func):
    unsupportedWidgets = ["QtWidgets.QFontComboBox"]
    if type(widget) == QtWidgets.QLineEdit:
        widget.textChanged.connect(func)
    elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox:
        widget.valueChanged.connect(func)
    elif type(widget) == QtWidgets.QCheckBox:
        widget.stateChanged.connect(func)
    elif type(widget) == QtWidgets.QComboBox:
        widget.currentIndexChanged.connect(func)
    elif type(widget) in unsupportedWidgets:
        log.info(
            "Could not connect %s using connectWidget()", str(widget.__class__.__name__)
        )
    else:
        log.warning("Failed to connect %s ", str(widget.__class__.__name__))
        return False
    return True


def setWidgetValue(widget, val):
    """Generic setValue method for use with any typical QtWidget"""
    log.verbose("Setting %s to %s" % (str(widget.__class__.__name__), val))
    if type(widget) == QtWidgets.QLineEdit:
        widget.setText(val)
    elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox:
        widget.setValue(val)
    elif type(widget) == QtWidgets.QCheckBox:
        widget.setChecked(val)
    elif type(widget) == QtWidgets.QComboBox:
        widget.setCurrentIndex(val)
    else:
        log.warning("Failed to set %s ", str(widget.__class__.__name__))
        return False
    return True


def getWidgetValue(widget):
    if type(widget) == QtWidgets.QLineEdit:
        return widget.text()
    elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox:
        return widget.value()
    elif type(widget) == QtWidgets.QCheckBox:
        return widget.isChecked()
    elif type(widget) == QtWidgets.QComboBox:
        return widget.currentIndex()


def randomColor():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
