# encoding=utf-8
__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"

from enum import Enum
from .cotation_acoustic_analysis import CotationAcousticAnalysis
from cotation.acoustic.struct.parameters.f0_prerequisits import F0Prerequisits
from cotation.acoustic.struct.parameters.f0_selection import F0Selection
from ..pm.parselmouth_intensity import ParselmouthIntensity
from ..pm.parselmouth_pitch import ParselmouthPitch
from typing import Optional, Union
from cotation.acoustic.display.io_widget.info import InfoBox
from cotation.acoustic.display.io_widget.info import Info
from cotation.acoustic.display.io_widget.output import Output

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

import pyqtgraph


# @property transparantly have getter and setter.
# class.x instead of class.(get,set)_x()

DEFAULT_SOUND_FILE_PATTERN = "{}_{}_Module{}.wav"

# TODO find a better name


class LaurieType(Enum):
	"""
	Enumeration representing the intonation type of the 'Laurie' phrase.

	Members:
	    NEUTRE: A neutral intonation pattern (e.g., statement).
	    QUESTION: A rising intonation pattern (e.g., question).

	This enum is used to distinguish between different prosodic variants of the Laurie task.
	"""

	NEUTRE = "Neutre"
	QUESTION = "Question"

	def __str__(self) -> str:
		return self.value


