# 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 dataclasses import dataclass
from typing import Union, Callable

from cotation.acoustic.struct.acoustic_value import AcousticValue
from cotation.acoustic.struct.operation import Operation


@dataclass
class CalculatedIndicator:
	code: str
	component_ids: list[str]
	op: Operation

	def filter_components(
		self, component_candidates: list[AcousticValue]
	) -> list[AcousticValue]:
		"""
		Filters the candidate acoustic components, keeping only those whose code matches an expected identifier.

		Args:
			component_candidates (list[AcousticValue]): List of available acoustic components.

		Returns:
			list[AcousticValue]: Filtered list containing only valid components.
		"""

		return [
			compo for compo in component_candidates if compo.code in self.component_ids
		]

	def calculatable(self, component_candidates: list[AcousticValue]) -> bool:
		"""
		Checks if all required components are present among the candidates.

		Args:
			component_candidates (list[AcousticValue]): List of available acoustic components.

		Returns:
			bool: True if calculation is possible (all required components are present), False otherwise.
		"""

		return len(self.component_ids) == len(
			self.filter_components(component_candidates)
		)

	def calculate(self, component_candidates: list[AcousticValue]) -> Union[float, int]:
		"""
		Calculates the final value from the filtered acoustic components,
		applying the defined operation (`self.op`).

		Args:
			component_candidates (list[AcousticValue]): List of available acoustic components.

		Returns:
			float | int: Result of the calculation.

		Raises:
			ValueError: If the number of available components is incorrect.
		"""

		if not self.calculatable(component_candidates):
			raise ValueError("Wrong number of components.")

		map_f: Callable[[AcousticValue], Union[float, int]] = lambda comp: comp.value

		return self.op(list(map(map_f, self.filter_components(component_candidates))))

	def calculate_to_acoustif_value(
		self, component_candidates: list[AcousticValue]
	) -> AcousticValue:
		"""
		Calculates a new acoustic value from the provided components
		and returns it as an `AcousticValue` object.

		Args:
			component_candidates (list[AcousticValue]): List of available acoustic components.

		Returns:
			AcousticValue: New calculated acoustic value with the associated code.

		Raises:
			ValueError: If the number of provided components is incorrect for performing the calculation.
		"""

		if not self.calculatable(component_candidates):
			raise ValueError("Wrong number of components.")

		return AcousticValue(self.code, self.calculate(component_candidates), None)
