# encoding=utf-8
"""
Classes for the UI to manage export of audio files and Praat scripts to Praat software.

This module provides interfaces for exporting files needed for acoustic analysis in Praat:
- PraatExportWindow: Base abstract class for export windows
- PraatExportWindowResearch: Advanced export interface for research purposes
- PraatExportWindowAcoustic: Simplified export interface for acoustic analysis
"""

__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 shutil import copyfile
from typing import Generic, Iterator, TypeVar

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

from tools.csv_manager import CSVManager
from tools.display_tools import DisplayTools
from tools.general_tools import GeneralTools
from tools.participant_manager import ParticipantManager
from tools.preferences_tools import PreferencesTools

logger = logging.getLogger(__name__)

ListWidgetType = TypeVar("ListWidgetType", bound=QWidget)


class PraatExportWindow(QWidget, Generic[ListWidgetType]):
	"""
	Abstract base class for Praat export windows.

	This class defines common properties and interfaces for the different types
	of Praat export windows. It is implemented as a generic class to allow
	specialization with different types of participant selection widgets.
	"""

	output_dir: QLabel
	"""Label displaying the path to the output directory"""

	new_sub_dir: QLabel
	"""Label for subdirectory creation"""

	participant_list: ListWidgetType
	"""Widget for participant selection (type depends on subclass)"""

	file_list: QTreeWidget
	"""Tree widget displaying available audio files"""

	files: dict[str, list[str]] = {}
	"""Dictionary mapping file groups to lists of file paths"""

	analyse_list: QTreeWidget
	"""Tree widget displaying available Praat scripts"""

	analyses: Iterator[str]
	"""Iterator of Praat script paths"""

	pbar: QProgressBar
	"""Progress bar displaying export progress"""

	copy_modes: list[QRadioButton] = []
	"""List of radio buttons for different file organization options"""

	def __init__(self):
		"""Initialize the base widget."""
		super(PraatExportWindow, self).__init__()


