# -*- coding: utf-8 -*-

# Copyright 2018 Carsten Blank
#
# 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.
r"""
CCXToffoli
============

.. currentmodule:: dc_qiskit_qml.distance_based.hadamard.state.cnot._CCXToffoli

This module implements a :py:class:`CCXFactory` to create a multi-controlled X-gate (or NOT gate) to a circuit
using the Toffoli gate cascade on ancillary qubits described by Nielsen & Chuang (https://doi.org/10.1017/CBO9780511976667).
Its advantage is that it doesn't need expnential gates w.r.t. to the controlled qubit count, however, its downside is
that it needs auxiliary qubits. It depends strongly on the use case whether to use this or the algorithm by
Möttönen et al. as implemented by :py:class:`CCXMöttönen`.

.. autosummary::
    :nosignatures:

    CCXToffoli

CCXToffoli
#############

.. autoclass:: CCXToffoli
    :members:

"""

import logging
from typing import List, Union, Tuple

from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.register import Register

from . import CCXFactory

log = logging.getLogger(__name__)


class CCXToffoli(CCXFactory):
    """
    cc-X gate implemented via the Toffoli cascade with auxiliary qubits.
    """
    def ccx(self, qc, conditial_case, control_qubits, tgt):
        # type:(CCXToffoli, QuantumCircuit, int, List[Tuple[Register, int]], Union[Tuple[Register, int], QuantumRegister]) -> QuantumCircuit
        """
        Using the Toffoli gate cascade on auxiliary qubits, one can create multi-controlled NOT gate. The routine
        needs to add an auxiliary register called ``
        ccx_ancilla`` which also will be re-used if multiple calls
        of this routine are done within the same circuit. As the cascade is always 'undone', the ancilla register
        is left to the ground state.

        :param qc: the circuit to apply this operation to
        :param conditial_case: an integer whose binary representation signifies the branch to controll on
        :param control_qubits: the qubits that hold the desired conditional case
        :param tgt: the target to be applied the controlled X gate on
        :return: the circuit after application of the gate
        """

        # Prepare conditional case
        bit_string = "{:b}".format(conditial_case).zfill(len(control_qubits))
        for i, b in enumerate(reversed(bit_string)):
            if b == '0':
                qc.x(control_qubits[i])
        qc.barrier()

        if len(control_qubits) == 1: # This is just the normal CNOT
            qc.cx(control_qubits[0], tgt)
        elif len(control_qubits) == 2: # This is the simple Toffoli
            qc.ccx(control_qubits[0], control_qubits[1], tgt)
        else:
            # Create ancilla qubit or take the one that is already there
            if 'ccx_ancilla' not in [q.name for q in qc.qregs]:
                ccx_ancilla = QuantumRegister(len(control_qubits) - 1, 'ccx_ancilla')  # type: QuantumRegister
                qc.add_register(ccx_ancilla)
            else:
                ccx_ancilla = [q for q in qc.qregs if q.name == 'ccx_ancilla'][0]  # type: QuantumRegister

            # Algorithm
            qc.ccx(control_qubits[0], control_qubits[1], ccx_ancilla[0])
            for i in range(1, ccx_ancilla.size):
                qc.ccx(control_qubits[i], ccx_ancilla[i - 1], ccx_ancilla[i])

            qc.ccx(control_qubits[-1], ccx_ancilla[ccx_ancilla.size - 1], tgt)

            for i in reversed(range(1, ccx_ancilla.size)):
                qc.ccx(control_qubits[i], ccx_ancilla[i - 1], ccx_ancilla[i])
            qc.ccx(control_qubits[0], control_qubits[1], ccx_ancilla[0])

        qc.barrier()
        # Undo the conditional case
        for i, b in enumerate(reversed(bit_string)):
            if b == '0':
                qc.x(control_qubits[i])

        return qc
