"""
********************************************************************************
* Copyright (c) 2025 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

import numpy as np

from qrisp.alg_primitives.arithmetic import gidney_adder
from qrisp.jasp import qache, jrange
from qrisp.qtypes import QuantumFloat, QuantumBool
from qrisp.environments import control
from qrisp.core import cx, x


@qache(static_argnames="inpl_adder")
def jasp_controlling_multiplyer(a, b, inpl_adder=gidney_adder):

    s = QuantumFloat(a.size + b.size)

    for i in jrange(b.size):
        with control(b[i]):
            inpl_adder(a, s[i : i + a.size + 1])

    return s


@qache(static_argnames="inpl_adder")
def jasp_squaring(a, inpl_adder=gidney_adder):

    s = QuantumFloat(2 * a.size)
    temp = QuantumBool()

    for i in jrange(a.size):

        cx(a[i], temp[0])

        with control(temp[0]):
            inpl_adder(a, s[i : i + a.size + 1])

        cx(a[i], temp[0])

    temp.delete()

    return s


@qache(static_argnames="inpl_adder")
def jasp_multiplyer(factor_1, factor_2, inpl_adder=gidney_adder):

    # Executes the algorithm

    # x*y = (x<<n - y_0*x - sum([x<<i*(-1)**y_i for i in range(1, len(x)+1)))
    # https://arxiv.org/abs/2112.10537

    n = factor_1.size - 1
    s = QuantumFloat(
        factor_1.size + factor_2.size, exponent=factor_1.exponent + factor_2.exponent
    )

    # Handle trivial cases first
    with control(factor_1.size == 1):
        with control(factor_1[0]):
            for j in jrange(factor_2.size):
                cx(factor_2[j], s[j])
    with control((factor_2.size == 1) & (factor_1.size != 1)):
        with control(factor_2[0]):
            for j in jrange(factor_1.size):
                cx(factor_1[j], s[j])

    # Non trivial case
    with control((factor_1.size > 1) & (factor_2.size > 1)):

        for i in jrange(factor_2.size):
            cx(factor_2[i], s[i + n])

        x(s)

        with control(factor_1[0], ctrl_state=0):
            inpl_adder(factor_2, s)

        for j in jrange(s.size):
            cx(factor_1[1], s[j])

        for i in jrange(1, factor_1.size - 1):

            inpl_adder(factor_2[: s.size - i], s[i - 1 :])

            cx(factor_1[i], factor_1[i + 1])
            for j in jrange(s.size):
                cx(factor_1[i + 1], s[j])
            cx(factor_1[i], factor_1[i + 1])

        inpl_adder(factor_2[: s.size - factor_1.size + 1], s[factor_1.size - 2 :])

        for i in jrange(s.size):
            cx(factor_1[factor_1.size - 1], s[i])

        x(s)

    return s
