# encoding=utf-8
"""
Cotation main window class file
"""

__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 logging
import os
from enum import Enum
from typing import Optional

try:
	from typing import override
except ImportError:
	from typing_extensions import override  # noqa: F401

from PySide6.QtCore import QDir, Qt
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (
	QApplication,
	QCheckBox,
	QComboBox,
	QFileDialog,
	QLineEdit,
	QMainWindow,
	QMenu,
	QPushButton,
	QTreeWidget,
	QTreeWidgetItem,
)

from cotation.acoustic.cotation_acoustic_window import CotationAcousticWindow

# from cotation.acoustic.praat_import_window import PraatImportWindow
from cotation.cotation_diadoco import CotationDiadocoWindow
from cotation.cotation_intelligibilite import CotationIntelligibiliteWindow
from cotation.cotation_phrases import CotationPhrasesWindow
from cotation.cotation_pneumophonatoire import CotationPneumoPhonatoireWindow
from cotation.cotation_pseudomots import CotationPseudoMotsWindow
from cotation.cotation_texte import CotationTexteWindow
from export.csv_export_window import CSVExportWindow
from export.export_csv import ExportCSV
from export.praat_export_window import (
	PraatExportWindow,
	PraatExportWindowAcoustic,
	PraatExportWindowResearch,
)
from reporting.indicator_reporting import IndicatorReporting
from reporting.reporting_window import ReportingWindow
from tools.audio_manager import AudioManager
from tools.display_tools import DisplayTools
from tools.general_tools import GeneralTools
from tools.migration_tools import MigrationTools
from tools.options import Options
from tools.participant_manager import ParticipantManager
from tools.preferences_tools import PreferencesTools
from tools.results_merging_window import ResultsMergingWindow
from ui.LoadedWindow import LoadedWindow

logger = logging.getLogger(__name__)


class ModuleType(Enum):
	PERCEPTUAL = 0
	ACOUSTICAL = 1


class ComboboxAction:
	"""
	Represents an action in a combobox UI component.

	Attributes:
	    display_name (str): The user-visible name of the action.
	    identifier (str): A unique identifier for the action.
	    module_type (ModuleType): The type of module associated with the action.
	    sound_sources (Optional[list[str]]): List of related sound source identifiers. Defaults to [identifier].
	"""

	def __init__(
		self,
		display_name: str,
		identifier: str,
		module_type: ModuleType,
		sound_sources: Optional[list[str]] = None,
	):
		if sound_sources is None:
			sound_sources = [identifier]

		self.display_name = display_name
		self.identifier = identifier
		self.module_type = module_type
		self.sound_sources = sound_sources

	def __repr__(self):
		return f"ComboboxAction(display_name={self.display_name}, identifier={self.identifier}, module_type={self.module_type})"

	def __hash__(self):
		return hash((self.display_name, self.identifier))