class PraatExportWindowResearch(PraatExportWindow[QTreeWidget]):
	"""
	Advanced export interface for research purposes.

	This window allows selection of multiple participants, Praat scripts, and audio files
	with customizable directory organization. It provides advanced features for research
	projects working with multiple participants and analyses.

	Attributes:
		parent: Reference to the parent window
	"""

	def __init__(self, parent):
		"""
		Initialize the research export window.

		Args:
			parent: Parent window object
		"""
		super(PraatExportWindowResearch, self).__init__()
		self.parent = parent
		self.init_ui()
		self.load_file_list()
		self.load_participant_list()
		self.load_analyse_list()
		self.show()

	def init_ui(self):
		"""
		Initialize the UI
		:return: None
		"""
		# self.setGeometry(80, 80, 500, 350)
		# self.setFixedSize(500, 350)
		self.setGeometry(DisplayTools.get_window_display_rect())
		self.setFixedSize(DisplayTools.size(800, 450))
		self.setWindowTitle("MonPaGe Export pour Praat")
		# self.connect(self, QtCore.SIGNAL('triggered()'), self.closeEvent)

		gtmp = QGroupBox("Répertoire de sortie", self)
		gtmp.resize(DisplayTools.size(780, 30))
		gtmp.move(DisplayTools.coord(10, 10))

		self.output_dir = QLabel(
			PreferencesTools.get_preference_directory_path("praat_export_pref_path"),
			gtmp,
		)
		self.output_dir.resize(DisplayTools.size(700, 20))
		self.output_dir.move(DisplayTools.coord(10, 10))
		# DisplayTools.set_font_size(self.output_dir, "lab_export_directory")

		tmp = QPushButton("...", gtmp)
		tmp.move(DisplayTools.coord(700, 5))
		tmp.resize(DisplayTools.size(70, 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(DisplayTools.size(255, 260))
		gtmp.move(DisplayTools.coord(10, 45))

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

		self.participant_list.resize(DisplayTools.size(245, 240))
		self.participant_list.move(DisplayTools.coord(5, 15))

		gtmp = QGroupBox("Fichiers Praat", self)
		gtmp.resize(DisplayTools.size(255, 260))
		gtmp.move(DisplayTools.coord(270, 45))

		self.analyse_list = QTreeWidget(gtmp)
		self.analyse_list.setColumnCount(1)
		self.analyse_list.setColumnWidth(0, DisplayTools.w(240))
		self.analyse_list.setHeaderLabels(["Analyse"])
		self.analyse_list.resize(DisplayTools.size(245, 240))
		self.analyse_list.setSelectionMode(
			QAbstractItemView.SelectionMode.ExtendedSelection
		)
		self.analyse_list.move(DisplayTools.coord(5, 15))

		gtmp = QGroupBox("Fichiers audio", self)
		gtmp.resize(DisplayTools.size(255, 260))
		gtmp.move(DisplayTools.coord(530, 45))

		self.file_list = QTreeWidget(gtmp)
		self.file_list.setColumnCount(1)
		self.file_list.setColumnWidth(0, DisplayTools.w(240))
		self.file_list.setSelectionMode(
			QAbstractItemView.SelectionMode.ExtendedSelection
		)
		self.file_list.setHeaderLabels(["Fichier audio"])
		self.file_list.resize(DisplayTools.size(245, 240))
		self.file_list.move(DisplayTools.coord(5, 15))

		gtmp = QGroupBox("Mode de copie", self)
		gtmp.resize(DisplayTools.size(780, 100))
		gtmp.move(DisplayTools.coord(10, 310))

		tmpr = QRadioButton("Tout dans le répertoire de sortie", gtmp)
		tmpr.move(DisplayTools.coord(15, 10))
		tmpr.resize(DisplayTools.size(500, 20))
		tmpr.setObjectName("1")
		self.copy_modes.append(tmpr)

		tmpr = QRadioButton("Créer une arborescence par participant", gtmp)
		tmpr.move(DisplayTools.coord(15, 35))
		tmpr.resize(DisplayTools.size(500, 20))
		tmpr.setObjectName("2")
		self.copy_modes.append(tmpr)

		tmpr = QRadioButton(
			"Créer une arborescence par participant et par date de session", gtmp
		)
		tmpr.move(DisplayTools.coord(15, 60))
		tmpr.resize(DisplayTools.size(500, 20))
		tmpr.setObjectName("3")
		tmpr.setChecked(True)
		self.copy_modes.append(tmpr)

		self.pbar = QProgressBar(self)
		self.pbar.move(DisplayTools.coord(5, 420))
		self.pbar.setTextVisible(False)
		self.pbar.resize(DisplayTools.size(670, 25))

		tmp = QPushButton("Copier", self)
		tmp.move(DisplayTools.coord(680, 420))
		tmp.resize(DisplayTools.size(100, 25))
		DisplayTools.set_font_size(tmp, "lab_export_gobtn")
		tmp.clicked.connect(self.duplicate_file_for_praat)

	def change_output_folder(self):
		"""
		Opens a file dialog to change the output directory.

		Allows the user to select a new directory for export files and saves
		this preference for future sessions.
		"""
		filename = QDir.toNativeSeparators(
			QFileDialog.getExistingDirectory(
				self, "Select Directory", self.output_dir.text()
			)
		)
		if filename != "":
			self.output_dir.setText(filename)
			PreferencesTools.set_preference_value("praat_export_pref_path", filename)

	def create_output_sub_folder(self):
		"""
		Creates a new subdirectory in the output path.

		Takes the name from new_sub_dir text field, creates the directory if it
		doesn't exist, and updates the current output directory to this new 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):
		"""
		Populates the participant selection tree widget.

		Retrieves the list of participants and displays their codes and demographic
		information. If a participant's data file is missing, this is indicated in the display.
		"""
		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_analyse_list(self):
		"""
		Loads available Praat script files into the analyse list.

		Finds all .praat files in the data/praat_scripts directory and adds them to
		the analyse_list tree widget. The full path is stored in the item's user data.
		"""
		self.analyse_list.clear()
		self.analyses = GeneralTools.find_files("./data/praat_scripts", ".praat", False)

		for a in self.analyses:
			tmp = a.split("/")
			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, tmp[-1])
			current_dir_treeitem.setData(0, Qt.ItemDataRole.UserRole, a)
			self.analyse_list.addTopLevelItem(current_dir_treeitem)

	def load_file_list(self):
		"""
		Loads available audio files grouped by type.

		Reads file groups from data/analyses.csv and organizes them in a hierarchical
		tree structure. The file groups themselves are not selectable, but the individual
		files within each group are.
		"""
		self.file_list.clear()
		self.files.clear()
		file_groups = []
		analyses_csv: list[list[str]] = CSVManager.read_file(
			"./data/analyses.csv", "\t"
		)
		for row in analyses_csv:
			file_group = row[0]
			wav_files_start = next(
				i for i, file in enumerate(row) if file.endswith(".wav")
			)
			wav_files = row[wav_files_start:]

			if file_group not in self.files:
				self.files[file_group] = []
				file_groups.append(file_group)

			for wav_filename in wav_files:
				assert wav_filename.endswith(".wav"), wav_filename
				self.files[file_group].append(wav_filename)

		for file_group in file_groups:
			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, file_group)
			current_dir_treeitem.setFlags(
				current_dir_treeitem.flags() & ~Qt.ItemFlag.ItemIsSelectable
			)

			for wav_filename in self.files[file_group]:
				current_subdir_treeitem = QTreeWidgetItem(0)
				current_subdir_treeitem.setText(0, wav_filename)

				current_dir_treeitem.addChild(current_subdir_treeitem)
			self.file_list.addTopLevelItem(current_dir_treeitem)
			current_dir_treeitem.setExpanded(False)

	def duplicate_file_for_praat(self):
		"""
		Performs the file export operation.

		This method:
		1. Validates the output directory and selections
		2. Determines files to copy based on selections
		3. Creates target directories as needed
		4. Copies files to target locations
		5. Displays a completion message

		If validation fails at any point, an error message is displayed and the operation aborts.
		"""
		self.pbar.setValue(0)
		self.pbar.setMaximum(1)
		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_analyses = self.analyse_list.selectedItems()
		selected_files = self.file_list.selectedItems()
		if len(selected_analyses) < 1 and len(selected_files) < 1:
			GeneralTools.alert_box(
				self,
				"Impossible de procéder à l'export",
				"Vous devez sélectionner au moins un fichier praat et/ou un fichier audio",
			)
			return

		wanted_praat_files = []
		wanted_wav_files_model = []
		for a in selected_analyses:
			tmp = str(a.data(0, Qt.ItemDataRole.UserRole))
			wanted_praat_files.append(tmp)
		wanted_praat_files = list(set(wanted_praat_files))
		for a in selected_files:
			tmp = str(a.text(0))
			wanted_wav_files_model.append(tmp)
		wanted_wav_files_model = list(set(wanted_wav_files_model))

		create_sub_participant = False
		create_sub_session = False
		for rb in self.copy_modes:
			if rb.isChecked():
				val = rb.objectName()
				if val == "2":
					create_sub_participant = True
				if val == "3":
					create_sub_participant = True
					create_sub_session = True

		files_to_copy = []
		target_dirs = []

		for p in selected_participants:
			pcode = str(p.text(0)).split(" - ")[0]
			ParticipantManager.set_current_participant(pcode)
			sessions = ParticipantManager.get_all_available_sessions()
			for sdate in sessions:
				tdir = self.calculate_target_directory(
					target_dir, pcode, sdate, create_sub_participant, create_sub_session
				)
				target_dirs.append(tdir)
				source_dir = ParticipantManager.folder_path + sdate + "/"
				for fname in wanted_wav_files_model:
					files = GeneralTools.find_files(source_dir, fname, True)
					for f in files:
						tmp = f.split("/")
						t = (
							os.path.normpath(f),
							os.path.normpath(tdir + "/" + tmp[-1]),
						)
						files_to_copy.append(t)
		target_dirs = list(set(target_dirs))
		for td in target_dirs:
			# print("td : ", td)
			# print('wanted_praat_files : ', wanted_praat_files)
			for praat_file in wanted_praat_files:
				# fname = praat_file.split("/")[-1]
				# print('fname1',fname)
				praat_file = praat_file.replace("\\", "/")
				fname = praat_file.split("/")[-1]
				# print('fname2',fname)
				t = (os.path.normpath(praat_file), os.path.normpath(td + "/" + fname))
				files_to_copy.append(t)
			if not os.path.isdir(td):
				os.makedirs(td)
				# t au lieu de td ?

		self.pbar.setMaximum(len(files_to_copy))
		for t in files_to_copy:
			# print(t)
			self.pbar.setValue(self.pbar.value() + 1)
			copyfile(t[0], t[1])

		GeneralTools.alert_box(
			self,
			"Export terminé",
			str(len(files_to_copy))
			+ " fichiers ont été copiés dans "
			+ str(len(target_dirs))
			+ " dossiers",
		)

	@staticmethod
	def calculate_target_directory(
		target_base: str,
		participant_code: str,
		session_date: str,
		create_sub_participant: bool = False,
		create_sub_session: bool = False,
	):
		"""
		Determines target path based on selected organization mode.

		Args:
			target_base: Base output directory
			participant_code: Participant identifier
			session_date: Session date
			create_sub_participant: Whether to create participant subdirectories
			create_sub_session: Whether to create session subdirectories

		Returns:
			Normalized path to target directory

		Organization modes:
		- Mode 1: All files in a single output directory (both False)
		- Mode 2: Create subdirectories by participant (create_sub_participant=True)
		- Mode 3: Create subdirectories by participant and session date (both True)
		"""
		ret = target_base
		if create_sub_participant:
			ret += "/" + participant_code.upper()
		if create_sub_session:
			ret += "/" + session_date.upper()
		return os.path.normpath(ret)


class PraatExportWindowAcoustic(PraatExportWindow[QComboBox]):
	"""
	Simplified export interface for acoustic analysis.

	This window provides a streamlined interface for exporting files needed for
	acoustic analysis. It focuses on session-based exports for individual participants
	and copies files to the session's "Analyse" directory.

	Attributes:
		parent: Reference to the parent window
		session_list: Tree widget for selecting session dates
	"""

	session_list: QTreeWidget
	"""Tree widget for selecting session dates"""

	def __init__(self, parent):
		"""
		Initialize the acoustic export window.

		Args:
			parent: Parent window object
		"""
		super(PraatExportWindowAcoustic, self).__init__()
		self.parent = parent
		self.init_ui()
		# # self.load_file_list()
		# self.load_participant_list()
		# self.load_analyse_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 pour Praat")
		# self.connect(self, QtCore.SIGNAL('triggered()'), self.closeEvent)

		group = QGroupBox("1/ Sélectionnez un participant", self)
		group.resize(400, 85)
		group.move(10, 10)
		self.participant_list = DisplayTools.generate_participant_dropdown(group)
		self.participant_list.resize(380, 20)
		self.participant_list.move(10, 20)

		qbtn = QPushButton("Charger", group)
		qbtn.clicked.connect(self.load_participant_session)
		qbtn.resize(70, 25)
		qbtn.move(300, 50)
		DisplayTools.set_font_size(qbtn, "btn_ctn_charger")

		group = QGroupBox("2/ Choisissez une session", self)
		group.resize(400, 185)
		group.move(10, 100)

		self.session_list = QTreeWidget(group)
		self.session_list.setSelectionMode(
			QAbstractItemView.SelectionMode.SingleSelection
		)
		self.session_list.setColumnCount(1)
		self.session_list.setColumnWidth(0, 210)
		self.session_list.setHeaderLabels(["Session"])
		self.session_list.resize(380, 150)
		self.session_list.move(10, 20)

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

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

	#
	# self.participant_list.resize(220, 240)
	# self.participant_list.move(5, 15)
	#
	# gtmp = QGroupBox("Analyse", self)
	# gtmp.resize(230, 260)
	# gtmp.move(240, 45)
	#
	# self.analyse_list = QTreeWidget(gtmp)
	# self.analyse_list.setColumnCount(1)
	# self.analyse_list.setColumnWidth(0, 210)
	# self.analyse_list.setHeaderLabels(["Analyse"])
	# self.analyse_list.resize(220, 240)
	# self.analyse_list.move(5, 15)
	#
	# self.pbar = QProgressBar(self)
	# self.pbar.move(5, 315)
	# self.pbar.setTextVisible(False)
	# self.pbar.resize(370, 25)
	#
	# tmp = QPushButton("Copier", self)
	# tmp.move(380, 315)
	# tmp.resize(100, 25)
	# DisplayTools.set_font_size(tmp, "lab_export_gobtn")
	# tmp.clicked.connect(self.duplicate_file_for_praat)

	def load_participant_session(self):
		"""
		Loads sessions for the selected participant.

		Extracts the participant code from the dropdown selection, sets it as the
		current participant, and populates the session list with available sessions.
		"""
		tmp = self.participant_list.currentText().split("-")
		participant_code = str(tmp[0]).strip()
		ParticipantManager.set_current_participant(participant_code)

		sessions = ParticipantManager.get_all_available_sessions()
		self.session_list.clear()
		for s in sessions:
			current_dir_treeitem = QTreeWidgetItem(0)
			current_dir_treeitem.setText(0, s)
			self.session_list.addTopLevelItem(current_dir_treeitem)

	def duplicate_file_for_praat(self):
		"""
		Exports files to the session's analysis folder.

		This method:
		1. Gets the selected session date
		2. Creates the "Analyse" subdirectory if needed
		3. Identifies files to copy from data/analyses.csv (first line only)
		4. Copies files to the session's analysis folder
		5. Displays a completion message

		In non-research mode, only files specified in the first line of analyses.csv are exported.
		"""
		self.pbar.setValue(0)
		self.pbar.setMaximum(1)
		session_date = str(self.session_list.selectedItems()[0].text(0))
		logger.info(
			f"Exporting files to analysis folder for {ParticipantManager.current_participant} / {session_date}"
		)

		session_dir = ParticipantManager.folder_path + session_date
		target_dir = session_dir + "/Analyse/"
		if not os.path.isdir(target_dir):
			os.mkdir(target_dir)

		wanted_files = []
		tocopy = CSVManager.read_file("./data/analyses.csv", "\t")
		# In non-research mode, we only output first line of the analyse.csv file
		wanted_files += list(set(tocopy[0][2:]))
		files_to_copy = []
		for file in wanted_files:
			if file.endswith(".praat"):
				tmp = GeneralTools.find_files("./data/praat_scripts", file, False)
			else:
				tmp = GeneralTools.find_files(session_dir, file, True)
			for f in tmp:
				if "Analyse/" not in f:
					tmp2 = f.split("/")
					filename = tmp2[-1]
					t = (f, target_dir + filename)
					files_to_copy.append(t)

		self.pbar.setMaximum(len(files_to_copy))
		for t in files_to_copy:
			try:
				logger.info(f"Copying {t[0]} -> {t[1]}")
				self.pbar.setValue(self.pbar.value() + 1)
				copyfile(t[0], t[1])
			except Exception as e:
				logger.error(f"Error exporting praat analysis: {e}")

		GeneralTools.alert_box(
			self,
			"Export terminé",
			str(len(files_to_copy)) + " fichiers ont été copiés dans " + target_dir,
		)
