from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit import Parameter
from qiskit.circuit.library import XXPlusYYGate

from .base_mixer import Mixer

import math
import itertools

import numpy as np


class XY(Mixer):
    """
    XY mixer.

    Subclass of the `Mixer` subclass that implements the XY mixing operation.

    Attributes:
        topology (list): The topology of the mixer, default is None.
        mixer_param (Parameter): The parameter for the XY mixer.
        N_qubits (int): The number of qubits in the mixer circuit.
        circuit (QuantumCircuit): The quantum circuit representing the XY mixer.

    Methods:
        create_circuit(): Constructs the XY mixer circuit using the specified topology.
        generate_pairs(n, case="ring"): Generates pairs of qubits based on the specified topology.
    """

    def __init__(self, topology=None, case="ring") -> None:
        """
        Initializes the XY mixer.

        Args:
            topology (list, optional): The topology of the mixer as list of connected pairs. If None, generated by the 'case' parameter
            case (str, optional): The topology as string. If topology is None, case is used to generate topology using "ring" as default 
        """
        self.topology = topology
        self.case = case
        self.mixer_param = Parameter("x_beta")

    def create_circuit(self):
        """
        Constructs the XY mixer circuit using the specified topology.

        If no topology is specified, it defaults to a "ring" topology.
        """
        if not self.topology:
            self.topology = XY.generate_pairs(self.N_qubits, case=self.case)

        q = QuantumRegister(self.N_qubits)
        self.circuit = QuantumCircuit(q)

        for i, e in enumerate(self.topology):
            self.circuit.append(XXPlusYYGate(0.5 * self.mixer_param), e)

    @staticmethod
    def generate_pairs(n, case="ring"):
        """Generates pairs of adjacent qubit indices for the given topology.

        Args:
            n (int): The number of qubits.
            case (str, optional): Topology. Defaults to "ring".

        Returns:
            list: A list of pairs of qubit indices based on the specified topology.
        """
        assert(case in ["ring", "chain"])

        # default ring, otherwise "chain"
        if n < 2:
            return []  # Not enough elements to form any pairs

        pairs = [[i, i + 1] for i in range(n - 1)]

        if case == "ring":
            pairs.append([n - 1, 0])

        return pairs
