# 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.struct.analysis.cotation_acoustic_analysis import (
	CotationAcousticAnalysis,
)
from cotation.acoustic.struct.parameters.f0_prerequisits import F0Prerequisits
from cotation.acoustic.struct.parameters.f0_selection import F0Selection
from cotation.acoustic.struct.pm.parselmouth_intensity import ParselmouthIntensity
from cotation.acoustic.struct.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.output import Output
from cotation.acoustic.display.io_widget.info import Info
import pyqtgraph

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

# @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 MelanieType(Enum):
	"""
	Enumeration representing the intonation type of the 'Melanie' sentence.

	Members:
	    NEUTRE: A neutral intonation (e.g., declarative sentence).

	This enum allows potential expansion to other prosodic forms of the 'Melanie' task in the future.
	"""

	NEUTRE = "Neutre"

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


class Melanie(CotationAcousticAnalysis):
	"""
	Acoustic analysis task for the 'Melanie' sentence, designed to measure speech fluency and pitch-related features.

	The class enables segmentation of a spoken sentence and extraction of relevant metrics such as phoneme and syllable rates,
	pitch statistics (mean, standard deviation, covariation), voice quality indicators (HNR, SCPP), and more.

	Attributes:
	    pitch (Optional[ParselmouthPitch]): Object handling pitch extraction.
	    intensity (Optional[ParselmouthIntensity]): Object handling intensity and SNR computation.
	    rate_phon_out, rate_syll_out (Output): Output elements for phoneme and syllable speech rates.
	    mean_picth_out, std_dev_out, covar_out (Output): Output elements for pitch statistics.
	    scpp_out (Output): Output for smoothed cepstral peak prominence (SCPP).
	    hnr (Output): Output for harmonics-to-noise ratio.
	    f0_selection (F0Selection): User interface for selecting F0 min/max values.
	    f0_prereq (F0Prerequisits): Widget displaying signal quality requirements.
	    is_signal_selection_movable (bool): Whether the audio segment boundaries are draggable.
	    is_signal_selection_manually_resizable (bool): Whether segment boundaries can be resized manually.
	    signal_selection_min_length (float): Minimum allowed segment duration.
	    signal_selection_max_length (float): Maximum allowed segment duration.
	    pitch_plot (PlotDataItem): Visualization object for the pitch contour.
	"""

	pitch: Optional[ParselmouthPitch]
	intensity: Optional[ParselmouthIntensity]

	def __init__(
		self,
		melanie_type: MelanieType,
		speaker_code: str,
		session_date: str,
	):
		super().__init__(
			speaker_code,
			f"Phrases_Melanie-{melanie_type}",
			session_date,
		)
		self.rate_phon_out = Output("Rate Phon", "")
		self.rate_syll_out = Output("Rate Syll", "")
		self.mean_picth_out = Output("F0 moyenne", "")
		self.std_dev_out = Output("Écart-type F0", "")
		self.covar_out = Output("Covariation F0", "")
		self.scpp_out = Output("SCPP", "")
		self.hnr = Output("Rapport Harmoniques/Bruit", "")
		self.f0_selection = F0Selection()
		self.f0_prereq = F0Prerequisits()
		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.pitch = None
		self.intensity = None

		self.on_interval_change.append(self.load_data)

		self.pitch_plot = pyqtgraph.PlotDataItem()

	def get_rate_phon(self) -> float:
		"""
		Calculate the phoneme rate for the current interval by dividing 14.0 by the interval duration.
		"""
		return 14.0 / self.get_interval_duration()

	def get_rate_syll(self) -> float:
		"""
		Calculate the syllable rate for the current interval by dividing 7.0 by the interval duration.
		"""
		return 7.0 / self.get_interval_duration()

	@override
	def get_results(self) -> dict[str, Union[float, str]]:
		"""
		Return the results of the analysis as a dictionary including speech rates, pitch statistics, and error messages if any signal quality issues are detected.
		"""
		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 {
			"rate_sentence_phon": self.get_rate_phon(),
			"rate_sentence_syll": self.get_rate_syll(),
			"voice_speaking_meanf0": self.pitch.get_mean(),
			"voice_speaking_sdf0": self.pitch.get_standard_deviation(),
			"voice_speaking_varcof0": self.pitch.get_covariation(),
			"voice_speaking_cpps": self.pitch.get_smoothed_cepstral_peak_prominence(),
			"ErrorMessage": res.get("ErrorMessage", ""),
		}

	@override
	def get_io(self) -> Info:
		"""
		Prepare and return the input/output interface for the Melanie segmentation task, including info boxes for instructions, F0 calculation results, F0 selection, and prerequisites.
		"""
		info_box = InfoBox(
			"Segmentation Melanie",
			"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.",
		)

		info_f0 = InfoBox(
			"Calcul du F0",
			dynamic_content=[
				self.rate_phon_out,
				self.rate_syll_out,
				self.mean_picth_out,
				self.std_dev_out,
				self.covar_out,
				self.scpp_out,
				self.hnr,
			],
		)

		self.f0_selection.set_f0_min_or_max_changed_callback(self.f0_min_or_max_changed)

		info = Info()
		info.add_infobox(info_box)
		info.add_infobox(info_f0)
		info.add_infobox(self.f0_prereq)
		info.add_infobox(self.f0_selection)
		return info

	@override
	def update_io(self):
		"""
		Update all displayed output values based on current pitch, voice report, and intensity data, including speech rates and pitch statistics. Also update F0 selection fields accordingly.
		"""
		self.rate_phon_out.update_value(self.get_rate_phon())
		self.rate_syll_out.update_value(self.get_rate_syll())
		self.mean_picth_out.update_value(self.pitch.get_mean())
		self.std_dev_out.update_value(self.pitch.get_standard_deviation())
		self.covar_out.update_value(self.pitch.get_covariation())
		self.scpp_out.update_value(self.pitch.get_smoothed_cepstral_peak_prominence())
		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 along with the 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 the event when the minimum or maximum F0 selection changes by updating the pitch range values.
		"""
		self._update_pitch_range()
