# 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 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 cotation.acoustic.struct.pm.parselmouth_voice_report import ParselmouthVoiceReport

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

from typing import Optional, Union

import pyqtgraph

from cotation.acoustic.display.io_widget.info import Info, InfoBox
from cotation.acoustic.display.io_widget.output import Output

# @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 Aa2s(CotationAcousticAnalysis):
	pitch: Optional[ParselmouthPitch]

	intensity: Optional[ParselmouthIntensity]
	voice_report: Optional[ParselmouthVoiceReport]

	jitter_out: Output
	shimmer_out: Output
	hnr_out: Output
	stddev_out: Output
	scpp_out: Output

	f0_selection: F0Selection
	f0_prereq: F0Prerequisits

	min_max_pitch_range: Optional[tuple[float, float]]

	def __init__(
		self,
		speaker_code: str,
		session_date: str,
	):
		super().__init__(
			speaker_code,
			"PneumoPhonatoire_aa2s",
			session_date,
		)

		self.jitter_out = Output("Jitter", "")
		self.shimmer_out = Output("Shimmer", "")
		self.hnr_out = Output("HNR", "")
		self.stddev_out = Output("STD DEV", "")
		self.scpp_out = Output("SCPP", "")

		self.is_signal_selection_movable = True
		self.is_signal_selection_manually_resizable = True
		self.signal_selection_min_length = 1
		self.signal_selection_max_length = 2
		self.signal_selection_default_duration = 2

		self.pitch = None
		self.intensity = None
		self.voice_report = None

		self.on_interval_change.append(self.load_data)

		self.min_max_pitch_range = None
		self.pitch_plot = pyqtgraph.PlotDataItem()

		self.f0_selection = F0Selection()
		self.f0_prereq = F0Prerequisits()

	@override
	def get_results(self) -> dict[str, Union[float, str]]:
		"""
		Compute and return voice analysis metrics and potential error messages.

		Checks signal-to-noise ratio and voice break quality, returning error messages
		if the signal is weak or unusable.

		Returns:
			dict[str, Union[float, str]]: A dictionary containing:
				- 'voice_a_jitterppq5': Jitter measurement.
				- 'voice_a_shimmerapq11': Shimmer measurement.
				- 'voice_a_hnr': Harmonics-to-noise ratio.
				- 'voice_a_sdf0': Standard deviation of F0.
				- 'voice_a_cpps': Smoothed cepstral peak prominence.
				- 'ErrorMessage': An error message string if issues detected, otherwise empty.
		"""
		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"] = (
				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 {
			"voice_a_jitterppq5": self.pitch.get_jitter(),
			"voice_a_shimmerapq11": self.pitch.get_shimmer(),
			"voice_a_hnr": self.voice_report.get_harmonics_to_noise_ratio(),
			"voice_a_sdf0": self.pitch.get_standard_deviation(),
			"voice_a_cpps": self.pitch.get_smoothed_cepstral_peak_prominence(),
			"ErrorMessage": res.get("ErrorMessage", ""),
		}

	@override
	def get_io(self) -> Info:
		"""
		Create and return the Info object containing UI components for Aa2s segmentation.

		- Provides instructions for precisely adjusting the left boundary at the start of the sustained /a/ sound.
		- If the sustained /a/ is too short, adjust the right boundary to match the end of the /a/.
		- Only the first 2 seconds of the segment are analyzed.
		- Initializes outputs for jitter, shimmer, HNR, standard deviation, and smoothed cepstral peak prominence.
		- Sets up the pitch (F0) selection widget with callbacks.
		- Includes prerequisite info and dynamic results display.
		"""
		info_box = InfoBox(
			"Segmentation Aa2s",
			"Ajuster la frontière gauche précisément au début du /a/ tenu. Si le /a/ tenu est trop court,"
			" ajuster la frontière droite pour correspondre à la fin du /a/. Seules les 2 premières secondes sont analysées",
		)

		results = InfoBox(
			"Résultats",
			dynamic_content=[
				self.jitter_out,
				self.shimmer_out,
				self.hnr_out,
				self.stddev_out,
				self.scpp_out,
			],
		)

		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 UI elements with the latest pitch and voice analysis results.

		- Updates jitter, shimmer, harmonics-to-noise ratio (HNR), standard deviation, and smoothed cepstral peak prominence outputs.
		- Updates voice break degree and signal-to-noise ratio in the prerequisites info.
		- Updates minimum and maximum F0 values in the pitch selection widget.
		- If a pitch range is specified, updates the input fields for minimum and maximum pitch range accordingly.
		"""
		self.jitter_out.update_value(self.pitch.get_jitter())
		self.shimmer_out.update_value(self.pitch.get_shimmer())
		self.hnr_out.update_value(self.voice_report.get_harmonics_to_noise_ratio())
		self.stddev_out.update_value(self.pitch.get_standard_deviation())
		self.scpp_out.update_value(self.pitch.get_smoothed_cepstral_peak_prominence())

		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 inputs.
		Stores the updated pitch range as a tuple (min_pitch_range, max_pitch_range).
		"""
		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 a tuple containing a list of plots to display and the y-axis limits for the pitch plot.
		The list contains the pitch plot, and the y-axis range is fixed from 0 to 500.
		"""
		return [self.pitch_plot], 0, 500

	@override
	def reload_plots(self):
		"""
		Refresh the pitch plot data using the latest pitch plot values.
		"""
		self.pitch_plot.setData(*self.pitch.get_plot_values())

	def f0_min_or_max_changed(self):
		"""
		Callback triggered when the minimum or maximum F0 values change.
		Updates the pitch range accordingly.
		"""
		self._update_pitch_range()
