# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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.
import math
import re
from copy import deepcopy

from oqpy import Program

from braket.circuits.free_parameter import FreeParameter
from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform
from braket.pulse.ast.qasm_parser import ast_to_qasm


def test_arbitrary_waveform():
    amps = [complex(1, 2), complex(0.3, -1), 0, 4.2]
    id = "arb_wf_x"
    wf = ArbitraryWaveform(amps, id)
    assert wf.amplitudes == amps
    assert wf.id == id
    oq_exp = wf._to_oqpy_expression()
    assert oq_exp.init_expression == amps
    assert oq_exp.name == wf.id


def test_arbitrary_waveform_default_params():
    amps = [1, 4, 5]
    wf = ArbitraryWaveform(amps)
    assert wf.amplitudes == amps
    assert re.match(r"[A-Za-z]{10}", wf.id)


def test_arbitrary_wf_eq():
    wf = ArbitraryWaveform([1, 4, 5], "wf_x")
    wf_2 = ArbitraryWaveform(wf.amplitudes, wf.id)
    assert wf_2 == wf
    for att in ["amplitudes", "id"]:
        wfc = deepcopy(wf_2)
        setattr(wfc, att, "wrong_value")
        assert wf != wfc


def test_constant_waveform():
    length = 4e-3
    iq = 4
    id = "const_wf_x"
    wf = ConstantWaveform(length, iq, id)
    assert wf.length == length
    assert wf.iq == iq
    assert wf.id == id

    _assert_wf_qasm(wf, "waveform const_wf_x = constant(4000000.0ns, 4);")


def test_constant_waveform_default_params():
    amps = [1, 4, 5]
    wf = ArbitraryWaveform(amps)
    assert wf.amplitudes == amps
    assert re.match(r"[A-Za-z]{10}", wf.id)


def test_constant_wf_eq():
    wf = ConstantWaveform(4e-3, complex(2, 3), "wf_c")
    wf_2 = ConstantWaveform(wf.length, wf.iq, wf.id)
    assert wf_2 == wf
    for att in ["length", "iq", "id"]:
        wfc = deepcopy(wf_2)
        setattr(wfc, att, "wrong_value")
        assert wf != wfc


def test_constant_wf_free_params():
    wf = ConstantWaveform(
        FreeParameter("length_v") + FreeParameter("length_w"), iq=complex(2, -3), id="const_wf"
    )
    assert wf.parameters == [FreeParameter("length_v") + FreeParameter("length_w")]
    _assert_wf_qasm(
        wf,
        "waveform const_wf = "
        "constant((1000000000.0*length_v + 1000000000.0*length_w)ns, 2.0 - 3.0im);",
    )

    wf_2 = wf.bind_values(length_v=2e-6, length_w=4e-6)
    assert len(wf_2.parameters) == 1
    assert math.isclose(wf_2.parameters[0], 6e-6)
    _assert_wf_qasm(wf_2, "waveform const_wf = constant(6000.0ns, 2.0 - 3.0im);")


def test_drag_gaussian_waveform():
    length = 4e-9
    sigma = 0.3
    beta = 0.6
    amplitude = 0.4
    zero_at_edges = False
    id = "drag_gauss_wf"
    wf = DragGaussianWaveform(length, sigma, beta, amplitude, zero_at_edges, id)
    assert wf.id == id
    assert wf.zero_at_edges == zero_at_edges
    assert wf.amplitude == amplitude
    assert wf.beta == beta
    assert wf.sigma == sigma
    assert wf.length == length

    _assert_wf_qasm(
        wf, "waveform drag_gauss_wf = drag_gaussian(4.0ns, 300000000.0ns, 0.6, 0.4, false);"
    )


def test_drag_gaussian_waveform_default_params():
    length = 4e-9
    sigma = 0.3
    beta = 0.6
    wf = DragGaussianWaveform(length, sigma, beta)
    assert re.match(r"[A-Za-z]{10}", wf.id)
    assert wf.zero_at_edges is False
    assert wf.amplitude == 1
    assert wf.beta == beta
    assert wf.sigma == sigma
    assert wf.length == length


def test_drag_gaussian_wf_eq():
    wf = DragGaussianWaveform(4e-3, 0.3, 0.2, 0.7, True, "wf_dg")
    wf_2 = DragGaussianWaveform(wf.length, wf.sigma, wf.beta, wf.amplitude, wf.zero_at_edges, wf.id)
    assert wf_2 == wf
    for att in ["length", "sigma", "beta", "amplitude", "zero_at_edges", "id"]:
        wfc = deepcopy(wf_2)
        setattr(wfc, att, "wrong_value")
        assert wf != wfc


