"""
Copyright 2024 Entropica Labs Pte Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

"""

from dataclasses import dataclass, field
import uuid
from typing import Optional

from .base_operation import Operation, OpType
from .controlled_operation import has_ccontrol


@dataclass
@has_ccontrol
class MeasurementOperation(Operation):
    """
    Operations of this type perform measurements on qubits within the Engine during
    runtime.
    """

    operation_type: str = field(default=OpType.MEASUREMENT, init=False)
    target_qubit: int


@dataclass
class Measurement(MeasurementOperation):
    """
    Creates a measurement operation for a single qubit.
    By default, the output of the measurement is saved to the DataStore. If the user
    wants to write the output onto the classical register, the classical register
    needs to be specified with reg_name and (bit_order or bit_id).

    Parameters
    ----------
    label: str
        The ID of the Measurement Operation. Automatically generated by Default.
    reg_name: Optional[str]
        The name of the classical register to write the measurement output to.
    bit_order: Optional[int]
        The ordering of the bit within the classical register to write the measurement
        output to.
    bit_id: Optional[str]
        The bit ID of the bit within the classical register to write the measurement
        output to.
    basis: str
        The basis of the measurement. Default is Z.
    bias: float
        The bias of the measurement. Default is 0.5.
    """

    name: str = field(default="Measurement", init=False)
    label: str = field(default_factory=lambda: str(uuid.uuid4()), init=True)
    reg_name: Optional[str] = field(default=None)
    bit_order: Optional[int] = field(default=None)
    bit_id: Optional[str] = field(default=None)

    # Parameters for the measurement operation
    basis: str = field(default="Z")
    bias: float = field(default=0.5)

    def __post_init__(self):
        # Checks only trigger if a reg_name is specified.
        if self.reg_name is not None:
            if self.bit_order is None and self.bit_id is None:
                raise ValueError(
                    "Either the bit order or the bit id of the classical bit has to be"
                    "specified."
                )
            if self.bit_order is not None and self.bit_id is not None:
                raise ValueError(
                    "Both bit_order and bit_id cannot be specified together. Only input"
                    " 1 parameter, not both."
                )


@dataclass
class Reset(MeasurementOperation):
    """
    Resets a qubit to a specific cardinal state. By default, the qubit is reset to
    the `|0>` state.

    Parameters
    ----------
    state: str
        The state to which the qubit is reset. The state can be one of the following:
        0, 1, +, -, +i, -i. Default is 0.
    """

    name: str = field(default="Reset", init=False)
    label: str = field(default_factory=lambda: str(uuid.uuid4()), init=True)
    state: str = "0"

    def __post_init__(self):
        if self.state not in ["0", "1", "+", "-", "+i", "-i"]:
            raise ValueError(
                "The state of the reset operation must be either 0, 1, "
                "+, -, +i, or -i."
            )
