# GridCal
# Copyright (C) 2015 - 2024 Santiago Peñate Vera
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
import numpy as np

from typing import Union, List
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QPushButton, QVBoxLayout, QDialog, QLabel, QDoubleSpinBox, QComboBox, QCheckBox
from GridCal.Gui.gui_functions import get_list_model
from GridCal.Gui.messages import error_msg
from GridCalEngine.Devices.Branches.line import Line, SequenceLineType, OverheadLineType, UndergroundLineType


class LineEditor(QDialog):
    """
    LineEditor
    """

    def __init__(self,
                 line: Line,
                 Sbase=100,
                 frequency=50,
                 templates: Union[List[Union[SequenceLineType, OverheadLineType, UndergroundLineType]], None] = None,
                 current_template=None):
        """
        Line Editor constructor
        :param line: Branch object to update
        :param templates: List of templates
        :param Sbase: Base power in MVA
        """
        super(LineEditor, self).__init__()

        # keep pointer to the line object
        self.line = line

        self.Sbase = Sbase

        self.frequency = frequency

        self.templates = templates

        self.current_template = current_template

        self.selected_template = None

        self.setObjectName("self")

        self.setContextMenuPolicy(Qt.NoContextMenu)

        self.layout = QVBoxLayout(self)

        # ------------------------------------------------------------------------------------------
        # Set the object values
        # ------------------------------------------------------------------------------------------

        Vf = self.line.bus_from.Vnom
        # Vt = self.line.bus_to.Vnom

        if Vf <= 0.0:
            error_msg(f"Vnom in bus {self.line.bus_from} is {Vf}\n"
                      f"That causes an infinite base admittance.\n"
                      f"The process has been aborted.\n"
                      f"Please correct the data and try again.",
                      title="Line editor initialization")
            return

        Zbase = (Vf * Vf) / self.Sbase
        Ybase = 1 / Zbase
        length = self.line.length

        if length == 0:
            length = 1.0

        R = self.line.R * Zbase / length
        X = self.line.X * Zbase / length
        B = self.line.B * Ybase / length
        I = np.round(self.line.rate / (Vf * 1.73205080757), 6)  # current in KA

        # ------------------------------------------------------------------------------------------

        # catalogue
        self.catalogue_combo = QComboBox()
        if self.templates is not None:
            if len(self.templates) > 0:
                self.catalogue_combo.setModel(get_list_model(self.templates))

                if self.current_template is not None:
                    try:
                        idx = self.templates.index(self.current_template)
                        self.catalogue_combo.setCurrentIndex(idx)

                        if isinstance(self.current_template, SequenceLineType):
                            I = self.current_template.Imax
                            R = self.current_template.R
                            X = self.current_template.X
                            B = self.current_template.B

                        if isinstance(self.current_template, UndergroundLineType):
                            I = self.current_template.Imax
                            R = self.current_template.R
                            X = self.current_template.X
                            B = self.current_template.B

                        elif isinstance(self.current_template, OverheadLineType):
                            I = self.current_template.Imax
                            R = self.current_template.R1
                            X = self.current_template.X1
                            B = self.current_template.Bsh1

                    except:
                        pass

        # load template
        self.load_template_btn = QPushButton()
        self.load_template_btn.setText('Load template values')
        self.load_template_btn.clicked.connect(self.load_template_btn_click)

        # line length
        self.l_spinner = QDoubleSpinBox()
        self.l_spinner.setMinimum(0)
        self.l_spinner.setMaximum(9999999)
        self.l_spinner.setDecimals(6)
        self.l_spinner.setValue(length)
        self.l_spinner.setSuffix(' Km')

        # Max current
        self.i_spinner = QDoubleSpinBox()
        self.i_spinner.setMinimum(0)
        self.i_spinner.setMaximum(9999999)
        self.i_spinner.setDecimals(2)
        self.i_spinner.setValue(I)
        self.i_spinner.setSuffix(' KA')

        # R
        self.r_spinner = QDoubleSpinBox()
        self.r_spinner.setMinimum(0)
        self.r_spinner.setMaximum(9999999)
        self.r_spinner.setDecimals(6)
        self.r_spinner.setValue(R)
        self.r_spinner.setSuffix(' Ω/Km')

        # X
        self.x_spinner = QDoubleSpinBox()
        self.x_spinner.setMinimum(0)
        self.x_spinner.setMaximum(9999999)
        self.x_spinner.setDecimals(6)
        self.x_spinner.setValue(X)
        self.x_spinner.setSuffix(' Ω/Km')

        # B
        self.b_spinner = QDoubleSpinBox()
        self.b_spinner.setMinimum(0)
        self.b_spinner.setMaximum(9999999)
        self.b_spinner.setDecimals(6)
        self.b_spinner.setValue(B)
        self.b_spinner.setSuffix(" uS/Km")

        # apply to profile
        self.apply_to_profile = QCheckBox()
        self.apply_to_profile.setToolTip("Apply the newly computed values like the rating to the profile")
        self.apply_to_profile.setChecked(True)
        self.apply_to_profile.setText("Apply to profiles")

        # accept button
        self.accept_btn = QPushButton()
        self.accept_btn.setText('Accept')
        self.accept_btn.clicked.connect(self.accept_click)

        # add all to the GUI
        if templates is not None:
            self.layout.addWidget(QLabel("Available templates"))
            self.layout.addWidget(self.catalogue_combo)
            self.layout.addWidget(self.load_template_btn)
            self.layout.addWidget(QLabel(""))

        self.layout.addWidget(QLabel("L: Line length"))
        self.layout.addWidget(self.l_spinner)

        self.layout.addWidget(QLabel("Imax: Max. current @" + str(int(Vf)) + " [KV]"))
        self.layout.addWidget(self.i_spinner)

        self.layout.addWidget(QLabel("R: Resistance"))
        self.layout.addWidget(self.r_spinner)

        self.layout.addWidget(QLabel("X: Inductance"))
        self.layout.addWidget(self.x_spinner)

        self.layout.addWidget(QLabel("S: Susceptance"))
        self.layout.addWidget(self.b_spinner)

        self.layout.addWidget(self.apply_to_profile)

        self.layout.addWidget(self.accept_btn)

        self.setLayout(self.layout)

        self.setWindowTitle('Line editor')

    def accept_click(self):
        """
        Set the values
        :return:
        """

        if self.selected_template is not None:
            self.line.length = self.l_spinner.value()
            self.line.apply_template(self.selected_template, Sbase=self.Sbase)
        else:
            wf = 2 * np.pi * self.frequency
            self.line.fill_design_properties(
                r_ohm=self.r_spinner.value(),  # ohm / km
                x_ohm=self.x_spinner.value(),  # ohm / km
                c_nf=self.b_spinner.value() * 1e3 / wf,  # nF / km
                length=self.l_spinner.value(),  # km
                Imax=self.i_spinner.value(),  # KA
                freq=self.frequency,  # Hz
                Sbase=self.Sbase,  # MVA
                apply_to_profile=self.apply_to_profile.isChecked()
            )

        self.accept()

    def load_template(self, template: Union[SequenceLineType, OverheadLineType, UndergroundLineType]):
        """
        Load a template in the editor
        :param template: line compatible template
        :return:
        """

        if isinstance(template, SequenceLineType):
            self.i_spinner.setValue(template.Imax)
            self.r_spinner.setValue(template.R)
            self.x_spinner.setValue(template.X)
            self.b_spinner.setValue(template.B)

            self.selected_template = template

        elif isinstance(template, UndergroundLineType):
            self.i_spinner.setValue(template.Imax)
            self.r_spinner.setValue(template.R)
            self.x_spinner.setValue(template.X)
            self.b_spinner.setValue(template.B)

            self.selected_template = template

        elif isinstance(template, OverheadLineType):
            self.i_spinner.setValue(template.Imax)
            self.r_spinner.setValue(template.R1)
            self.x_spinner.setValue(template.X1)
            self.b_spinner.setValue(template.Bsh1)

            self.selected_template = template

    def load_template_btn_click(self):
        """
        Accept template values
        """

        if self.templates is not None:
            idx = self.catalogue_combo.currentIndex()

            if idx > -1:
                self.load_template(template=self.templates[idx])
