# encoding=utf-8
# coding=utf-8
"""
Class to provide an UI for the export to TSV/CSV format
"""

__author__ = "Roland Trouville"
__copyright__ = "Copyright 2015+, Consortium MonPaGe"
__license__ = "Creative Commons 4.0 By-Nc-Sa"
__maintainer__ = "Roland Trouville"
__email__ = "contact.monpage@gmail.com"
__status__ = "Production"

import os

from PySide6.QtCore import QDir
from PySide6.QtWidgets import (
	QAbstractItemView,
	QApplication,
	QFileDialog,
	QGroupBox,
	QLabel,
	QProgressBar,
	QPushButton,
	QTreeWidget,
	QTreeWidgetItem,
	QWidget,
)

from export.export_csv import ExportCSV
from tools.csv_manager import CSVManager
from tools.db_manager import DBManager
from tools.display_tools import DisplayTools
from tools.general_tools import GeneralTools
from tools.preferences_tools import PreferencesTools


class CSVExportWindow(QWidget):
	parent = None
	output_dir = None
	new_sub_dir = None
	participant_list = None
	file_list = None
	script_list = None
	export_list = None
	pbar = None
	analyses = []

	labels = None
	answers = None

	def __init__(self, parent):
		super(CSVExportWindow, self).__init__()
		self.parent = parent
		self.init_ui()
		self.load_participant_list()
		self.load_export_list()
		self.show()

	def init_ui(self):
		"""
		Initialize the UI
		:return: None
		"""
		self.setGeometry(80, 80, 500, 350)
		self.setFixedSize(500, 350)
		self.setWindowTitle("MonPaGe Export en CSV")

		gtmp = QGroupBox("Répertoire de sortie", self)
		gtmp.resize(460, 30)
		gtmp.move(10, 10)

		self.output_dir = QLabel(
			PreferencesTools.get_preference_directory_path("csv_export_pref_path"), gtmp
		)
		self.output_dir.resize(400, 20)
		self.output_dir.move(10, 10)

		tmp = QPushButton("...", gtmp)
		tmp.move(410, 10)
		tmp.resize(40, 20)
		tmp.setToolTip("Choisir le répertoire de destination")
		DisplayTools.set_font_size(tmp, "lab_export_browsebtn")
		tmp.clicked.connect(self.change_output_folder)

		gtmp = QGroupBox("Participant(s)", self)
		gtmp.resize(230, 260)
		gtmp.move(10, 45)

		self.participant_list = QTreeWidget(gtmp)
		self.participant_list.setSelectionMode(
			QAbstractItemView.SelectionMode.ExtendedSelection
		)
		self.participant_list.setColumnCount(1)
		self.participant_list.setColumnWidth(0, 210)
		self.participant_list.setHeaderLabels(["Participant"])

		self.participant_list.resize(220, 240)
		self.participant_list.move(5, 15)

		gtmp = QGroupBox("Données", self)
		gtmp.resize(230, 260)
		gtmp.move(240, 45)

		self.export_list = QTreeWidget(gtmp)
		self.export_list.setSelectionMode(
			QAbstractItemView.SelectionMode.ExtendedSelection
		)
		self.export_list.setColumnCount(2)
		self.export_list.setColumnWidth(0, 10)
		self.export_list.setColumnWidth(1, 200)
		self.export_list.setHeaderLabels(["", "Analyse"])
		self.export_list.resize(220, 240)
		self.export_list.move(5, 15)

		self.pbar = QProgressBar(self)
		self.pbar.move(5, 315)
		self.pbar.setTextVisible(False)
		self.pbar.resize(370, 25)

		tmp = QPushButton("Exporter", self)
		tmp.move(380, 315)
		tmp.resize(100, 25)
		DisplayTools.set_font_size(tmp, "lab_export_gobtn")
		tmp.clicked.connect(self.export_data_to_csv)

	def change_output_folder(self):
		"""Open a folder selection dialog and update the output directory preference."""
		filename = QDir.toNativeSeparators(
			QFileDialog.getExistingDirectory(
				self, "Select Directory", self.output_dir.text()
			)
		)
		if filename != "":
			self.output_dir.setText(filename)
			PreferencesTools.set_preference_value("csv_export_pref_path", filename)

	def create_output_sub_folder(self):
		"""Create a new subfolder within the selected output directory and update the preference path."""
		subdir_name = str(self.new_sub_dir.text()).strip()
		if subdir_name != "":
			target_dir = str(self.output_dir.text()) + os.path.sep + subdir_name
			if not os.path.isdir(target_dir):
				os.makedirs(target_dir)

			self.output_dir.setText(target_dir)
			PreferencesTools.set_preference_value("praat_export_pref_path", target_dir)
			self.new_sub_dir.setText("")

	def load_participant_list(self):
		"""Load and display the list of participants in the UI, showing age or missing data warnings."""
		participants = DisplayTools.get_participant_list()
		self.participant_list.clear()
		for p in participants:
			if p[1] is not None:
				tmp = p[0] + " - (" + p[1] + " - " + p[2] + " ans)"
			else:
				tmp = p[0] + " - (! data.csv manquant !)"

			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, tmp)
			self.participant_list.addTopLevelItem(current_dir_treeitem)

	def load_export_list(self):
		"""Populate the export list UI component with predefined export types and their labels."""
		self.export_list.clear()
		data = [
			["qa_pneumo", "PneumoPhonatoire"],
			["qa_diado", "Diadoco"],
			["qa_phrases", "Phrases"],
			["qa_texte", "Texte"],
			["int", "Intelligibilite"],
			["pw", "Pseudos Mots"],
			["pw_count", "Decompte Pseudos Mots"],
		]

		for a in data:
			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, a[0])
			current_dir_treeitem.setText(1, a[1])
			self.export_list.addTopLevelItem(current_dir_treeitem)

	def export_data_to_csv(self):
		"""Export selected participant data to CSV files in the specified output directory.

		Checks if the output directory exists and if participants and export types are selected.
		Exports data for each selected export type and participant, showing progress via a progress bar.
		Handles file locking errors and formats data with appropriate labels before writing.
		Notifies the user when the export process is complete.
		"""
		target_dir = str(self.output_dir.text())
		if not os.path.isdir(target_dir):
			GeneralTools.alert_box(
				self,
				None,
				"Le répertoire de sortie choisi n'existe pas ou est inaccessible",
			)
			return

		selected_participants = self.participant_list.selectedItems()
		if len(selected_participants) < 1:
			GeneralTools.alert_box(
				self,
				"Impossible de procéder à l'export",
				"Vous devez sélectionner au moins un participant",
			)
			return

		selected_export = self.export_list.selectedItems()
		if len(selected_export) < 1:
			GeneralTools.alert_box(
				self,
				"Impossible de procéder à l'export",
				"Vous devez sélectionner au moins un type de données",
			)
			return

		self.pbar.setValue(0)
		max = len(selected_export) * len(selected_participants)
		self.pbar.setMaximum(max)

		for t in selected_export:
			data = []
			headers = []
			data_type = str(t.text(0))
			label = str(t.text(1)).replace(",", "_").replace(" ", "_")
			tmp = QFileDialog.getSaveFileName(
				self,
				"Enregistrer les données brutes",
				target_dir + os.path.sep + "export_" + label + ".csv",
				".csv",
			)
			filename = str(QDir.toNativeSeparators(tmp[0]))

			if filename != "":
				PreferencesTools.set_preference_directory_path_from_filename(
					"csv_export_pref_path", filename
				)

				for p in selected_participants:
					tmp = str(p.text(0)).split("-")
					participant_code = str(tmp[0]).strip()

					res = ExportCSV.get_export_data(data_type, participant_code)
					headers = res[0]
					for r in res[1]:
						data.append(r)

					self.pbar.setValue(self.pbar.value() + 1)
					QApplication.processEvents()  # Processing the other application events to allow for interrupting

				try:
					CSVManager.write_file(filename, "\t", [headers], "w+")
				except IOError:
					GeneralTools.alert_box(
						self,
						"Fichier verouillé",
						"Impossible d'acceder au fichier "
						+ filename
						+ ", verifiez si il n'est pas déjà ouvert ailleurs",
						True,
					)
					return

				if "qa_" in data_type:
					idx_question_id = CSVExportWindow.__get_index_of__(
						headers, "question_id"
					)
					idx_value = CSVExportWindow.__get_index_of__(headers, "value")
					idx_label_answer = CSVExportWindow.__get_index_of__(
						headers, "label_answer"
					)

					for d in data:
						qid = int(d[idx_question_id])
						val = int(d[idx_value])
						tmp = CSVExportWindow.__get_answer__(("answer", qid, val))
						if tmp != "" and tmp is not None:
							d[idx_label_answer] = tmp.strip()

				if data_type == "pw":
					CSVExportWindow.__load_labels__()

					idx_state = CSVExportWindow.__get_index_of__(headers, "state")
					idx_state_label = CSVExportWindow.__get_index_of__(
						headers, "state_label"
					)
					idx_effort = CSVExportWindow.__get_index_of__(headers, "effort")
					idx_effort_label = CSVExportWindow.__get_index_of__(
						headers, "effort_label"
					)
					idx_inversion = CSVExportWindow.__get_index_of__(
						headers, "inversion"
					)
					idx_inversion_label = CSVExportWindow.__get_index_of__(
						headers, "inversion_label"
					)
					idx_ajout = CSVExportWindow.__get_index_of__(headers, "ajout")
					idx_ajout_label = CSVExportWindow.__get_index_of__(
						headers, "ajout_label"
					)
					idx_error_type = CSVExportWindow.__get_index_of__(
						headers, "error_type"
					)
					idx_consonnant = CSVExportWindow.__get_index_of__(
						headers, "consonnant"
					)
					idx_type = CSVExportWindow.__get_index_of__(headers, "type")
					idx_error_type_label = CSVExportWindow.__get_index_of__(
						headers, "error_type_label"
					)
					idx_error_nature = CSVExportWindow.__get_index_of__(
						headers, "error_nature"
					)
					idx_error_nature_label = CSVExportWindow.__get_index_of__(
						headers, "error_nature_label"
					)

					for d in data:
						d[idx_state_label] = CSVExportWindow.__get_label__(
							("state", int(d[idx_state]))
						)
						d[idx_effort_label] = CSVExportWindow.__get_label__(
							("bool", int(d[idx_effort]))
						)
						d[idx_inversion_label] = CSVExportWindow.__get_label__(
							("bool", int(d[idx_inversion]))
						)
						d[idx_ajout_label] = CSVExportWindow.__get_label__(
							("bool", int(d[idx_ajout]))
						)
						d[idx_error_type_label] = CSVExportWindow.__get_label__(
							("error_type", int(d[idx_error_type]), d[idx_type])
						)
						if str(d[idx_error_type]) != "0":
							d[idx_error_nature_label] = CSVExportWindow.__get_label__(
								(
									"error_nature",
									int(d[idx_error_nature]),
									d[idx_type],
									int(d[idx_consonnant]),
								)
							)

				CSVManager.add_lines(filename, "\t", data)

			else:
				self.pbar.setValue(self.pbar.value() + len(selected_participants))
				QApplication.processEvents()  # Processing the other application events to allow for interrupting

		GeneralTools.alert_box(
			self, "Export terminé", "Les fichiers ont été crées dans " + target_dir
		)

	@staticmethod
	def __get_index_of__(headers, fieldname):
		"""
		Return the index of the specified fieldname in the headers list.

		Args:
			headers (list of str): The list of header field names.
			fieldname (str): The field name to find.

		Returns:
			int: The index of the fieldname in headers, or -1 if not found.
		"""
		for i in range(0, len(headers)):
			if headers[i] == fieldname:
				return i
		return -1

	@staticmethod
	def __load_labels__():
		"""Initialize and populate the labels dictionary with predefined label mappings for states, booleans, error types, and error natures."""
		CSVExportWindow.labels = {}
		CSVExportWindow.labels[("state", -1)] = "Probleme de son"
		CSVExportWindow.labels[("state", 1)] = "Incorrect"
		CSVExportWindow.labels[("bool", 0)] = "Non"
		CSVExportWindow.labels[("bool", 1)] = "Oui"
		types_simples = ["a", "b", "c", "d", "e", "f"]
		for t in types_simples:
			CSVExportWindow.labels[("error_type", 1, t)] = "Omission"
			CSVExportWindow.labels[("error_type", 2, t)] = "Inintelligible"
			CSVExportWindow.labels[("error_type", 3, t)] = "Distorsion/substitution"

		CSVExportWindow.labels[("error_type", 1, "g")] = "Omission"
		CSVExportWindow.labels[("error_type", 2, "g")] = (
			"Distortion/erreur articulatoire"
		)
		CSVExportWindow.labels[("error_type", 4, "g")] = "non identifiable"

		data = [
			(1, "Aperture", ""),
			(2, "Lieu d'articulation", ""),
			(3, "Arrondissement", ""),
			(4, "Nasalite", ""),
			(5, "Voisement", ""),
			(6, "Autre", ""),
		]
		tmp = {}
		CSVExportWindow.__create_all_combinations__(data, "", 0, tmp, " ")
		for k in tmp.keys():
			for t in types_simples:
				CSVExportWindow.labels[("error_nature", k, t, 0)] = tmp[k]

		data = [
			(1, "Lieu d'articulation", ""),
			(2, "Mode d'articulation", ""),
			(3, "Voisement", ""),
			(4, "Nasalite", ""),
			(5, "Autre", ""),
		]
		tmp = {}
		CSVExportWindow.__create_all_combinations__(data, "", 0, tmp, " ")
		for k in tmp.keys():
			for t in types_simples:
				CSVExportWindow.labels[("error_nature", k, t, 1)] = tmp[k]

		data = [
			(1, "X", "_"),
			(2, "X", "_"),
			(3, "X", "_"),
			(4, "X", "_"),
		]
		tmp = {}
		CSVExportWindow.__create_all_combinations__(data, "", 0, tmp, "")
		for k in tmp.keys():
			CSVExportWindow.labels[("error_nature", k, "g", 0)] = tmp[k]
			CSVExportWindow.labels[("error_nature", k, "g", 1)] = tmp[k]

	@staticmethod
	def __get_label__(key):
		"""
		Retrieve the label associated with the given key from the labels dictionary.

		If the labels dictionary is not yet initialized, it loads the labels first.

		Args:
			key (tuple): The key used to look up the label in the labels dictionary.

		Returns:
			str: The label string corresponding to the key, or an empty string if not found.
		"""
		if CSVExportWindow.labels is None:
			CSVExportWindow.__load_labels__()
		if key in CSVExportWindow.labels:
			return CSVExportWindow.labels[key]
		return ""

	@staticmethod
	def __load_answers__():
		"""
		Load answer labels from the database and populate the answers dictionary.

		Fetches multichoice answers from the database, generates all label combinations,
		and stores them in the class-level answers dictionary.

		No parameters or return value.
		"""
		CSVExportWindow.answers = {}
		with DBManager.get_cotation_db() as cotation_db:
			tmp_db = {}
			result = cotation_db.execute(
				"select a.question_id, a.value, a.label from answer as a inner join question as q on q.id = a.question_id "
				"where q.multichoice = 1"
			)
			for r in result:
				if r[0] not in tmp_db:
					tmp_db[r[0]] = []

				tmp_db[r[0]].append((int(r[1]), r[2], ""))

			for k in tmp_db.keys():
				tmp = {}
				CSVExportWindow.__create_all_combinations__(tmp_db[k], "", 0, tmp, " ")
				for k2 in tmp.keys():
					CSVExportWindow.answers[("answer", k, k2)] = tmp[k2]

	@staticmethod
	def __get_answer__(key):
		"""
		Retrieve the answer label associated with the given key from the answers dictionary.

		If the answers dictionary is not yet initialized, it loads the answers first.

		Args:
			key (tuple): The key used to look up the answer label.

		Returns:
			str: The answer label corresponding to the key, or an empty string if not found.
		"""
		if CSVExportWindow.answers is None:
			CSVExportWindow.__load_answers__()
		if key in CSVExportWindow.answers:
			return CSVExportWindow.answers[key]
		return ""

	@staticmethod
	def __create_all_combinations__(data, base_str, base_val, result, sep=" "):
		"""
		Recursively generate all combinations of labels from the given data and store them in the result dictionary.

		Each combination is represented by a numeric key (`base_val`) mapped to a concatenated label string (`base_str`).
		The method uses bitwise operations to calculate unique keys for each label combination.

		Args:
			data (list of tuples): List of tuples where each tuple contains an integer key, a label string, and an alternate label string.
			base_str (str): The current concatenated label string (used in recursion).
			base_val (int): The current numeric key representing the combination (used in recursion).
			result (dict): Dictionary to store the generated combinations as {int: str}.
			sep (str, optional): Separator string used between labels. Defaults to a single space.
		"""
		result[base_val] = base_str.strip()
		if len(data) > 0:
			if base_str == "":
				new_str = data[0][1]
				old_str = data[0][2]
			else:
				new_str = data[0][1] + sep + base_str
				old_str = data[0][2] + sep + base_str

			CSVExportWindow.__create_all_combinations__(
				data[1:], old_str, base_val, result, sep
			)
			new_val = base_val + pow(2, data[0][0] - 1)
			CSVExportWindow.__create_all_combinations__(
				data[1:], new_str, new_val, result, sep
			)
