# encoding=utf-8
"""
File for the IntelligibiliteModuleWindow class
"""

__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
import random

from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLabel

from passation.module_window import ModuleWindow
from tools.audio_manager import AudioManager
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.participant_manager import ParticipantManager

logger = logging.getLogger(__name__)


class ModuleIntelligibiliteWindow(ModuleWindow):
	words = []
	positions = []
	word_img_display = None
	word_txt_display = None

	current_consigne_index = 0
	consignes_done = False

	lines = 5
	cols = 5

	positions_to_xy = {}

	consignes = []

	def __init__(self, parent, participant_code, module_name, stimuli_accent):
		db_filepath = "./data/module/" + module_name + "/"

		if not os.path.isfile(db_filepath + "liste_mots.db"):
			GeneralTools.alert_box(
				self, "Impossible de trouver la base de données des mots pour ce module"
			)
			self.end_module()

		with DBManager(db_filepath + "liste_mots.db") as mots_db:
			self.words = ModuleIntelligibiliteWindow.get_words_list(mots_db)

		self.positions = ModuleIntelligibiliteWindow.get_random_positions(
			len(self.words), self.lines, self.cols
		)
		self.current_consigne_index = 0
		self.current_module_index = -1
		self.consignes_done = False
		if os.path.isfile(db_filepath + "consignes.csv"):
			self.consignes = []
			tmp = CSVManager.read_file(db_filepath + "consignes.csv")
			for t in tmp:
				self.consignes.append("\n".join(t))
		else:
			self.consignes = [
				"Nous allons maintenant faire quelque chose ensemble.\nVous allez voir sur l’écran différentes formes de différentes couleurs.",
				"Sur ces formes, des mots et leur image vont s'afficher un par un",
				'Votre objectif est de m\'aider a placer les mots qui vont apparaitre au bon endroit sur ma feuille.\nVous devrez toujours utiliser la phrase : "Mettez le mot xxx dans le/la yyy" \n(exemple : "Mettez le mot boîte dans le carré bleu")',
				" Prêt ? On fait un essai",
				" Prêt ? On commence",
			]
		super(ModuleIntelligibiliteWindow, self).__init__(
			parent, participant_code, module_name, stimuli_accent, self.words
		)

	def init_ui(self):
		"""
		Initialize the user interface elements for the intelligibility module window.

		Calls the superclass init_ui, then creates and configures QLabel widgets for
		displaying words as images and text. Sets size, position, alignment, font,
		and background for these labels, and initializes display buttons with "start".
		"""
		super(ModuleIntelligibiliteWindow, self).init_ui()
		self.word_img_display = QLabel(self)
		self.word_txt_display = QLabel(self)

		self.word_img_display.resize(80, 80)
		self.word_img_display.move(0, 0)
		self.word_img_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
		self.word_img_display.setFont(self.parent.display_font)
		self.word_txt_display.resize(80, 30)
		self.word_txt_display.move(0, 0)
		self.word_txt_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
		self.word_txt_display.setAutoFillBackground(True)
		self.word_txt_display.setFont(self.parent.display_font)
		self.display_buttons("start")

	def start_module(self):
		"""
		Start the module by preparing the environment and initializing display elements.

		Prepares necessary directories, saves the current script, displays a background image,
		calculates positions for word images, and initializes the current instruction item.
		"""
		self.prepare_directory()
		self.save_script()
		DisplayTools.display_image(
			self.text_display, "./data/module/" + self.module_name + "/fond.png"
		)
		self.positions_to_xy = DisplayTools.calculate_positions_xy(
			self.text_display, self.word_img_display, self.lines, self.cols
		)
		self.current_module_index = -1
		self.current_consigne_item()

	def start_recording(self):
		"""
		Start recording audio for the current word in the module.

		Constructs the filename for the audio file, stops any ongoing recording,
		and starts a new recording session with a 120-second limit, updating progress UI elements.
		Handles exceptions by displaying an alert box with the error message.
		"""
		try:
			filenames = [
				ParticipantManager.full_session_path
				+ ParticipantManager.filename_prefix
				+ self.words[self.current_module_index][1]
				+ ".wav"
			]
			AudioManager.stop()
			AudioManager.record_wave(
				filenames,
				120,
				self.progress_bar,
				self.progress_bar_label,
				self.option_group,
			)
		except Exception as e:
			GeneralTools.alert_box(self, repr(e))

	def next_module_item(self):
		"""
		Advance to the next module item or instruction based on completion status.

		Stops any ongoing audio recording. If instructions are completed, increments the
		module index and displays the current module item. Otherwise, increments the instruction
		index and displays the current instruction.
		"""
		AudioManager.stop()
		if self.consignes_done:
			self.current_module_index += 1
			self.current_module_item()
		else:
			self.current_consigne_index += 1
			self.current_consigne_item()

	def current_module_item(self):
		"""
		Display the current module item and start recording audio.

		Ensures the module index is non-negative, updates the UI to show the image display buttons,
		then attempts to display the current word image and start recording. If the index is out of range,
		ends the module. Forces UI repaint and returns False.
		"""
		self.current_module_index = max(0, self.current_module_index)
		self.display_buttons("image")
		try:
			word = self.words[self.current_module_index]
			pos = self.positions[self.current_module_index]
			self.display_image_at(word, pos)
			self.start_recording()

		except IndexError:
			self.end_module()

		self.repaint()
		return False

	def current_consigne_item(self):
		"""
		Display the current instruction (consigne) or transition to the module items.

		Manages display content based on the current instruction index:
		- Shows text or images specific to each instruction step.
		- Hides word text and image displays during instructions.
		- Marks instructions as done after the final step and transitions to the module.
		- If instructions are completed, directly shows the module items.

		Forces UI repaint and returns False.
		"""
		self.display_buttons("image")
		if not self.consignes_done:
			self.word_txt_display.hide()
			self.word_img_display.hide()

			if self.current_consigne_index == 0:
				self.text_display.setText(self.consignes[0])
			elif self.current_consigne_index == 1:
				DisplayTools.display_image(
					self.text_display,
					"./data/module/" + self.module_name + "/fond_legend.png",
				)
			elif self.current_consigne_index == 2:
				self.text_display.setText(self.consignes[1])
			elif self.current_consigne_index == 3:
				self.text_display.setText(self.consignes[2])
			elif self.current_consigne_index == 4:
				DisplayTools.display_image(
					self.text_display,
					"./data/module/" + self.module_name + "/fond_avec_boite.png",
				)
			elif self.current_consigne_index == 5:
				self.text_display.setText(self.consignes[3])
			elif self.current_consigne_index == 6:
				DisplayTools.display_image(
					self.text_display, "./data/module/" + self.module_name + "/fond.png"
				)
				self.display_image_at([None, None, "Banane", "banane.png"], (3, 1))
			elif self.current_consigne_index == 7:
				DisplayTools.display_image(
					self.text_display, "./data/module/" + self.module_name + "/fond.png"
				)
				self.display_image_at([None, None, "Haltères", "halteres.png"], (2, 4))

			elif self.current_consigne_index == 8:
				self.display_buttons("endconsigne")
				self.text_display.setText(self.consignes[4])
				self.consignes_done = True

			elif self.current_consigne_index > 8:
				self.consignes_done = True
				DisplayTools.display_image(
					self.text_display, "./data/module/" + self.module_name + "/fond.png"
				)
				self.current_module_item()
		else:
			DisplayTools.display_image(
				self.text_display, "./data/module/" + self.module_name + "/fond.png"
			)
			self.current_module_item()

		self.repaint()
		return False

	def display_image_at(self, word, position):
		"""
		Display an image and its corresponding label at a specified position.

		- Checks if the given position exists in the positions mapping.
		- Displays a background image on the main text display.
		- Displays the word image at the calculated (x, y) coordinates, adjusted slightly vertically.
		- Shows and positions the label text below the image, adjusting font size.
		- If the position is not found, logs an error with the missing position info.
		"""
		if position in self.positions_to_xy:
			DisplayTools.display_image(
				self.text_display, "./data/module/" + self.module_name + "/fond.png"
			)
			DisplayTools.display_image(
				self.word_img_display,
				"./data/module/" + self.module_name + "/images/" + word[3],
			)
			self.word_img_display.show()
			self.word_img_display.move(
				self.positions_to_xy[position][0],
				self.positions_to_xy[position][1] - 20,
			)
			self.word_img_display.setVisible(True)
			self.word_img_display.raise_()
			self.word_txt_display.show()
			self.word_txt_display.move(
				self.positions_to_xy[position][0],
				self.positions_to_xy[position][1] + 60,
			)
			self.word_txt_display.setText(word[2])
			DisplayTools.set_font_size(self.word_txt_display, None, False, 22)
			self.word_txt_display.setVisible(True)
			self.word_txt_display.raise_()
		else:
			logger.error("position introuvable ", position, self.positions_to_xy)

	def display_shortcuts_btn(self, stop: bool, quit: bool, record: bool, next: bool):
		"""
		Enable or disable shortcut buttons for stop, quit, record, and next actions.

		Parameters:
			stop (bool): Enable or disable the stop button shortcuts.
			quit (bool): Enable or disable the quit button shortcuts.
			record (bool): Enable or disable the record button shortcuts.
			next (bool): Enable or disable the next button shortcuts.
		"""
		for short in self.btn_stop_shortcuts:
			short.setEnabled(stop)
		for short in self.btn_quit_shortcuts:
			short.setEnabled(quit)
		for short in self.btn_record_shortcuts:
			short.setEnabled(record)
		for short in self.btn_next_shortcuts:
			short.setEnabled(next)

	def display_buttons(self, mode):
		"""
		Configure the visibility and enabled state of control buttons based on the given mode.

		Args:
			mode (str): The mode to set button states for. Supported values:
				- "image": Show next button enabled, hide start button, disable stop/record shortcuts.
				- "start": Show start button enabled, hide next button, enable quit shortcuts.
				- "endconsigne": Show next button enabled, hide start button, enable quit and next shortcuts.

		Effects:
			- Adjusts visibility and enabled status of buttons: start, next, stop, restart, quit, and record.
			- Calls display_shortcuts_btn() to update shortcut button states accordingly.
			- Triggers a repaint of the widget to reflect changes.
		"""
		self.btn_record.setVisible(False)
		self.btn_stop.setVisible(False)
		self.btn_restart.setVisible(False)
		self.btn_quit.setVisible(True)

		if mode == "image":
			self.btn_start.setVisible(False)
			self.btn_next.setVisible(True)
			self.btn_next.setEnabled(True)
			self.display_shortcuts_btn(False, False, False, True)
			# for short in self.btn_stop_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_quit_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_record_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_next_shortcuts:
			# 	short.setEnabled(True)
		elif mode == "start":
			self.btn_start.setVisible(True)
			self.btn_start.setEnabled(True)
			self.btn_next.setVisible(False)
			self.btn_next.setEnabled(False)
			self.btn_quit.setVisible(True)
			self.display_shortcuts_btn(False, True, False, False)
			# for short in self.btn_stop_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_quit_shortcuts:
			# 	short.setEnabled(True)
			# for short in self.btn_record_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_next_shortcuts:
			# 	short.setEnabled(False)
		elif mode == "endconsigne":
			self.btn_start.setVisible(False)
			self.btn_start.setEnabled(False)
			self.btn_next.setVisible(True)
			self.btn_next.setEnabled(True)
			self.btn_quit.setVisible(True)
			self.display_shortcuts_btn(False, True, False, True)
			# for short in self.btn_stop_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_quit_shortcuts:
			# 	short.setEnabled(True)
			# for short in self.btn_record_shortcuts:
			# 	short.setEnabled(False)
			# for short in self.btn_next_shortcuts:
			# 	short.setEnabled(True)

		self.repaint()

	@staticmethod
	def get_words_list(db=None):
		"""
		Retrieve a randomized list of words from the database, ensuring no duplicates.

		Args:
			db (optional): Database connection object to execute queries on.

		Returns:
			list: A shuffled list of word records selected from the database,
				each chosen from different predefined categories and excluding previously selected IDs.

		Details:
			- Starts with an initial exclusion list containing "0".
			- Iterates over predefined order lists ("1" to "5").
			- For each list, queries up to 3 random words where the column L{list_pool} equals 1,
			and the word ID is not already selected.
			- Appends newly fetched words to the result and updates the exclusion list.
			- Finally, shuffles all collected words before returning.
		"""
		selected_ids = ["0"]
		order_lists = ["1", "2", "3", "4", "5"]

		words = []

		if db is not None:
			for list_pool in order_lists:
				sql = (
					"Select * from mots where L"
					+ list_pool
					+ "=1 and id not in("
					+ ",".join(selected_ids)
					+ ") order by random() LIMIT 3"
				)
				r = db.execute(sql)
				for line in r:
					words.append(line)
					selected_ids.append(str(line[0]))
		random.shuffle(words)
		return words

	@staticmethod
	def get_random_positions(nbwanted, max_line=5, max_col=5):
		"""
		Generate a list of random unique (column, line) position tuples.

		Args:
			nbwanted (int): Number of random positions to return.
			max_line (int, optional): Maximum number of lines. Defaults to 5.
			max_col (int, optional): Maximum number of columns. Defaults to 5.

		Returns:
			list of tuples: A list containing `nbwanted` unique positions as (column, line),
							randomly shuffled from the grid defined by max_col and max_line.

		Details:
			- Positions are generated starting from (1,1) up to (max_col, max_line).
			- The list of all possible positions is shuffled randomly.
			- The first `nbwanted` positions from the shuffled list are returned.
		"""
		temp = []
		for x in range(0, max_col):
			for y in range(0, max_line):
				temp.append((x + 1, y + 1))

		random.shuffle(temp)
		return temp[:nbwanted]

	def save_script(self):
		"""
		Save the current script of words and their positions to a CSV file.

		- Collects word identifiers and their associated x, y positions.
		- Writes the data to a tab-separated CSV file named "script.csv" in the participant's session directory.

		The CSV file contains rows of:
			[word_identifier, position_x, position_y]
		"""
		towrite = []
		for i in range(0, len(self.words)):
			towrite.append(
				[self.words[i][1], self.positions[i][0], self.positions[i][1]]
			)

		# CSVManager.write_file(self.session_path + self.participant_code + "_" + self.session_date + "_" +
		#                       self.session_affix + self.module_name + "_script.csv", "\t", towrite, "w+")

		CSVManager.write_file(
			ParticipantManager.full_session_path
			+ ParticipantManager.filename_prefix
			+ "script.csv",
			"\t",
			towrite,
			"w+",
		)
