""" Module containing Sequence class """
from __future__ import annotations
from abc import ABC
from dataclasses import fields
from typing import TYPE_CHECKING, Any
import logging

from .parameter_class import ParameterClass
from .sequence_base import SequenceBase

if TYPE_CHECKING:
    from .measurement import Measurement

class SubSequence(SequenceBase, ABC):
    """
    Class describing a subsequence of a QUA programm (e.g Init, Control, Read). 
    """
    _enforce_parameter_class: bool = False
    def __init__(
            self,
            parent,
            name: str,
            sequence_config: dict | None = None,
            check_step_requirements: bool = False,
            **kwargs
            ):
        """
        Constructor class for `Program` class
        
        Args:
            name (str): Name of the program
            sequence_config (dict): Dictionary containing all device parameters
            check_step_requirements (bool): Whether to check step requirements
                for this subsequence
            **kwargs: Arbitrary keyword arguments.
        """
        super().__init__(
            parent, name, sequence_config, check_step_requirements, **kwargs)
        self.parent.add_subsequence(self)
        self.arbok_params = self.map_arbok_params()

    @property
    def measurement(self) -> Measurement:
        """Returns parent (sub) sequence"""
        return self.find_measurement()

    def qua_sequence(self):
        if self.check_step_requirements:
            self.measurement.qua_check_step_requirements(
                super().qua_sequence
            )
        else:
            super().qua_sequence()

    def map_arbok_params(self) -> ParameterClass:
        """Maps required params for QUA code to arbok_params attribute"""
        arg_names = [f.name for f in fields(self.PARAMETER_CLASS) if f.init]
        init_dict = self.measurement.get_parameters_and_maps(arg_names)
        init_dict.update(self.get_parameters_and_maps(arg_names))
        return self.PARAMETER_CLASS(**init_dict)

    def add_subsequences_from_dict(
            self,
            subsequence_dict: dict,
            namespace_to_add_to: dict | None = None) -> None:
        """
        Adds subsequences to the sequence from a given dictionary

        Args:
            subsequence_dict (dict): Dictionary containing the subsequences
            namespace_to_add_to (dict): Namespace to add the registered
                subsequences to
        """
        class ContainerParameterClass(ParameterClass):
            pass

        class ContainerSubSequence(SubSequence):
            PARAMETER_CLASS = ContainerParameterClass
    
        super()._add_subsequences_from_dict(
            default_sequence = ContainerSubSequence,
            subsequence_dict = subsequence_dict,
            namespace_to_add_to = namespace_to_add_to
        )

    def find_measurement(self) -> Measurement:
        """Recursively searches the parent sequence"""
        if self.parent.__class__.__name__ == 'Measurement':
            return self.parent
        elif isinstance(self.parent, SubSequence):
            return self.parent.find_measurement()
        else:
            raise ValueError(
                "Parent sequence must be of type Sequence. "
                f"Is of type {self.parent.__class__.__name__}")

    def get_sequence_path(self, path: str | None = None) -> str:
        """Returns the path of subsequences up to the parent sequence"""
        if path is None:
            path = ""
        if self.parent.__class__.__name__ == 'Measurement':
            return f"{self.parent.short_name}__{self.short_name}__{path}"
        elif self.parent is None:
            return f"{self.short_name}__{path}"
        else:
            return self.parent.get_sequence_path(f"{self.short_name}__{path}")

    def _return_measurement_parameters(self, key: str) -> Any:
        """Returns attribute from parent sequence"""
        logging.debug("Searching parent sequence %s for parameter %s",
                        self.measurement.name, key)
        if key in self.measurement.parameters:
            return self.measurement.parameters[key]
        raise AttributeError(
            f"Parameter {key} not found in {self.measurement.name}")
