# This code is part of Qiskit.
#
# (C) Copyright IBM 2023
#
# 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.

# pylint: disable=missing-docstring,invalid-name,no-member
# pylint: disable=attribute-defined-outside-init

import copy

from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister

try:
    from qiskit.compiler import transpile

    TRANSPILER_SEED_KEYWORD = "seed_transpiler"
except ImportError:
    from qiskit.transpiler import transpile

    TRANSPILER_SEED_KEYWORD = "seed_mapper"
try:
    from qiskit.quantum_info.random import random_unitary

    HAS_RANDOM_UNITARY = True
except ImportError:
    from qiskit.tools.qi.qi import random_unitary_matrix

    HAS_RANDOM_UNITARY = False


# Make a random circuit on a ring
def make_circuit_ring(nq, depth, seed):
    assert int(nq / 2) == nq / 2  # for now size of ring must be even
    # Create a Quantum Register
    q = QuantumRegister(nq)
    # Create a Classical Register
    c = ClassicalRegister(nq)
    # Create a Quantum Circuit
    qc = QuantumCircuit(q, c)
    offset = 1
    decomposer = OneQubitEulerDecomposer()
    # initial round of random single-qubit unitaries
    for i in range(nq):
        qc.h(q[i])
    for j in range(depth):
        for i in range(int(nq / 2)):  # round of CNOTS
            k = i * 2 + offset + j % 2  # j%2 makes alternating rounds overlap
            qc.cx(q[k % nq], q[(k + 1) % nq])
        for i in range(nq):  # round of single-qubit unitaries
            if HAS_RANDOM_UNITARY:
                u = random_unitary(2, seed).data
            else:
                u = random_unitary_matrix(2)  # pylint: disable=used-before-assignment  # noqa

            angles = decomposer.angles(u)
            qc.u3(angles[0], angles[1], angles[2], q[i])

    # insert the final measurements
    qcm = copy.deepcopy(qc)
    for i in range(nq):
        qcm.measure(q[i], c[i])
    return [qc, qcm, nq]


class BenchRandomCircuitHex:
    params = [2 * i for i in range(2, 8)]
    param_names = ["n_qubits"]
    version = 3

    def setup(self, n):
        depth = 2 * n
        self.seed = 0
        self.circuit = make_circuit_ring(n, depth, self.seed)[0]

    def time_ibmq_backend_transpile(self, _):
        # Run with ibmq_16_melbourne configuration
        coupling_map = [
            [1, 0],
            [1, 2],
            [2, 3],
            [4, 3],
            [4, 10],
            [5, 4],
            [5, 6],
            [5, 9],
            [6, 8],
            [7, 8],
            [9, 8],
            [9, 10],
            [11, 3],
            [11, 10],
            [11, 12],
            [12, 2],
            [13, 1],
            [13, 12],
        ]
        transpile(
            self.circuit,
            basis_gates=["u1", "u2", "u3", "cx", "id"],
            coupling_map=coupling_map,
            **{TRANSPILER_SEED_KEYWORD: self.seed},
        )

    def track_depth_ibmq_backend_transpile(self, _):
        # Run with ibmq_16_melbourne configuration
        coupling_map = [
            [1, 0],
            [1, 2],
            [2, 3],
            [4, 3],
            [4, 10],
            [5, 4],
            [5, 6],
            [5, 9],
            [6, 8],
            [7, 8],
            [9, 8],
            [9, 10],
            [11, 3],
            [11, 10],
            [11, 12],
            [12, 2],
            [13, 1],
            [13, 12],
        ]
        return transpile(
            self.circuit,
            basis_gates=["u1", "u2", "u3", "cx", "id"],
            coupling_map=coupling_map,
            **{TRANSPILER_SEED_KEYWORD: self.seed},
        ).depth()