class Laurie(CotationAcousticAnalysis):
	"""
	Acoustic analysis task for the 'Laurie' sentence, which involves pitch tracking and prosodic feature extraction.

	This class supports different intonation types (e.g., neutral, question) through the LaurieType enum.
	It allows signal segmentation and calculates key metrics such as semitone range, mean F0,
	pitch standard deviation, and harmonics-to-noise ratio (HNR).

	Attributes:
	    pitch (Optional[ParselmouthPitch]): Pitch analysis object.
	    intensity (Optional[ParselmouthIntensity]): Intensity analysis object.
	    laurie_type (LaurieType): Type of the Laurie utterance (neutral or question).
	    is_signal_selection_movable (bool): Whether the signal segment can be moved.
	    is_signal_selection_manually_resizable (bool): Whether the segment boundaries can be resized.
	    signal_selection_min_length (float): Minimum allowed segment duration.
	    signal_selection_max_length (float): Maximum allowed segment duration.
	    pitch_plot (PlotDataItem): PyQtGraph item for pitch visualization.
	    hnr, std_dev_out, mean_picth_out, semitone_range_out (Output): Output display elements for results.
	"""

	pitch: Optional[ParselmouthPitch]

	intensity: Optional[ParselmouthIntensity]
	laurie_type: LaurieType

	DEFAULT_F0_RANGE_MIN_MAX = ()

	def __init__(
		self,
		laurie_type: LaurieType,
		speaker_code: str,
		session_date: str,
	):
		super().__init__(
			speaker_code,
			f"Phrases_Laurie-{laurie_type}",
			session_date,
		)
		self.f0_prereq = F0Prerequisits()
		self.f0_selection = F0Selection()
		self.hnr = Output("Rapport Harmoniques/Bruit", "")
		self.std_dev_out = Output("Écart-type F0", "")
		self.mean_picth_out = Output("F0 moyenne", "")
		self.semitone_range_out = Output("Semitone range", "")
		self.is_signal_selection_movable = True
		self.is_signal_selection_manually_resizable = True
		self.signal_selection_min_length = 0.1
		self.signal_selection_max_length = 100

		self.laurie_type = laurie_type

		self.pitch = None
		self.intensity = None

		self.on_interval_change.append(self.load_data)

		self.pitch_plot = pyqtgraph.PlotDataItem()

	@override
	def get_results(self) -> dict[str, Union[float, str]]:
		res: dict[str, Union[float, str]] = {}
		if self.intensity.get_is_weak_snr():
			# TODO Find the error code
			res["ErrorMessage"] = (
				f"Faible rapport signal/bruit (enregistrement de mauvaise qualité): {self.intensity.get_signal_to_noise_ratio()}dB.\n"
			)

		if self.intensity.get_is_unusable_snr():
			# TODO Error out ?
			return res

		if self.pitch.get_is_high_voice_breaks():
			res["ErrorMessage"] = (
				str(res.get("errorMessage", ""))
				+ f"Taux important de ruptures de voisement: {self.pitch.get_degree_voicing()}"
			)

		if self.pitch.get_is_unusable_voice_breaks():
			# TODO Error out ?
			return res

		return {
			f"laurie{self.laurie_type.value[0]}_temp": self.pitch.get_semitone_range(),
			"ErrorMessage": res.get("ErrorMessage", ""),
		}

	@override
	def get_io(self) -> Info:
		"""
		Prepare and return the input/output interface for the Laurie segmentation task, including info boxes for instructions, results outputs, F0 selection, and prerequisites.
		"""
		info_box = InfoBox(
			f"Segmentation Laurie {self.laurie_type.value[0]}",
			"Ajuster les frontières, précisément, au début et à la fin de la phrase.",
			# "Si nécessaire, ajouter un # indiquant une pause pendant la phrase.",
		)

		results = InfoBox(
			"Résultats",
			dynamic_content=[
				self.semitone_range_out,
				self.mean_picth_out,
				self.std_dev_out,
				self.hnr,
			],
		)

		self.f0_selection.set_f0_min_or_max_changed_callback(self.f0_min_or_max_changed)
		# self.f0_selection.min_f0_range_input.set_value_changed_callback(self._update_pitch_range)
		# self.f0_selection.max_f0_range_input.set_value_changed_callback(self._update_pitch_range)

		info = Info()

		info.add_infobox(info_box)
		info.add_infobox(results)
		info.add_infobox(self.f0_prereq)
		info.add_infobox(self.f0_selection)
		return info

	@override
	def update_io(self):
		"""
		Update the displayed output values based on current pitch, voice report, and intensity data. Also update F0 selection fields accordingly.
		"""
		self.semitone_range_out.update_value(self.pitch.get_semitone_range())
		self.mean_picth_out.update_value(self.pitch.get_mean())
		self.std_dev_out.update_value(self.pitch.get_standard_deviation())
		self.hnr.update_value(self.voice_report.get_harmonics_to_noise_ratio())

		self.f0_prereq.voice_breaks.update_value(self.pitch.get_degree_voicing())
		self.f0_prereq.signal_to_noise_ratio.update_value(
			self.intensity.get_signal_to_noise_ratio()
		)

		self.f0_selection.min_f0.update_value(self.pitch.get_minimum())
		self.f0_selection.max_f0.update_value(self.pitch.get_maximum())

		if self.min_max_pitch_range is not None:
			min_pitch_range, max_pitch_range = self.min_max_pitch_range
			self.f0_selection.min_f0_range.set_input_value(min_pitch_range)
			self.f0_selection.max_f0_range.set_input_value(max_pitch_range)

	def _update_pitch_range(self):
		"""
		Update the minimum and maximum pitch range values from the F0 selection input fields.
		"""
		min_pitch_range = self.f0_selection.min_f0_range.get_input_value()
		max_pitch_range = self.f0_selection.max_f0_range.get_input_value()
		self.min_max_pitch_range = (min_pitch_range, max_pitch_range)

	@override
	def get_required_plots(self) -> tuple[list[pyqtgraph.PlotDataItem], float, float]:
		"""
		Return the list of required plots and their y-axis range for visualization.
		"""
		return [self.pitch_plot], 0, 500

	@override
	def reload_plots(self):
		"""
		Reload and update the pitch plot with the current pitch data.
		"""
		self.pitch_plot.setData(*self.pitch.get_plot_values())

	def f0_min_or_max_changed(self):
		"""
		Handle changes to the minimum or maximum F0 values by updating the pitch range accordingly.
		"""
		self._update_pitch_range()
