from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QSpacerItem, QSpinBox, QDoubleSpinBox
from PyQt6.QtWidgets import QGridLayout, QMenu, QListWidget, QPushButton, QFormLayout, QDialog, QDialogButtonBox

"""
QWidget class that contains a variable amount of key - input pairs.
One pair per line in a dense layout
pairs. The value may be text - line edit, float - qdoublespinbox and int - qspinbox.
"""

class MetadataInput(QWidget):
    def __init__(self, elements: list[tuple[str, str]]|dict[str,str]=None):
        super().__init__()
        # set layout
        self.l_vbox = QVBoxLayout()
        self.l_vbox.addWidget(QLabel("Measurement Metadata"))
        self.l_grid = QGridLayout()
        self.l_grid.setColumnMinimumWidth(0, 100)
        self.l_grid.setColumnMinimumWidth(1, 100)
        self.l_grid.setContentsMargins(0, 0, 0, 0)
        self.l_grid.setSpacing(0)
        # first row: key value <new element button>
        self.l_grid.addWidget(QLabel("Key"), 0, 0)
        self.l_grid.addWidget(QLabel("Value"), 0, 1)
        self.btn_new_element = QPushButton("+")
        self.btn_new_element.setFixedSize(20, 20)
        self.btn_new_element.clicked.connect(self.add_element_dialog)
        self.l_grid.addWidget(self.btn_new_element, 0, 2)

        # key-value widgets
        self.ws_elements = {}
        if type(elements) == dict:
            for (n, v) in elements.items():
                self.add_element(n, v)
        elif type(elements) == list:
            for (n, v) in elements:
                self.add_element(n, v)
        self.l_grid.setContentsMargins(4, 4, 4, 4)
        self.l_grid.setSpacing(4)
        self.l_vbox.addLayout(self.l_grid)
        self.setLayout(self.l_vbox)
        self.layout_changed()  # call even when no element was added

    def layout_changed(self):
        # add stretch to the last row
        for r in range(self.l_grid.rowCount()):
            self.l_grid.setRowStretch(r, 0)
        self.l_grid.setRowStretch(self.l_grid.rowCount(), 1)

    def add_element_dialog(self):
        """
        Prompt for a new key-value pair using a Dialog having a form
        """
        dialog = QDialog(self)
        dialog.setWindowTitle("Add new field")
        dialog.layout = QFormLayout()
        dialog.key_input = QLineEdit()
        dialog.value_input = QLineEdit()
        dialog.layout.addRow("Key", dialog.key_input)
        dialog.layout.addRow("Value", dialog.value_input)

        # ok and cancel
        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        buttons.accepted.connect(dialog.accept)
        buttons.rejected.connect(dialog.reject)
        dialog.layout.addRow(buttons)
        dialog.setLayout(dialog.layout)

        ret = dialog.exec()
        if ret == QDialog.DialogCode.Accepted:
            self.add_element(dialog.key_input.text(), dialog.value_input.text())

    def add_element(self, key, init_val=""):
        if key in self.ws_elements:
            raise RuntimeError(f"Can not add field '{key}', it already exists")
        if key == "":
            raise RuntimeError(f"Can not add field with empty key")
        row = self.l_grid.rowCount()
        w_label = QLabel(key)
        self.l_grid.addWidget(w_label, row, 0)

        w_input= QLineEdit(str(init_val))

        self.l_grid.addWidget(w_input, row, 1)

        # Add a small button with an X from the theme
        btn_remove = QPushButton("X")
        btn_remove.setFixedSize(20, 20)
        btn_remove.clicked.connect(lambda: self.remove_element(key))
        self.l_grid.addWidget(btn_remove, row, 2)

        self.ws_elements[key] = (w_label, w_input, btn_remove)
        self.layout_changed()

    def remove_element(self, name):
        if name not in self.ws_elements:
            raise RuntimeError(f"Can not remove field '{name}', it does not exist")
        for w in self.ws_elements[name]:
            self.l_grid.removeWidget(w)
        del self.ws_elements[name]
        self.layout_changed()


    def set_from_dict(self, d: dict[str, str]):
        """
        Set the widgets from the dictionary
        Parameters
        ----------
        d
            Dictionary of key-value pairs
        Returns
        -------
        None
        """
        # first remove widgets not in new dict
        for key in self.ws_elements.keys():
            if key not in d:
                self.remove_element(key)
        self.update_from_dict(d)

    def update_from_dict(self, d: dict[str, str]):
        """
        Update widgets from the dictionary.
        New values will be added, no values will be removed
        Parameters
        ----------
        d
            Dictionary of key-value pairs
        Returns
        -------
        None
        """
        for key, value in d.items():
            if key not in self.ws_elements:
                self.add_element(key, value)
            else:
                self.ws_elements[key][1].setText(value)

    def get_dict(self):
        """
        Returns
        -------
        Dictionary of all key-values pairs
        """
        d = {}
        for name, (w_label, w_input, _) in self.ws_elements.items():
            key = w_label.text()
            value = w_input.text()
            d[key] = value
        return d