#!/usr/bin/env python3

"""
global_spinbox.py
"""

__author__ = ["Benjamin Mummery", "Tiago Sarmento"]
__credits__ = ["Benjamin Mummery", "Dónal Murray", "Tim Powell", "Tiago Sarmento"]
__license__ = "GPL"
__version__ = "0.0.1"
__maintainer__ = "Tiago Sarmento"
__email__ = "tiago.sarmento@stfc.ac.uk"
__status__ = "Prototype"

from PySide2 import QtWidgets, QtGui, QtCore

# from global_widgets.global_typeval_popup import TypeValuePopup
from CommsCommon import ReadbackFormat
import logging


class signallingSpinBox(QtWidgets.QDoubleSpinBox):
    """the base class for all the spinboxes.
    Additional functionality:
         A popup to edit the value appears when the box is double clicked.
         A signal is emitted when the user presses up or down buttons.
    """

    manualChanged = QtCore.Signal()
    programmaticallyChanged = QtCore.Signal()

    def __init__(self, NativeUI, typeval_popup, label_text, min, max, initVal, step, decPlaces):
        super().__init__()
        self.lineEdit().installEventFilter(self)
        self.editable = True
        self.label_text, self.min, self.max, self.initVal, self.step, self.decPlaces = (
            label_text,
            min,
            max,
            initVal,
            step,
            decPlaces,
        )
        self.setRange(min, max)
        self.setSingleStep(step)
        self.setDecimals(decPlaces)
        self.setValue(initVal)
        self.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.NativeUI = NativeUI
        self.typeval_popup = (
            typeval_popup
        )  # TypeValuePopup(NativeUI, label_text, min, max, initVal, step, decPlaces)


    def setEditability(self, setBool):
        self.editable = setBool

    def okButtonPressed(self):
        """Ok button press applies changes in popup to the spin box, closes the popup, and emits a signal"""
        val = float(self.typeval_popup.lineEdit.text())
        self.setValue(val)
        self.typeval_popup.close()
        self.manualChanged.emit()

    def cancelButtonPressed(self):
        """Cancel button press reverts changes and closes popup"""
        self.typeval_popup.lineEdit.setText(self.typeval_popup.lineEdit.saveVal)
        self.typeval_popup.close()

    def stepBy(self, step):
        """Overrides stepBy to emit a signal when called"""
        value = self.value()
        #self.prevValue = value
        super(signallingSpinBox, self).stepBy(step)
        if self.value() != value:
            self.manualChanged.emit() # only emitted if value actually changes

    def set_value(self, value):
        self.setValue(value)
        self.programmaticallyChanged.emit()

    def eventFilter(self, source, event):
        """Overrides event filter to implement response to double click """
        if (
            source is self.lineEdit()
            and event.type() == QtCore.QEvent.MouseButtonPress
        ):
            if not self.editable:
                return
            self.typeval_popup.populatePopup(self, self.NativeUI.display_stack.currentWidget())
            self.NativeUI.display_stack.setCurrentWidget(self.typeval_popup)
            return True
        return False