class CotationMainWindow(LoadedWindow):
	"""
	Main window for the cotation module. Allows to choose a participant, module and judge and launch the appropriate
	cotation module
	"""

	cotationwindow: CotationAcousticWindow
	exportwindow: PraatExportWindow
	csvwindow: CSVExportWindow
	# importwindow: PraatImportWindow
	mergewindow: ResultsMergingWindow
	reportingwindow: ReportingWindow

	def __init__(self, parent: QMainWindow):
		super().__init__(os.path.join("ui", "cotation.ui"))

		self.parentWindow = parent
		self.participant_list: QComboBox = self.find_element(
			QComboBox, "participantComboBox"
		)
		self.module_list: QComboBox = self.find_element(QComboBox, "moduleComboBox")
		self.file_list: QTreeWidget = self.find_element(QTreeWidget, "fileTree")
		self.judge_name: QLineEdit = self.find_element(QLineEdit, "judgeCodeEdit")
		self.advanced_mode: QCheckBox = self.find_element(
			QCheckBox, "advancedModeCheck"
		)

	@override
	def setup(self):
		"""
		Initialize the UI
		We base our design on a 800*600 window, and will automatically adapt it to actual window size
		:return: None
		"""
		# self.setGeometry(DisplayTools.get_window_display_rect())
		# self.setFixedSize(DisplayTools.get_window_size())

		self.setWindowTitle("MonPaGe Cotation - v" + GeneralTools.get_version())

		DisplayTools.fill_participant_dropdown(self.participant_list)

		verify_button: QPushButton = self.find_element(QPushButton, "verifyBtn")

		verify_button.clicked.connect(self.verify_result_db)

		ordered_module = [
			"ModuleIntelligibilite.csv",
			"ModulePneumoPhonatoire.csv",
			"ModulePseudoMots.csv",
			"ModuleDiadoco.csv",
			"ModulePhrases.csv",
			"ModuleTexte.csv",
		]
		if not Options.is_enabled(Options.Option.RESEARCH):
			ordered_module = ["ModuleIntelligibilite.csv", "ModulePseudoMots.csv"]
		for dirname, dirnames, filenames in os.walk("./data/module/"):
			for filename in ordered_module:
				if filename in filenames:
					module = ComboboxAction(
						filename, filename[:-4], ModuleType.PERCEPTUAL
					)
					self.module_list.addItem(module.display_name, module)

		module = ComboboxAction(
			"Cotation Acoustique",
			"screening",
			ModuleType.ACOUSTICAL,
		)
		self.module_list.addItem(module.display_name, module)

		load_sessions_button: QPushButton = self.find_element(
			QPushButton, "chargeSessionsBtn"
		)

		load_sessions_button.clicked.connect(self.load_participant_module)

		self.file_list.setColumnCount(2)
		self.file_list.setColumnWidth(0, DisplayTools.w(260))
		self.file_list.setHeaderLabels(["Session", "Nb Fichiers"])

		self.advanced_mode.setChecked(True)
		if not Options.is_enabled(Options.Option.RESEARCH):
			self.advanced_mode.setChecked(False)
			self.advanced_mode.setVisible(False)

		go_button: QPushButton = self.find_element(QPushButton, "goBtn")

		go_button.clicked.connect(self.start_cotation)

		menubar = self.menuBar()
		file_menu = menubar.addMenu("&Fichier")
		tmp_action = QAction("&Quitter", self)
		tmp_action.triggered.connect(lambda: exit(1))
		file_menu.addAction(tmp_action)

		# scriptMenu = menubar.addMenu('&Screening Acoustique')
		# tmpAction = QAction("&Exporter les enregistrements pour analyse acoustique praat", self)
		# tmpAction.triggered.connect(self.export_file_to_praat_2)
		# scriptMenu.addAction(tmpAction)

		reporting_menu = menubar.addMenu("&Rapport")
		tmp_action = QAction("&Lancer un Rapport", self)
		tmp_action.triggered.connect(self.launch_reporting)
		reporting_menu.addAction(tmp_action)

		if Options.is_enabled(Options.Option.RESEARCH):
			script_menu = menubar.addMenu("Recherche")

			cotper = QMenu("Cotation perceptive", self)
			tmp_action = QAction("Export des résultats en CSV", self)
			tmp_action.triggered.connect(self.export_data_to_csv)
			cotper.addAction(tmp_action)

			manipdb = QMenu("Manipulation des fichiers de cotation db", self)
			tmp_action = QAction("Fusionner les fichiers de résultat", self)
			tmp_action.triggered.connect(self.merge_results)
			manipdb.addAction(tmp_action)

			cotac = QMenu("Exports de fichiers sons", self)
			tmp_action = QAction(
				"Export des enregistrements et scripts vers praat", self
			)
			tmp_action.triggered.connect(self.export_file_to_praat)
			cotac.addAction(tmp_action)

			script_menu.addMenu(cotper)
			script_menu.addSeparator()
			script_menu.addMenu(manipdb)
			script_menu.addSeparator()
			script_menu.addMenu(cotac)

	def extract_participant_code(self):
		"""
		Extracts participant_code of current participant selected
		:return: participant_code
		"""
		tmp = self.participant_list.currentText().split("-")

		return str(tmp[0]).strip()

	def set_current_participant(self):
		"""
		Set current participant
		:return: None
		"""
		self.file_list.clear()
		participant_code = self.extract_participant_code()
		ParticipantManager.set_current_participant(participant_code)
		ParticipantManager.check_cotation_result_db_file(self)

	def load_participant_module(self):
		"""
		Look at all the directories in the data/participant/participantcode, based on which module and participant is
		selected in the menus
		:return:
		"""
		self.set_current_participant()
		selected_module = self.module_list.currentData()
		# selected_module_text = str(self.module_list.currentText())

		# module_code = selected_module.identifier
		base_path = "./data/participant/" + ParticipantManager.current_participant

		if not os.path.isdir(base_path):
			return True

		for dirname in os.listdir(base_path):
			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, dirname)
			has_audio_or_csvconfig: bool = False

			tmp_path = base_path + "/" + dirname
			if not os.path.isdir(tmp_path):
				continue

			if selected_module.module_type == ModuleType.PERCEPTUAL:
				for subdirname in os.listdir(tmp_path):
					if not any(
						source in subdirname for source in selected_module.sound_sources
					):
						continue

					subdir_path = tmp_path + "/" + subdirname
					if not os.path.isdir(subdir_path):
						continue

					files = os.listdir(subdir_path)
					nb_useful_files = sum(
						1
						for fname in files
						if fname.endswith(".wav") or fname.endswith(".csv")
					)

					if nb_useful_files <= 0:
						continue

					has_audio_or_csvconfig = True
					subcurrent_dir_treeitem = QTreeWidgetItem(current_dir_treeitem, 1)
					subcurrent_dir_treeitem.setText(0, subdirname)
					subcurrent_dir_treeitem.setText(1, str(nb_useful_files))
					current_dir_treeitem.addChild(subcurrent_dir_treeitem)

				if has_audio_or_csvconfig:
					self.file_list.addTopLevelItem(current_dir_treeitem)
			elif selected_module.module_type == ModuleType.ACOUSTICAL:
				self.file_list.addTopLevelItem(current_dir_treeitem)
			else:
				pass

	def start_acoustical_cotation(self, participant_code: str, judge_code: str):
		"""
		Start a selected session of cotation with specially code of participant and judge, this function concerns acoustic module
		:param participant_code: The participant's code of session
		:param judge_code: The judge's code of session.
		:returns None
		"""
		files = self.file_list.selectedItems()

		if len(files) != 1:
			GeneralTools.alert_box(
				self, "Veuillez selectionner une et une seule session."
			)
			return

		session_date = files[0].text(0)

		session_path = "./data/participant/" + participant_code + "/" + session_date

		if not os.path.isdir(session_path):
			GeneralTools.alert_box(
				self,
				f"La session {session_date} n'existe pas pour le participant {participant_code}",
			)
			return
		IndicatorReporting.set_current_values(
			participant_code, judge_code, session_date
		)

		try:
			QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
			QApplication.processEvents()  # the cursor will not change otherwise
			self.cotationwindow = CotationAcousticWindow(
				self,
				participant_code,
				judge_code,
				session_date,
				self.advanced_mode.isChecked(),
			)
			self.hide()
			self.cotationwindow.show()
		except Exception as e:
			raise e
		finally:
			QApplication.restoreOverrideCursor()

	def start_perceptual_cotation(
		self, selected_module: ComboboxAction, participant_code: str, judge_code: str
	):
		"""
		Start a selected session of cotation with specially code of participant and judge, this function concerns all other module than acoustic module
		:param selected_module: The module of perceptual cotation chosen by the user
		:param participant_code: The participant's code of session
		:param judge_code: The judge's code of session.
		:returns None
		"""
		logger.info("Starting perceptual cotation")
		files = self.file_list.selectedItems()

		is_valid_session = (
			len(files) > 0 and files[0].text(1) != "" and int(files[0].text(1)) > 0
		)
		logger.info("The session" + " is " + "valid" if is_valid_session else "invalid")
		if ModuleType.PERCEPTUAL and not is_valid_session:
			logger.warning("The selected session is invalid")
			GeneralTools.alert_box(
				self, "La session que vous avez sélectionné n'est pas valide"
			)
			return

		session_path = files[0].parent().text(0) + "/" + files[0].text(0) + "/"
		try:
			self.__start_perceptual_cotation(
				selected_module.identifier, session_path, participant_code, judge_code
			)
			self.hide()
			self.cotationwindow.show()
		except Exception as e:
			logger.error(f"Error starting module cotation: {e}")

	def start_cotation(self):
		"""
		Start a cotation window appropriate for the selected module
		:return:
		"""
		participant_code = self.extract_participant_code()
		judge_code = str(self.judge_name.text()).strip().upper()

		if judge_code == "" or judge_code is None:
			GeneralTools.alert_box(
				self, "Vous devez saisir un code de juge pour continuer"
			)
			return

		selected_module = self.module_list.currentData()
		if selected_module.module_type == ModuleType.PERCEPTUAL:
			self.start_perceptual_cotation(
				selected_module, participant_code, judge_code
			)
		elif selected_module.module_type == ModuleType.ACOUSTICAL:
			self.start_acoustical_cotation(participant_code, judge_code)
		else:
			pass  # TODO raise ModuleType not handled error

	def __start_perceptual_cotation(
		self,
		module_code: str,
		session_path: str,
		participant_code: str,
		judge_code: str,
	):
		"""
		Launches a cotation window based on its module_code
		"""

		module_constructor_map: dict[str, type] = {
			"ModulePseudoMots": CotationPseudoMotsWindow,
			"ModulePhrases": CotationPhrasesWindow,
			"ModuleTexte": CotationTexteWindow,
			"ModuleIntelligibilite": CotationIntelligibiliteWindow,
			"ModulePneumoPhonatoire": CotationPneumoPhonatoireWindow,
			"ModuleDiadoco": CotationDiadocoWindow,
		}

		window_class = module_constructor_map.get(module_code)

		if window_class is None:
			logger.error(
				f"Aucun constructeur trouvé pour le code du module : {module_code}"
			)
		else:
			self.cotationwindow = window_class(
				self,
				participant_code,
				module_code,
				judge_code,
				str(session_path),
				self.advanced_mode.isChecked(),
			)

	def verify_result_db(self):
		"""
		Call data function verification to verify data of current participant
		:returns None
		"""
		participant_code = self.extract_participant_code()
		msgs = MigrationTools.verify_participant_result_db(participant_code)
		msg = "Fichier de résultat ok" if len(msgs) == 0 else "\n".join(msgs)

		GeneralTools.alert_box(
			self,
			"Vérification des résultats de la cotation de " + participant_code,
			msg,
		)

	def get_file_name_formated(
		self, pref_dir_path, msg, participant_code, end_path, extension
	):
		"""
		Prepare filename with specialy format
		:param pref_dir_path : the preferred path
		:param msg : A message text
		:param participant_code : the participant's code
		:param end_path : the end of filename
		:param extension : the file extension
		:Return filename : filename formated
		"""

		target_path = PreferencesTools.get_preference_directory_path(pref_dir_path)
		filename = QDir.toNativeSeparators(
			QFileDialog.getSaveFileName(
				self,
				msg,
				target_path + os.path.sep + participant_code + end_path,
				extension,
			)[0]
		)

		return filename

	def export_participant_results_pw(self):
		"""
		Export data with pw export of current participant in CSV format and set the preference directory to the directory of CSV file
		:returns None
		"""
		logger.critical("Deprecated")
		participant_code = self.extract_participant_code()

		filename = self.get_file_name_formated(
			"export_pw_pref_path",
			"Enregistrer les données brutes pseudomots",
			participant_code,
			"_pseudomots.csv",
			".csv",
		)

		if filename != "":
			PreferencesTools.set_preference_directory_path_from_filename(
				"export_pw_pref_path", filename
			)
			ExportCSV.export_pw_results(
				"./data/participant/" + participant_code + "/",
				"cotation_result" + participant_code,
				filename,
				False,
				True,
			)

	def export_participant_results_int(self):
		"""
		Export data with int export of current participant in CSV format and set the preference directory to the directory of CSV file
		:returns None
		"""
		logger.critical("Deprecated")
		participant_code = self.extract_participant_code()

		filename = self.get_file_name_formated(
			"export_int_pref_path",
			"Enregistrer les données brutes intelligibilité",
			participant_code,
			"_intelligibilite.csv",
			".csv",
		)

		if filename != "":
			PreferencesTools.set_preference_directory_path_from_filename(
				"export_int_pref_path", filename
			)
			ExportCSV.export_int_results(
				"./data/participant/" + participant_code + "/",
				"cotation_result" + participant_code,
				filename,
				False,
				True,
			)

	def export_participant_results_qa(self):
		"""
		Export data with qa export of current participant in CSV format and set the preference directory to the directory of CSV file
		:returns None
		"""
		logger.critical("Deprecated")
		participant_code = self.extract_participant_code()

		target_path = PreferencesTools.get_preference_directory_path(
			"export_qa_pref_path"
		)
		# noinspection PyCallByClass
		filename, _ = QFileDialog.getSaveFileName(
			self,
			"Enregistrer les données brutes question/réponse",
			target_path + os.path.sep + participant_code + "_question_reponse.csv",
			".csv",
		)
		filename = str(QDir.toNativeSeparators(filename))

		if filename != "":
			PreferencesTools.set_preference_directory_path_from_filename(
				"export_qa_pref_path", filename
			)
			ExportCSV.export_qa_results(
				"./data/participant/" + participant_code + "/",
				"cotation_result" + participant_code,
				filename,
				False,
				True,
			)

	def export_data_to_csv(self):
		"""Open a window to export data to a CSV file."""
		self.csvwindow = CSVExportWindow(self)

	def export_file_to_praat(self):
		"""Open a window to export data to Praat (Research format)."""
		self.exportwindow = PraatExportWindowResearch(self)

	def export_file_to_praat_2(self):
		"""Open a window to export data to Praat (Acoustic format)."""
		self.exportwindow = PraatExportWindowAcoustic(self)

	def import_praat_results(self):
		"""Placeholder for importing results from Praat (currently not implemented)."""
		pass
		# self.importwindow = PraatImportWindow(self)

	def merge_results(self):
		"""Open a window to merge imported or exported result data."""
		self.mergewindow = ResultsMergingWindow(self)

	def launch_reporting(self):
		"""Open the reporting window to generate and view analysis reports."""
		self.reportingwindow = ReportingWindow(self)

	@override
	def closeEvent(self, event):
		"""Handle the close event by stopping the audio manager and showing the parent window."""
		AudioManager.close()

		self.parentWindow.show()

		return super().closeEvent(event)
