"""
/***************************************************************************
 TrilaterationDialog
                                 A QGIS plugin
 Topaze
                             -------------------
        begin                : 2022-09-29
        git sha              : $Format:%H$
        copyright            : (C) 2022 by Jean-Marie ARSAC
        email                : jmarsac@arsac.wf
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import json
import os
from pathlib import Path

from PyQt5 import QtCore, QtWidgets, uic
from qgis.core import Qgis, QgsProject
from qgis.PyQt.QtCore import QDir, QUrl

from topaze.calc.trilateration import Trilateration
from topaze.dlg_util import DlgUtil
from topaze.file_utils import FileUtils
from topaze.ptopo import Ptopo, PtopoConst
from topaze.toolbelt.i18n import tr
from topaze.toolbelt.log_handler import PlgLogger
from topaze.topaze_utils import TopazeUtils

FORM_CLASS, _ = uic.loadUiType(
    os.path.join(os.path.dirname(__file__), "triangulation_dialog.ui")
)


class TrilaterationDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(TrilaterationDialog, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)
        self.setWindowTitle(tr("Compute isolated point by trilateration"))
        self.comboBox_compute_station.currentTextChanged.connect(self.load_available)
        self.listWidget_available.currentTextChanged.connect(
            self.display_available_coordinates
        )
        self.listWidget_useful.currentTextChanged.connect(
            self.display_useful_coordinates
        )
        self.pushButton_use.clicked.connect(self.use_it)
        self.pushButton_unuse.clicked.connect(self.unuse_it)
        self.pushButton_use_all.clicked.connect(self.use_all)
        self.pushButton_unuse_all.clicked.connect(self.unuse_all)
        self.pushButton_compute.clicked.connect(self.on_pushbutton_compute_clicked)
        self.pushButton_quit.clicked.connect(self.on_pushbutton_quit_clicked)
        self.pushButton_save.clicked.connect(self.on_pushbutton_save_clicked)
        self._obs_array = None
        self.iface = None
        self._ptopo_array = None
        self._ptopo_layer = None

    def set_iface(self, iface):
        self.iface = iface

    def showDialog(self, obs_array, ptopo_array=None, ptopo_layer=None):
        self._obs_array = obs_array
        self._ptopo_array = ptopo_array
        self._ptopo_layer = ptopo_layer
        station_matricules = TopazeUtils.find_all_station_matricules_in_obs_array(
            obs_array
        )
        DlgUtil.load_combobox_list_array(
            self.comboBox_compute_station, station_matricules, False
        )
        self.show()

    def on_pushbutton_compute_clicked(self):
        try:
            to_compute = [self.comboBox_compute_station.currentText()]
            references = []
            if self.listWidget_useful.count() < 3:
                raise RuntimeError(
                    tr("At least 3 references are required to compute an trilateration")
                )
            for index in range(self.listWidget_useful.count()):
                item = self.listWidget_useful.item(index)
                references.append(item.text())
            # print(to_compute)
            # print(references)
            fulljsonfilepath = self.save_data_to_compute_trilateration(
                "trilateration.json", self._obs_array, to_compute, references
            )
            self.clear_coordinates_fields()
            if not fulljsonfilepath:
                raise RuntimeError(
                    tr("Failed to save data for computing trilateration")
                )
            out_dir = QgsProject.instance().homePath() + "/rapport/"
            # pt_raw, pt_cmp, lsq_dict = Trilateration.compute_trilateration_from_json(
            trilateration_result = Trilateration.compute_trilateration_from_json(
                Path(fulljsonfilepath), Path(out_dir)
            )
            x_raw = trilateration_result["result"].x0
            y_raw = trilateration_result["result"].y0
            z_raw = trilateration_result["result"].z0
            if x_raw is None and y_raw is None:
                return

            x_cmp = trilateration_result["result"].x
            y_cmp = trilateration_result["result"].y
            z_cmp = trilateration_result["result"].z
            if x_cmp is None and y_cmp is None:
                return

            if x_raw is not None and y_raw is not None:
                self.lineEdit_x_raw.setText(f"{x_raw:>16.4f}")
                self.lineEdit_y_raw.setText(f"{y_raw:>16.4f}")
                if z_raw is not None:
                    self.lineEdit_z_raw.setText(f"{z_raw:>16.4f}")
                else:
                    self.lineEdit_z_raw.setText("")
            if x_cmp is not None and y_cmp is not None:
                self.lineEdit_x_cmp.setText(f"{x_cmp:>16.4f}")
                self.lineEdit_y_cmp.setText(f"{y_cmp:>16.4f}")
                if z_cmp is not None:
                    self.lineEdit_z_cmp.setText(f"{z_cmp:>16.4f}")
                else:
                    self.lineEdit_z_cmp.setText("")
            if (
                trilateration_result["result"]
                and trilateration_result["result"].sigma_x is not None
                and trilateration_result["result"].sigma_y is not None
            ):
                self.lineEdit_emq_x.setText(
                    f"{trilateration_result['result'].sigma_x:>16.4f}"
                )
                self.lineEdit_emq_y.setText(
                    f"{trilateration_result['result'].sigma_y:>16.4f}"
                )
                if (
                    trilateration_result["result"]
                    and trilateration_result["result"].sigma_z is not None
                ):
                    self.lineEdit_emq_z.setText(
                        f"{trilateration_result['result'].sigma_z:>16.4f}"
                    )

            target_path = (
                QgsProject.instance().homePath() + "/rapport/trilateration_report.pdf"
            )
            msg1 = tr("Trilateration calculation of ") + to_compute[0]
            msg2 = tr(' report in folder <a href="{}">{}</a>').format(
                QUrl.fromLocalFile(target_path).toString(),
                QDir.toNativeSeparators(target_path),
            )
            PlgLogger.log(msg1 + msg2, log_level=3, push=True)
        except Exception as e:
            PlgLogger().log(str(e), log_level=2, push=True)
            return

    def on_pushbutton_quit_clicked(self):
        if FileUtils.exists_temp_file("trilateration.json"):
            FileUtils.remove_temp_file("trilateration.json")
        self.hide()

    def on_pushbutton_save_clicked(self):
        station_id = self.comboBox_compute_station.currentText()
        pt = Ptopo(
            matricule=station_id,
            type="R",
            x=float(self.lineEdit_x_cmp.text()),
            y=float(self.lineEdit_y_cmp.text()),
            z=(
                float(self.lineEdit_z_cmp.text())
                if self.lineEdit_z_cmp.text()
                else PtopoConst.UNKNOWN_Z
            ),
        )
        if TopazeUtils.ptopo_exists_in_array(pt.matricule, self._obs_array):
            TopazeUtils.update_xyz_in_array(
                self._obs_array, pt.matricule, pt.x, pt.y, pt.z
            )
        else:
            self._obs_array.append(pt)
        fid, created = TopazeUtils.upsert_ptopo_in_layer(pt, True, self.iface)
        if fid:
            msg1 = tr("Trilateration")
            if created:
                msg2 = tr("PTOPO #{} ({}) created.").format(str(fid), station_id)
            else:
                msg2 = tr("PTOPO #{} ({}) updated.").format(str(fid), station_id)
            self.iface.messageBar().pushMessage(msg1, msg2, Qgis.Success, 10)

    def load_available(self):
        station_matricule = self.comboBox_compute_station.currentText()
        if station_matricule and self._obs_array:
            reference_matricules = (
                TopazeUtils.find_all_reference_matricules_in_obs_array(
                    self._obs_array, station_matricule
                )
            )
            self.listWidget_available.clear()
            self.listWidget_useful.clear()
            DlgUtil.load_combobox_list_array(
                self.listWidget_available, reference_matricules, False
            )
            self.listWidget_available.setCurrentRow(0)
            self.listWidget_available.setFocus()

    def use_it(self):
        selectedItems = self.listWidget_available.selectedItems()
        if not selectedItems:
            return

        for item in selectedItems:
            self.listWidget_available.takeItem(self.listWidget_available.row(item))
            self.listWidget_useful.addItem(item)

        self.clear_coordinates_fields()

    def unuse_it(self):
        selectedItems = self.listWidget_useful.selectedItems()
        if not selectedItems:
            return

        for item in selectedItems:
            self.listWidget_useful.takeItem(self.listWidget_useful.row(item))
            self.listWidget_available.addItem(item)

        self.clear_coordinates_fields()

    def use_all(self):
        items = self.listWidget_available.findItems("", QtCore.Qt.MatchContains)
        if not items:
            return

        for item in items:
            self.listWidget_available.takeItem(self.listWidget_available.row(item))
            self.listWidget_useful.addItem(item)

        self.clear_coordinates_fields()

    def unuse_all(self):
        items = self.listWidget_useful.findItems("", QtCore.Qt.MatchContains)
        if not items:
            return

        for item in items:
            self.listWidget_useful.takeItem(self.listWidget_useful.row(item))
            self.listWidget_available.addItem(item)
        self.clear_coordinates_fields()

    def display_coordinates(self, matricule):
        try:
            # print(matricule)
            self.clear_coordinates_fields()
            pt = TopazeUtils.get_ptopo_by_matricule_in_obs_array(
                matricule, self._obs_array
            )
            if pt is None:
                pt = TopazeUtils.get_ptopo_by_matricule(
                    matricule, self._ptopo_array, self._ptopo_layer
                )
            if pt:
                self.lineEdit_x_raw.setText(str(pt.x))
                self.lineEdit_y_raw.setText(str(pt.y))
                self.lineEdit_z_raw.setText(str(pt.z))
        except Exception as e:
            print(str(e))

    def display_available_coordinates(self):
        try:
            matricule = self.listWidget_available.currentItem().text()
            self.display_coordinates(matricule)
        except Exception as e:
            print(str(e))

    def display_useful_coordinates(self):
        try:
            matricule = self.listWidget_useful.currentItem().text()
            self.display_coordinates(matricule)
        except Exception as e:
            print(str(e))

    def save_data_to_compute_trilateration(
        self, tmp_filename, obs_array, to_compute_array, reference_array
    ) -> str:
        data_dict = {"calcul": to_compute_array, "stations": [], "obs": []}

        stations = TopazeUtils.find_all_stations_in_obs_array(obs_array)
        for station in stations:
            if station.matricule in to_compute_array:
                dico = {"matricule": station.matricule}
                pt = TopazeUtils.get_ptopo_by_matricule_in_obs_array(
                    station.matricule, obs_array
                )
                if pt is None:
                    pt = TopazeUtils.get_ptopo_by_matricule(
                        station.matricule, self._ptopo_array, self._ptopo_layer
                    )
                if pt:
                    if not pt.z or pt.z <= PtopoConst.UNKNOWN_Z:
                        dico.update({"x": pt.x, "y": pt.y, "z": PtopoConst.NO_Z})
                    else:
                        dico.update({"x": pt.x, "y": pt.y, "z": pt.z})
                else:
                    dico.update(
                        {
                            "x": PtopoConst.NO_X,
                            "y": PtopoConst.NO_Y,
                            "z": PtopoConst.NO_Z,
                        }
                    )
                data_dict["stations"].append(dico)

                reference_matricules = (
                    TopazeUtils.find_all_reference_matricules_in_obs_array(
                        obs_array, station.matricule
                    )
                )
                for matricule in reference_matricules:
                    dico = {"matricule": matricule}
                    pt = TopazeUtils.get_ptopo_by_matricule_in_obs_array(
                        matricule, obs_array
                    )
                    if pt is None:
                        pt = TopazeUtils.get_ptopo_by_matricule(
                            matricule, self._ptopo_array, self._ptopo_layer
                        )
                    if pt:
                        if not pt.z or pt.z <= PtopoConst.UNKNOWN_Z:
                            dico.update({"x": pt.x, "y": pt.y, "z": PtopoConst.NO_Z})
                        else:
                            dico.update({"x": pt.x, "y": pt.y, "z": pt.z})
                    else:
                        dico.update(
                            {
                                "x": PtopoConst.NO_X,
                                "y": PtopoConst.NO_Y,
                                "z": PtopoConst.NO_Z,
                            }
                        )
                    if dico not in data_dict["stations"]:
                        data_dict["stations"].append(dico)
                references = TopazeUtils.find_all_references_in_obs_array(
                    obs_array, station.matricule
                )
                for ref in references:
                    if (
                        ref.matricule in to_compute_array
                        or ref.matricule in reference_array
                    ):
                        dico = {"origine": station.matricule, "hi": station.hi}
                        dico.update(
                            {
                                "origine": station.matricule,
                                "type": "reference",
                                "hi": station.hi,
                                "cible": ref.matricule,
                                "ah": ref.ah,
                                "av": ref.av,
                                "di": ref.di,
                                "hp": ref.hp,
                            }
                        )
                        data_dict["obs"].append(dico)
        data_s = json.dumps(data_dict, indent=4)
        fullfilepath = FileUtils.create_temp_file(tmp_filename, data_s)
        return fullfilepath

    def clear_coordinates_fields(self):
        self.lineEdit_x_raw.setText("")
        self.lineEdit_y_raw.setText("")
        self.lineEdit_z_raw.setText("")
        self.lineEdit_x_cmp.setText("")
        self.lineEdit_y_cmp.setText("")
        self.lineEdit_z_cmp.setText("")
        self.lineEdit_emq_x.setText("")
        self.lineEdit_emq_y.setText("")
        self.lineEdit_emq_z.setText("")
