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

"""Test the decompose pass"""

from numpy import pi

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.transpiler.passes import Decompose
from qiskit.converters import circuit_to_dag
from qiskit.circuit.library import HGate, CCXGate, U2Gate
from qiskit.quantum_info.operators import Operator, Clifford

from test import QiskitTestCase  # pylint: disable=wrong-import-order


class TestDecompose(QiskitTestCase):
    """Tests the decompose pass."""

    def setUp(self):
        super().setUp()
        # example complex circuit
        #       ┌────────┐               ┌───┐┌─────────────┐
        # q2_0: ┤0       ├────────────■──┤ H ├┤0            ├
        #       │        │            │  └───┘│  circuit-57 │
        # q2_1: ┤1 gate1 ├────────────■───────┤1            ├
        #       │        │┌────────┐  │       └─────────────┘
        # q2_2: ┤2       ├┤0       ├──■──────────────────────
        #       └────────┘│        │  │
        # q2_3: ──────────┤1 gate2 ├──■──────────────────────
        #                 │        │┌─┴─┐
        # q2_4: ──────────┤2       ├┤ X ├────────────────────
        #                 └────────┘└───┘
        circ1 = QuantumCircuit(3)
        circ1.h(0)
        circ1.t(1)
        circ1.x(2)
        my_gate = circ1.to_gate(label="gate1")
        circ2 = QuantumCircuit(3)
        circ2.h(0)
        circ2.cx(0, 1)
        circ2.x(2)
        my_gate2 = circ2.to_gate(label="gate2")
        circ3 = QuantumCircuit(2)
        circ3.x(0)
        q_bits = QuantumRegister(5)
        qc = QuantumCircuit(q_bits)
        qc.append(my_gate, q_bits[:3])
        qc.append(my_gate2, q_bits[2:])
        qc.mcx(q_bits[:4], q_bits[4])
        qc.h(0)
        qc.append(circ3, [0, 1])
        self.complex_circuit = qc

    def test_basic(self):
        """Test decompose a single H into u2."""
        qr = QuantumRegister(1, "qr")
        circuit = QuantumCircuit(qr)
        circuit.h(qr[0])
        dag = circuit_to_dag(circuit)
        pass_ = Decompose(HGate)
        after_dag = pass_.run(dag)
        op_nodes = after_dag.op_nodes()
        self.assertEqual(len(op_nodes), 1)
        self.assertEqual(op_nodes[0].name, "u2")

    def test_decompose_none(self):
        """Test decompose a single H into u2."""
        qr = QuantumRegister(1, "qr")
        circuit = QuantumCircuit(qr)
        circuit.h(qr[0])
        dag = circuit_to_dag(circuit)
        pass_ = Decompose()
        after_dag = pass_.run(dag)
        op_nodes = after_dag.op_nodes()
        self.assertEqual(len(op_nodes), 1)
        self.assertEqual(op_nodes[0].name, "u2")

    def test_decompose_only_h(self):
        """Test to decompose a single H, without the rest"""
        qr = QuantumRegister(2, "qr")
        circuit = QuantumCircuit(qr)
        circuit.h(qr[0])
        circuit.cx(qr[0], qr[1])
        dag = circuit_to_dag(circuit)
        pass_ = Decompose(HGate)
        after_dag = pass_.run(dag)
        op_nodes = after_dag.op_nodes()
        self.assertEqual(len(op_nodes), 2)
        for node in op_nodes:
            self.assertIn(node.name, ["cx", "u2"])

    def test_decompose_toffoli(self):
        """Test decompose CCX."""
        qr1 = QuantumRegister(2, "qr1")
        qr2 = QuantumRegister(1, "qr2")
        circuit = QuantumCircuit(qr1, qr2)
        circuit.ccx(qr1[0], qr1[1], qr2[0])
        dag = circuit_to_dag(circuit)
        pass_ = Decompose(CCXGate)
        after_dag = pass_.run(dag)
        op_nodes = after_dag.op_nodes()
        self.assertEqual(len(op_nodes), 15)
        for node in op_nodes:
            self.assertIn(node.name, ["h", "t", "tdg", "cx"])

    def test_decompose_conditional(self):
        """Test decompose a 1-qubit gates with a conditional."""
        qr = QuantumRegister(1, "qr")
        cr = ClassicalRegister(1, "cr")
        circuit = QuantumCircuit(qr, cr)
        with self.assertWarns(DeprecationWarning):
            circuit.h(qr).c_if(cr, 1)
        with self.assertWarns(DeprecationWarning):
            circuit.x(qr).c_if(cr, 1)
        dag = circuit_to_dag(circuit)
        pass_ = Decompose(HGate)
        after_dag = pass_.run(dag)

        ref_circuit = QuantumCircuit(qr, cr)
        with self.assertWarns(DeprecationWarning):
            ref_circuit.append(U2Gate(0, pi), [qr[0]]).c_if(cr, 1)
        with self.assertWarns(DeprecationWarning):
            ref_circuit.x(qr).c_if(cr, 1)
        ref_dag = circuit_to_dag(ref_circuit)

        self.assertEqual(after_dag, ref_dag)

    def test_decompose_oversized_instruction(self):
        """Test decompose on a single-op gate that doesn't use all qubits."""
        # ref: https://github.com/Qiskit/qiskit-terra/issues/3440
        qc1 = QuantumCircuit(2)
        qc1.x(0)
        gate = qc1.to_gate()

        qc2 = QuantumCircuit(2)
        qc2.append(gate, [0, 1])

        output = qc2.decompose()

        self.assertEqual(qc1, output)

    def test_decomposition_preserves_qregs_order(self):
        """Test decomposing a gate preserves the order of registers in its definition"""
        qr = QuantumRegister(2, "qr1")
        qc1 = QuantumCircuit(qr)
        qc1.cx(1, 0)
        gate = qc1.to_gate()

        qr2 = QuantumRegister(2, "qr2")
        qc2 = QuantumCircuit(qr2)
        qc2.append(gate, qr2)

        expected = QuantumCircuit(qr2)
        expected.cx(1, 0)

        self.assertEqual(qc2.decompose(), expected)

    def test_decompose_global_phase_1q(self):
        """Test decomposition of circuit with global phase"""
        qc1 = QuantumCircuit(1)
        qc1.rz(0.1, 0)
        qc1.ry(0.5, 0)
        qc1.global_phase += pi / 4
        qcd = qc1.decompose()
        self.assertEqual(Operator(qc1), Operator(qcd))

    def test_decompose_global_phase_2q(self):
        """Test decomposition of circuit with global phase"""
        qc1 = QuantumCircuit(2, global_phase=pi / 4)
        qc1.rz(0.1, 0)
        qc1.rxx(0.2, 0, 1)
        qcd = qc1.decompose()
        self.assertEqual(Operator(qc1), Operator(qcd))

    def test_decompose_global_phase_1q_composite(self):
        """Test decomposition of circuit with global phase in a composite gate."""
        circ = QuantumCircuit(1, global_phase=pi / 2)
        circ.x(0)
        circ.h(0)
        v = circ.to_gate()

        qc1 = QuantumCircuit(1)
        qc1.append(v, [0])
        qcd = qc1.decompose()
        self.assertEqual(Operator(qc1), Operator(qcd))

    def test_decompose_only_h_gate(self):
        """Test decomposition parameters so that only a certain gate is decomposed."""
        circ = QuantumCircuit(2, 1)
        circ.h(0)
        circ.cz(0, 1)
        decom_circ = circ.decompose(["h"])
        dag = circuit_to_dag(decom_circ)
        self.assertEqual(len(dag.op_nodes()), 2)
        self.assertEqual(dag.op_nodes()[0].name, "u2")
        self.assertEqual(dag.op_nodes()[1].name, "cz")

    def test_decompose_only_given_label(self):
        """Test decomposition parameters so that only a given label is decomposed."""
        decom_circ = self.complex_circuit.decompose(["gate2"])
        dag = circuit_to_dag(decom_circ)

        self.assertEqual(len(dag.op_nodes()), 7)
        self.assertEqual(dag.op_nodes()[0].op.label, "gate1")
        self.assertEqual(dag.op_nodes()[1].name, "h")
        self.assertEqual(dag.op_nodes()[2].name, "cx")
        self.assertEqual(dag.op_nodes()[3].name, "x")
        self.assertEqual(dag.op_nodes()[4].name, "mcx")
        self.assertEqual(dag.op_nodes()[5].name, "h")
        self.assertRegex(dag.op_nodes()[6].name, "circuit-")

    def test_decompose_only_given_name(self):
        """Test decomposition parameters so that only given name is decomposed."""
        decom_circ = self.complex_circuit.decompose(["mcx"], reps=2)
        dag = circuit_to_dag(decom_circ)

        self.assertEqual(len(dag.op_nodes()), 13)
        self.assertEqual(dag.op_nodes()[0].op.label, "gate1")
        self.assertEqual(dag.op_nodes()[1].op.label, "gate2")
        self.assertEqual(dag.op_nodes()[2].name, "h")
        self.assertEqual(dag.op_nodes()[3].name, "cu1")
        self.assertEqual(dag.op_nodes()[4].name, "rcccx")
        self.assertEqual(dag.op_nodes()[5].name, "h")
        self.assertEqual(dag.op_nodes()[6].name, "h")
        self.assertEqual(dag.op_nodes()[7].name, "cu1")
        self.assertEqual(dag.op_nodes()[8].name, "rcccx_dg")
        self.assertEqual(dag.op_nodes()[9].name, "h")
        self.assertEqual(dag.op_nodes()[10].name, "c3sx")
        self.assertEqual(dag.op_nodes()[11].name, "h")
        self.assertRegex(dag.op_nodes()[12].name, "circuit-")

    def test_decompose_mixture_of_names_and_labels(self):
        """Test decomposition parameters so that mixture of names and labels is decomposed"""
        decom_circ = self.complex_circuit.decompose(["mcx", "gate2"], reps=2)
        dag = circuit_to_dag(decom_circ)

        self.assertEqual(len(dag.op_nodes()), 15)
        self.assertEqual(dag.op_nodes()[0].op.label, "gate1")
        self.assertEqual(dag.op_nodes()[1].name, "h")
        self.assertEqual(dag.op_nodes()[2].name, "cx")
        self.assertEqual(dag.op_nodes()[3].name, "x")
        self.assertEqual(dag.op_nodes()[4].name, "h")
        self.assertEqual(dag.op_nodes()[5].name, "cu1")
        self.assertEqual(dag.op_nodes()[6].name, "rcccx")
        self.assertEqual(dag.op_nodes()[7].name, "h")
        self.assertEqual(dag.op_nodes()[8].name, "h")
        self.assertEqual(dag.op_nodes()[9].name, "cu1")
        self.assertEqual(dag.op_nodes()[10].name, "rcccx_dg")
        self.assertEqual(dag.op_nodes()[11].name, "h")
        self.assertEqual(dag.op_nodes()[12].name, "c3sx")
        self.assertEqual(dag.op_nodes()[13].name, "h")
        self.assertRegex(dag.op_nodes()[14].name, "circuit-")

    def test_decompose_name_wildcards(self):
        """Test decomposition parameters so that name wildcards is decomposed"""
        decom_circ = self.complex_circuit.decompose(["circuit-*"])
        dag = circuit_to_dag(decom_circ)

        self.assertEqual(len(dag.op_nodes()), 9)
        self.assertEqual(dag.op_nodes()[0].name, "h")
        self.assertEqual(dag.op_nodes()[1].name, "t")
        self.assertEqual(dag.op_nodes()[2].name, "x")
        self.assertEqual(dag.op_nodes()[3].name, "h")
        self.assertRegex(dag.op_nodes()[4].name, "cx")
        self.assertEqual(dag.op_nodes()[5].name, "x")
        self.assertEqual(dag.op_nodes()[6].name, "mcx")
        self.assertEqual(dag.op_nodes()[7].name, "h")
        self.assertEqual(dag.op_nodes()[8].name, "x")

    def test_decompose_label_wildcards(self):
        """Test decomposition parameters so that label wildcards is decomposed"""
        decom_circ = self.complex_circuit.decompose(["gate*"])
        dag = circuit_to_dag(decom_circ)

        self.assertEqual(len(dag.op_nodes()), 9)
        self.assertEqual(dag.op_nodes()[0].name, "h")
        self.assertEqual(dag.op_nodes()[1].name, "t")
        self.assertEqual(dag.op_nodes()[2].name, "x")
        self.assertEqual(dag.op_nodes()[3].name, "h")
        self.assertEqual(dag.op_nodes()[4].name, "cx")
        self.assertEqual(dag.op_nodes()[5].name, "x")
        self.assertEqual(dag.op_nodes()[6].name, "mcx")
        self.assertEqual(dag.op_nodes()[7].name, "h")
        self.assertRegex(dag.op_nodes()[8].name, "circuit-")

    def test_decompose_empty_gate(self):
        """Test a gate where the definition is an empty circuit is decomposed."""
        empty = QuantumCircuit(1)
        circuit = QuantumCircuit(1)
        circuit.append(empty.to_gate(), [0])

        decomposed = circuit.decompose()
        self.assertEqual(len(decomposed.data), 0)

    def test_decompose_reps(self):
        """Test decompose reps function is decomposed correctly"""
        decom_circ = self.complex_circuit.decompose(reps=2)
        decomposed = self.complex_circuit.decompose().decompose()
        self.assertEqual(decom_circ, decomposed)

    def test_decompose_single_qubit_clbit(self):
        """Test the decomposition of a block with a single qubit and clbit works.

        Regression test of Qiskit/qiskit-terra#8591.
        """
        block = QuantumCircuit(1, 1)
        block.h(0)

        circuit = QuantumCircuit(1, 1)
        circuit.append(block, [0], [0])

        decomposed = circuit.decompose()

        self.assertEqual(decomposed, block)

    def test_decompose_synthesis(self):
        """Test a high-level object with only a synthesis and no definition is correctly decomposed."""
        qc = QuantumCircuit(1)
        qc.h(0)
        cliff = Clifford(qc)

        bigger = QuantumCircuit(1)
        bigger.append(cliff, [0])

        decomposed = bigger.decompose()

        self.assertEqual(qc, decomposed)

    def test_specify_hls_object(self):
        """Test specifying an HLS object by name works."""
        qc = QuantumCircuit(1)
        qc.h(0)
        cliff = Clifford(qc)

        bigger = QuantumCircuit(1)
        bigger.append(cliff, [0])
        bigger.h(0)  # add another gate that should remain unaffected, but has a definition

        decomposed = bigger.decompose(gates_to_decompose=["clifford"])

        expected = QuantumCircuit(1)
        expected.h(0)
        expected.h(0)

        self.assertEqual(expected, decomposed)

    def test_cif(self):
        """Test decomposition with c_if."""
        circuit = QuantumCircuit(1, 1)
        with self.assertWarns(DeprecationWarning):
            circuit.x(0).c_if(0, 0)

        ops = circuit.decompose().count_ops()
        self.assertEqual(ops.get("u3", 0), 1)

    def test_cif_no_definition(self):
        """Test decomposition with c_if when the gate has no definition.

        Regression test of #13493.
        """
        circuit = QuantumCircuit(1, 1)
        with self.assertWarns(DeprecationWarning):
            circuit.u(1, 2, 3, 0).c_if(0, 0)

        ops = circuit.decompose().count_ops()
        self.assertEqual(ops.get("u", 0), 1)

    def test_control_flow_if(self):
        """Test decompose with control flow."""
        qr = QuantumRegister(2)
        cr = ClassicalRegister(1)
        qc = QuantumCircuit(qr, cr)

        qc.p(0.2, 0)
        qc.measure(0, 0)

        with qc.if_test((cr[0], 0)) as else_:
            qc.cry(0.5, 0, 1)
        with else_:
            qc.crz(0.5, 0, 1)

        expect = qc.copy_empty_like()
        expect.u(0, 0, 0.2, 0)
        expect.measure(0, 0)

        with expect.if_test((cr[0], 0)) as else_:
            expect.ry(0.25, 1)
            expect.cx(0, 1)
            expect.ry(-0.25, 1)
            expect.cx(0, 1)
        with else_:
            expect.rz(0.25, 1)
            expect.cx(0, 1)
            expect.rz(-0.25, 1)
            expect.cx(0, 1)

        self.assertEqual(expect, qc.decompose())

    def test_control_flow_for(self):
        """Test decompose with control flow."""
        qr = QuantumRegister(2)
        cr = ClassicalRegister(1)
        qc = QuantumCircuit(qr, cr)

        qc.p(0.2, 0)
        qc.measure(0, 0)

        with qc.for_loop(range(3)):
            qc.cry(0.5, 0, 1)

        expect = qc.copy_empty_like()
        expect.u(0, 0, 0.2, 0)
        expect.measure(0, 0)

        with expect.for_loop(range(3)):
            expect.ry(0.25, 1)
            expect.cx(0, 1)
            expect.ry(-0.25, 1)
            expect.cx(0, 1)

        self.assertEqual(expect, qc.decompose())