def test_gaussian_waveform():
    length = 4e-9
    sigma = 0.3
    amplitude = 0.4
    zero_at_edges = False
    id = "gauss_wf"
    wf = GaussianWaveform(length, sigma, amplitude, zero_at_edges, id)
    assert wf.id == id
    assert wf.zero_at_edges == zero_at_edges
    assert wf.amplitude == amplitude
    assert wf.sigma == sigma
    assert wf.length == length

    _assert_wf_qasm(wf, "waveform gauss_wf = gaussian(4.0ns, 300000000.0ns, 0.4, false);")


def test_drag_gaussian_wf_free_params():
    wf = DragGaussianWaveform(
        FreeParameter("length_v"),
        FreeParameter("sigma_a") + FreeParameter("sigma_b"),
        FreeParameter("beta_y"),
        FreeParameter("amp_z"),
        id="d_gauss_wf",
    )
    assert wf.parameters == [
        FreeParameter("length_v"),
        FreeParameter("sigma_a") + FreeParameter("sigma_b"),
        FreeParameter("beta_y"),
        FreeParameter("amp_z"),
    ]
    _assert_wf_qasm(
        wf,
        "waveform d_gauss_wf = "
        "drag_gaussian((1000000000.0*length_v)ns, (1000000000.0*sigma_a + "
        "1000000000.0*sigma_b)ns, beta_y, amp_z, false);",
    )

    wf_2 = wf.bind_values(length_v=0.6, sigma_a=0.4)
    assert wf_2.parameters == [
        0.6,
        0.4 + FreeParameter("sigma_b"),
        FreeParameter("beta_y"),
        FreeParameter("amp_z"),
    ]
    _assert_wf_qasm(
        wf_2,
        "waveform d_gauss_wf = drag_gaussian(600000000.0ns, (1000000000.0*sigma_b "
        "+ 400000000.0)ns, beta_y, amp_z, false);",
    )

    wf_3 = wf.bind_values(length_v=0.6, sigma_a=0.3, sigma_b=0.1, beta_y=0.2, amp_z=0.1)
    assert wf_3.parameters == [0.6, 0.4, 0.2, 0.1]
    _assert_wf_qasm(
        wf_3, "waveform d_gauss_wf = drag_gaussian(600000000.0ns, 400000000.0ns, 0.2, 0.1, false);"
    )


def test_gaussian_waveform_default_params():
    length = 4e-9
    sigma = 0.3
    wf = GaussianWaveform(length, sigma)
    assert re.match(r"[A-Za-z]{10}", wf.id)
    assert wf.zero_at_edges is False
    assert wf.amplitude == 1
    assert wf.sigma == sigma
    assert wf.length == length


def test_gaussian_wf_eq():
    wf = GaussianWaveform(4e-3, 0.3, 0.7, True, "wf_dg")
    wf_2 = GaussianWaveform(wf.length, wf.sigma, wf.amplitude, wf.zero_at_edges, wf.id)
    assert wf_2 == wf
    for att in ["length", "sigma", "amplitude", "zero_at_edges", "id"]:
        wfc = deepcopy(wf_2)
        setattr(wfc, att, "wrong_value")
        assert wf != wfc


def test_gaussian_wf_free_params():
    wf = GaussianWaveform(
        FreeParameter("length_v"), FreeParameter("sigma_x"), FreeParameter("amp_z"), id="gauss_wf"
    )
    assert wf.parameters == [
        FreeParameter("length_v"),
        FreeParameter("sigma_x"),
        FreeParameter("amp_z"),
    ]
    _assert_wf_qasm(
        wf,
        "waveform gauss_wf = gaussian((1000000000.0*length_v)ns, (1000000000.0*sigma_x)ns, "
        "amp_z, false);",
    )

    wf_2 = wf.bind_values(length_v=0.6, sigma_x=0.4)
    assert wf_2.parameters == [0.6, 0.4, FreeParameter("amp_z")]
    _assert_wf_qasm(
        wf_2, "waveform gauss_wf = gaussian(600000000.0ns, 400000000.0ns, amp_z, false);"
    )

    wf_3 = wf.bind_values(length_v=0.6, sigma_x=0.3, amp_z=0.1)
    assert wf_3.parameters == [0.6, 0.3, 0.1]
    _assert_wf_qasm(wf_3, "waveform gauss_wf = gaussian(600000000.0ns, 300000000.0ns, 0.1, false);")


def _assert_wf_qasm(waveform, expected_qasm):
    p = Program(None)
    p.declare(waveform._to_oqpy_expression())
    assert ast_to_qasm(p.to_ast(include_externs=False)) == expected_qasm
