# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Restless mixin class."""

import logging
from typing import Callable, Sequence, Optional
from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit.utils.deprecation import deprecate_func

from qiskit.providers import Backend
from qiskit_experiments.framework import Options
from qiskit_experiments.data_processing.data_processor import DataProcessor
from qiskit_experiments.data_processing.exceptions import DataProcessorError
from qiskit_experiments.data_processing.nodes import ProjectorType
from qiskit_experiments.data_processing import nodes
from qiskit_experiments.data_processing.processor_library import get_kerneled_processor
from qiskit_experiments.framework.base_analysis import BaseAnalysis

LOG = logging.getLogger(__name__)


class RestlessMixin:
    """A mixin to facilitate restless experiments.

    This class defines the following methods:

    - :meth:`~.RestlessMixin.enable_restless`
    - :meth:`~.RestlessMixin._get_restless_processor`
    - :meth:`~.RestlessMixin._t1_check`

    A restless enabled experiment is an experiment that can be run in a restless
    measurement setting. In restless measurements, the qubit is not reset after
    each measurement. Instead, the outcome of the previous quantum non-demolition
    measurement is the initial state for the current circuit. Restless measurements
    therefore require special data processing which is provided by sub-classes of
    the :class:`.RestlessNode`. Restless experiments are a fast alternative for
    several calibration and characterization tasks, for details see
    https://arxiv.org/pdf/2202.06981.pdf.

    This class makes it possible for users to enter a restless run mode without having
    to manually set all the required run options and the data processor. The required options
    are ``rep_delay``, ``init_qubits``, ``memory``, and ``meas_level``. Furthermore,
    subclasses can override the :meth:`_get_restless_processor` method if they require more
    complex restless data processing such as two-qubit calibrations. In addition, this
    class makes it easy to determine if restless measurements are supported for a given
    experiment.

    User Manual
        :doc:`/manuals/measurement/restless_measurements`

    """

    analysis: BaseAnalysis
    _default_run_options: Options()
    set_run_options: Callable
    _backend: Backend
    _physical_qubits: Sequence[int]
    _num_qubits: int

    @deprecate_func(
        since="0.8",
        package_name="qiskit-experiments",
        additional_msg=("Support for restless experiments has been deprecated."),
    )
    def enable_restless(
        self,
        rep_delay: Optional[float] = None,
        override_processor_by_restless: bool = True,
        suppress_t1_error: bool = False,
    ):
        """Enables a restless experiment by setting the restless run options and the
        restless data processor.

        Args:
            rep_delay: The repetition delay. This is the delay between a measurement
                and the subsequent quantum circuit. Since the backends have
                dynamic repetition rates, the repetition delay can be set to a small
                value which is required for restless experiments. Typical values are
                1 us or less.
            override_processor_by_restless: If False, a data processor that is specified in the
                analysis options of the experiment is not overridden by the restless data
                processor. The default is True.
            suppress_t1_error: If True, the default is False, then no error will be raised when
                ``rep_delay`` is larger than the T1 times of the qubits. Instead, a warning will
                be logged as restless measurements may have a large amount of noise.

        Raises:
            DataProcessorError: If the attribute rep_delay_range is not defined for the backend.
            DataProcessorError: If a data processor has already been set but
                override_processor_by_restless is True.
            DataProcessorError: If the experiment analysis does not have the data_processor
                option.
            DataProcessorError: If the rep_delay is equal to or greater than the
                T1 time of one of the physical qubits in the experiment and the flag
                ``ignore_t1_check`` is False.
        """
        try:
            if not rep_delay:
                # BackendV1 only; BackendV2 does not support this
                rep_delay = self._backend.configuration().rep_delay_range[0]
        except AttributeError as error:
            raise DataProcessorError(
                "The restless experiment can not be enabled because "
                "the attribute rep_delay_range is not defined for this backend "
                "and a minimum rep_delay can not be set."
            ) from error

        # Check the rep_delay compared to the T1 time.
        if not self._t1_check(rep_delay):
            msg = (
                f"The specified repetition delay {rep_delay} is equal to or greater "
                f"than the T1 time of one of the physical qubits"
                f"{self._physical_qubits} in the experiment. Consider choosing "
                f"a smaller repetition delay for the restless experiment."
            )

            if suppress_t1_error:
                LOG.warning(msg)
            else:
                raise DataProcessorError(msg)

        # The excited state promotion readout analysis option is set to
        # False because it is not compatible with a restless experiment.
        meas_level = self._default_run_options().get("meas_level", MeasLevel.CLASSIFIED)
        meas_return = self._default_run_options().get("meas_return", MeasReturnType.SINGLE)
        if not self.analysis.options.get("data_processor", None):
            self.set_run_options(
                rep_delay=rep_delay,
                init_qubits=False,
                memory=True,
                meas_level=meas_level,
                meas_return=meas_return,
                use_measure_esp=False,
            )
            if hasattr(self.analysis.options, "data_processor"):
                self.analysis.set_options(
                    data_processor=self._get_restless_processor(meas_level=meas_level)
                )
            else:
                raise DataProcessorError(
                    "The restless data processor can not be set since the experiment analysis"
                    "does not have the data_processor option."
                )
        else:
            if not override_processor_by_restless:
                self.set_run_options(
                    rep_delay=rep_delay,
                    init_qubits=False,
                    memory=True,
                    meas_level=meas_level,
                    meas_return=meas_return,
                    use_measure_esp=False,
                )
            else:
                raise DataProcessorError(
                    "Cannot enable restless. Data processor has already been set and "
                    "override_processor_by_restless is True."
                )

    def _get_restless_processor(self, meas_level: int = MeasLevel.CLASSIFIED) -> DataProcessor:
        """Returns the restless experiments data processor.

        Notes:
            Sub-classes can override this method if they need more complex data processing.
        """
        outcome = self.analysis.options.get("outcome", "1" * self._num_qubits)
        meas_return = self.analysis.options.get("meas_return", MeasReturnType.SINGLE)
        normalize = self.analysis.options.get("normalization", True)
        dimensionality_reduction = self.analysis.options.get(
            "dimensionality_reduction", ProjectorType.SVD
        )

        if meas_level == MeasLevel.KERNELED:
            return get_kerneled_processor(
                dimensionality_reduction, meas_return, normalize, [nodes.RestlessToIQ()]
            )

        else:
            return DataProcessor(
                "memory",
                [
                    nodes.RestlessToCounts(self._num_qubits),
                    nodes.Probability(outcome),
                ],
            )

    def _t1_check(self, rep_delay: float) -> bool:
        """Check that repetition delay < T1 of the physical qubits in the experiment.

        Args:
            rep_delay: The repetition delay. This is the delay between a measurement
                    and the subsequent quantum circuit.

        Returns:
            True if the repetition delay is smaller than the qubit T1 times.

        Raises:
            DataProcessorError: If the T1 values are not defined for the qubits of
                the used backend.
        """

        try:
            t1_values = [
                self._backend_data.qubit_t1(physical_qubit)
                for physical_qubit in self._physical_qubits
            ]

            if all(rep_delay / t1_value < 1.0 for t1_value in t1_values):
                return True
        except AttributeError as error:
            raise DataProcessorError(
                "The restless experiment can not be enabled since "
                "T1 values are not defined for the qubits of the used backend."
            ) from error

        return False