class labelledSpin(QtWidgets.QWidget):
    """Combines signalling spin box with information relevant to its layout and to handle value updates.
    It is created by an information array which indicates labels, units, command type and code for value setting,
    and the range of permitted values"""

    def __init__(self, NativeUI, typeval_popup, infoArray, *args, **kwargs):
        super(labelledSpin, self).__init__(*args, **kwargs)

        self.NativeUI = NativeUI
        self.cmd_type, self.cmd_code = "", ""
        self.min, self.max, self.step = 0, 10000, 1
        self.initVal = 1
        self.currentDbValue = self.initVal
        self.decPlaces = 2
        self.label = "default"
        if len(infoArray) == 10:
            self.label, self.units, self.tag, self.cmd_type, self.cmd_code, self.min, self.max, self.initVal, self.step, self.decPlaces = (
                infoArray
            )
        elif len(infoArray) == 9:
            self.label, self.units, self.tag, self.cmd_type, self.cmd_code, self.min, self.max, self.step, self.decPlaces = (
                infoArray
            )
        elif len(infoArray) == 6:
            self.label, self.units, self.tag, self.cmd_type, self.cmd_code, self.decPlaces = infoArray
        elif len(infoArray) == 5:
            self.label, self.units, self.tag, self.cmd_type, self.cmd_code = infoArray
        elif len(infoArray) == 3:
            self.label, self.units, self.tag = infoArray
        else:
            raise Exception("wrong length for infoArray: %s\n%s" % (len(infoArray),infoArray))
        self.manuallyUpdated = False

        self.layout = QtWidgets.QHBoxLayout()
        textStyle = "color:" + NativeUI.colors["page_foreground"].name() + ";"

        # if self.label != "":
        self.nameLabel = QtWidgets.QLabel()
        self.nameLabel.setStyleSheet(textStyle)
        self.nameLabel.setFont(NativeUI.text_font)
        self.nameLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.nameLabel.setWordWrap(True)

        self.signalling_spin = signallingSpinBox(
            NativeUI,
            typeval_popup,
            self.label,
            self.min,
            self.max,
            self.initVal,
            self.step,
            self.decPlaces,
        )

        self.signalling_spin.setStyleSheet(
            "QDoubleSpinBox{"
            "border:none;"
            "}"
            "QDoubleSpinBox[bgColour='1']{"
            "    background-color:" + NativeUI.colors["page_foreground"].name() + ";"
            "}"
            "QDoubleSpinBox[bgColour='0']{"
            "    background-color:"
            + NativeUI.colors["button_background_disabled"].name()
            + ";"
            "}"
            "QDoubleSpinBox[textColour='0']{"
            "    color:" + NativeUI.colors["page_background"].name() + ";"
            "}"
            "QDoubleSpinBox[textColour='1']{"
            "    color:" + NativeUI.colors["page_foreground"].name() + ";"
            "}"
            "QDoubleSpinBox[textColour='2']{"
            "    color:" + NativeUI.colors[
            "red"].name() + ";"
            "}"
        )
        self.signalling_spin.setFont(NativeUI.text_font)
        self.signalling_spin.setProperty("textColour", "0")
        self.signalling_spin.setProperty("bgColour", "0")
        self.signalling_spin.setAlignment(QtCore.Qt.AlignCenter)
        if self.cmd_type == "":
            self.signalling_spin.setReadOnly(True)
            self.signalling_spin.setEditability(False)
            self.signalling_spin.style().polish(self.signalling_spin)

        self.unitLabel = QtWidgets.QLabel()
        self.unitLabel.setFont(NativeUI.text_font)
        self.unitLabel.setStyleSheet(textStyle)
        self.unitLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)

        self.widgetList = [self.nameLabel, self.signalling_spin, self.unitLabel]
        for widget in self.widgetList:
            self.layout.addWidget(widget)

        self.setLayout(self.layout)
        self.signalling_spin.manualChanged.connect(self.manualStep)
        self.signalling_spin.programmaticallyChanged.connect(self.manualStep)
        self.setEnabled(True)
        # self.signalling_spin.valueChanged.connect(self.valChange)

        self.localise_text(NativeUI.text)

    def manualStep(self):
        """Handle changes in value. Change colour if different to set value, set updating values."""
        if self.manuallyUpdated:
            roundVal = round(self.currentDbValue, self.decPlaces)
            if self.decPlaces == 0:
                roundVal = int(roundVal)
            if self.signalling_spin.value() == roundVal:
                self.signalling_spin.setProperty("textColour", "0")
                self.manuallyUpdated = False
                self.signalling_spin.style().polish(self.signalling_spin)
        else:
            self.signalling_spin.setProperty("textColour", "2")
            self.manuallyUpdated = True
            self.signalling_spin.style().polish(self.signalling_spin)
        return 0

    def isEnabled(self, *args, **kwargs):
        """Override isEnabled to return value of base spinbox"""
        return self.signalling_spin.isEnabled()

    def setEnabled(self, bool):
        """Override isEnabled to set value of base spinbox and modify colour"""
        self.signalling_spin.setEnabled(bool)
        self.signalling_spin.setProperty("bgColour", str(int(bool)))
        if bool == False:
            self.signalling_spin.setProperty("textColour", "1")
            self.signalling_spin.lineEdit().removeEventFilter(self.signalling_spin)
        else:
            self.signalling_spin.setProperty("textColour", str(int(not bool)))
            self.signalling_spin.lineEdit().installEventFilter(self.signalling_spin)

        self.signalling_spin.style().polish(self.signalling_spin)

    def update_value(self, db):
        if self.tag == "":
            return 0

        try:
            if 'SET_TARGET' in self.cmd_type and self.NativeUI.currentMode.replace("/", "_").replace("-", "_") not in self.cmd_type: # only update value if spin widget corresponds to current mode
                return 0
            else:
                newVal = db[self.tag]
        except KeyError:
            logging.debug(
                "No key %s in db. Is the microcontroller running?", self.tag
            )
            return 1

        if self.manuallyUpdated:
            return 0

        self.currentDbValue = newVal
        self.signalling_spin.setValue(float(newVal))

        if self.isEnabled():
            self.signalling_spin.setProperty("textColour", "0")
        else:
            self.signalling_spin.setProperty("textColour", "1")
        self.signalling_spin.style().polish(self.signalling_spin)
        return 0

    def insertWidget(self, widget):
        """Insert a widget with the nameLabel. Used to put a radio button in specific spin boxes"""
        self.insertedWidget = widget

        temp_widgets = [self.layout.takeAt(0) for i in range(self.layout.count())]
        temp_widgets = [temp_widgets[0], self.insertedWidget] + temp_widgets[1:]
        for w in temp_widgets:
            try:
                self.layout.addWidget(w)
            except TypeError:
                self.layout.addItem(w)
        self.setLayout(self.layout)
        return 0

    def get_value(self):
        return self.signalling_spin.value()

    def set_value(self, value):
        self.signalling_spin.setValue(value)
        if self.isEnabled():
            self.signalling_spin.setProperty("textColour", "0")
        else:
            self.signalling_spin.setProperty("textColour", "1")
        self.signalling_spin.style().polish(self.signalling_spin)
        return 0

    def set_maximum(self, max):
        """Update maximum and correct existing value"""
        self.max = max
        if self.signalling_spin.value() > self.max:
            self.signalling_spin.set_value(max)
        self.signalling_spin.setRange(self.min, self.max)
        return 0

    def set_minimum(self, min):
        """Update minimimum and correct existing value"""
        self.min = min
        if self.signalling_spin.value() < self.min:
            self.signalling_spin.stepBy(self.min - self.signalling_spin.value())
        self.signalling_spin.setRange(self.min, self.max)
        return 0

    def set_size(self, x: int, y: int, spacing: int = 5) -> int:
        """
        Set the size of the widget.
        Also rescale the elements within it to equally distribute the width
        """
        if x is not None:
            self.setFixedWidth(x)
            x_widget = int(0.9 * x / 8)

            if hasattr(self, "insertedWidget"):
                self.nameLabel.setFixedWidth(2 * x_widget)
                self.insertedWidget.setFixedWidth(x_widget)
            else:
                self.nameLabel.setFixedWidth(3 * x_widget)
            self.signalling_spin.setFixedWidth(4 * x_widget)
            self.unitLabel.setFixedWidth(1 * x_widget)
        if y is not None:
            self.setFixedHeight(y)
            for widget in self.widgetList:
                widget.setFixedHeight(y)

        self.nameLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        self.signalling_spin.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.unitLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.layout.setSpacing(0)
        return 0

    def localise_text(self, text: dict) -> int:
        """
        Source the text for the labels from the specified dictionary.
        """
        if self.label == "":
            self.nameLabel.setText("")
        else:
            self.nameLabel.setText(text[self.tag])

        if self.units == "":
            self.unitLabel.setText("")
        else:
            self.unitLabel.setText(text[self.units])

        return 0
