# encoding=utf-8

__author__ = "Aaron Randreth"
__copyright__ = "Copyright 2015+, Consortium MonPaGe"
__license__ = "Creative Commons 4.0 By-Nc-Sa"
__maintainer__ = "Roland Trouville"
__email__ = "contact.monpage@gmail.com"
__status__ = "Production"

from typing import Optional

from cotation.acoustic.struct.acoustic_value import AcousticValue
from cotation.acoustic.struct.calculated_indicator import CalculatedIndicator
from cotation.acoustic.struct.analysis.cotation_acoustic_analysis import (
	CotationAcousticAnalysis,
)
from cotation.acoustic.struct.operation import Operation
from cotation.acoustic.struct.value_persistence import ValuePersistence


class IndicatorPersistence:
	"""
	Manages the calculation and persistence of acoustic indicators for a patient session.

	Attributes:
	    calculated_indicators (list[CalculatedIndicator]): A list of pre-defined calculated indicators,
	        each representing a derived metric based on base indicators and an operation.
	    value_persistence (ValuePersistence): Handles saving and retrieving acoustic values from storage.

	Methods:
	    calculated_indicator_to_acoustic_value(indicator, acoustic_values):
	        Converts a CalculatedIndicator into an AcousticValue if all required data is available.

	    step_to_acoustic_values(step):
	        Converts a CotationAcousticAnalysis step into a list of AcousticValue objects.

	    save_all_indicators(steps):
	        Aggregates acoustic values from analysis steps, calculates derived indicators,
	        and saves all relevant values persistently.
	"""

	calculated_indicators: list[CalculatedIndicator]
	value_persistence: ValuePersistence

	def __init__(self, patient_code: str, judge_code: str, session_date: str):
		# The order matters here. If an indicator depends on an indicator that
		# is declared later, it will not be calculated.
		self.calculated_indicators = [
			CalculatedIndicator(
				"syll_4s_amrcv",
				["syll_4s_amrcv_ba", "syll_4s_amrcv_de", "syll_4s_amrcv_go"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"syll_4s_amrccv",
				["syll_4s_amrccv_kla", "syll_4s_amrccv_tra"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"syll_4s_smrcv_amrcv",
				["syll_4s_smrcv_badego", "syll_4s_amrcv"],
				Operation.DIFF,
			),
			CalculatedIndicator(
				"syll_2s_amrcv",
				["syll_2s_amrcv_ba", "syll_2s_amrcv_de", "syll_2s_amrcv_go"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"syll_2s_amrccv",
				["syll_2s_amrccv_kla", "syll_2s_amrccv_tra"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"syll_2s_smrcv_amrcv",
				["syll_2s_smrcv_badego", "syll_2s_amrcv"],
				Operation.DIFF,
			),
			CalculatedIndicator(
				"phon_4s_amrcv",
				["phon_4s_amrcv_ba", "phon_4s_amrcv_de", "phon_4s_amrcv_go"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"phon_4s_amrccv",
				["phon_4s_amrccv_kla", "phon_4s_amrccv_tra"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"phon_4s_smrcv_amrcv",
				["phon_4s_smrcv_badego", "phon_4s_amrcv"],
				Operation.DIFF,
			),
			CalculatedIndicator(
				"phon_2s_amrcv",
				["phon_2s_amrcv_ba", "phon_2s_amrcv_de", "phon_2s_amrcv_go"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"phon_2s_amrccv",
				["phon_2s_amrccv_kla", "phon_2s_amrccv_tra"],
				Operation.MEAN,
			),
			CalculatedIndicator(
				"phon_2s_smrcv_amrcv",
				["phon_2s_smrcv_badego", "phon_2s_amrcv"],
				Operation.DIFF,
			),
			CalculatedIndicator(
				"proso_melodic_contrast",
				["laurieQ_temp", "laurieN_temp"],
				Operation.DIFF,
			),
			CalculatedIndicator(
				"mpt",
				# TODO see if semaine needs to be added
				[
					"mpt_candidate_1",
					"mpt_candidate_2",
					#  "mpt_candidate_week"
				],
				Operation.MAX,
			),
		]

		self.value_persistence = ValuePersistence(
			patient_code, judge_code, session_date
		)

	@staticmethod
	def calculated_indicator_to_acoustic_value(
		indicator: CalculatedIndicator, acoustic_values: list[AcousticValue]
	) -> Optional[AcousticValue]:
		"""
		Converts a calculated indicator into an AcousticValue if it can be calculated
		from the provided list of acoustic values.

		Args:
			indicator (CalculatedIndicator): The indicator to convert.
			acoustic_values (list[AcousticValue]): List of acoustic values to use for calculation.

		Returns:
			Optional[AcousticValue]: The resulting AcousticValue if calculation is possible, otherwise None.
		"""

		if not indicator.calculatable(acoustic_values):
			return None

		return AcousticValue(indicator.code, indicator.calculate(acoustic_values), None)

	@staticmethod
	def step_to_acoustic_values(step: CotationAcousticAnalysis) -> list[AcousticValue]:
		"""
		Converts a CotationAcousticAnalysis step into a list of AcousticValue objects.

		Args:
			step (CotationAcousticAnalysis): The analysis step to convert.

		Returns:
			list[AcousticValue]: List of AcousticValue objects representing the results of the step.
			Returns an empty list if the step interval is None.
		"""

		if step.get_interval() is None:
			return []

		error: Optional[str] = step.get_results().get("ErrorMsg", None)
		if error == "":
			error = None

		return [
			AcousticValue(code, value, error)
			for code, value in step.get_results().items()
		]

	def save_all_indicators(self, steps: list[CotationAcousticAnalysis]):
		"""
		Saves all relevant acoustic indicators and values from the provided analysis steps
		and the internal persistence system.

		This includes:
		- Words not recognized (from the intelligibility module)
		- Segmental errors (from the pseudo_words module)
		- Acoustic values extracted from each step
		- Calculated indicators based on the collected acoustic values

		Only saves values that are marked as saveable.

		Args:
			steps (list[CotationAcousticAnalysis]): List of analysis steps from which to extract values.
		"""

		acoustic_values: list[AcousticValue] = []
		# Todo Segments error and intelligibility should be calculated at reporting time not acoustic cotation time
		# Words from the intellegibility module
		wnr: Optional[int] = self.value_persistence.get_words_not_recognized()
		if wnr is not None:
			acoustic_values.append(AcousticValue("words_not_recognized", wnr, None))

		# errors from the pseudo_words module
		segerr: Optional[int] = self.value_persistence.get_segmental_errors()
		if segerr is not None:
			acoustic_values.append(AcousticValue("segmental_errors", segerr, None))

		for step in steps:
			acoustic_values.extend(self.step_to_acoustic_values(step))

		for c in self.calculated_indicators:
			val = self.calculated_indicator_to_acoustic_value(c, acoustic_values)

			if val is None:
				continue
			acoustic_values.append(val)

		for val in acoustic_values:
			if not self.value_persistence.saveable(val.code):
				continue

			self.value_persistence.save_acoustic_value(val)

		self.value_persistence.commit()
