from math import pi

import numpy as np

import pypulseq as pp

Nx = 256
Ny = 256
Nz = 32

system = pp.Opts(
    max_grad=32,
    grad_unit="mT/m",
    max_slew=130,
    slew_unit="T/m/s",
    grad_raster_time=10e-6,
    rf_ringdown_time=10e-6,
    rf_dead_time=100e-6,
)
seq = pp.Sequence(system)

fov = 256e-3
fov_z = 256e-3
slice_thickness = 1e-3
section_thickness = 5e-3

# =========
# RF preparatory, excitation
# =========
flip_exc = 12 * pi / 180
rf = pp.make_block_pulse(
    flip_angle=flip_exc, system=system, duration=250e-6, time_bw_product=4
)

flip_prep = 90 * pi / 180
rf_prep = pp.make_block_pulse(
    flip_angle=flip_prep, system=system, duration=500e-6, time_bw_product=4
)

# =========
# Readout
# =========
delta_k = 1 / fov
k_width = Nx * delta_k
readout_time = 3.5e-3
gx = pp.make_trapezoid(
    channel="x", system=system, flat_area=k_width, flat_time=readout_time
)
adc = pp.make_adc(num_samples=Nx, duration=gx.flat_time, delay=gx.rise_time)

# =========
# Prephase and Rephase
# =========
delta_kz = 1 / fov_z
phase_areas = (np.arange(Ny) - (Ny / 2)) * delta_k
slice_areas = (np.arange(Nz) - (Nz / 2)) * delta_kz

gx_pre = pp.make_trapezoid(channel="x", system=system, area=-gx.area / 2, duration=2e-3)
gy_pre = pp.make_trapezoid(
    channel="y", system=system, area=phase_areas[-1], duration=2e-3
)

# =========
# Spoilers
# =========
pre_time = 6.4e-4
gx_spoil = pp.make_trapezoid(
    channel="x",
    system=system,
    area=(4 * np.pi) / (42.576e6 * delta_k * 1e-3) * 42.576e6,
    duration=pre_time * 6,
)
gy_spoil = pp.make_trapezoid(
    channel="y",
    system=system,
    area=(4 * np.pi) / (42.576e6 * delta_k * 1e-3) * 42.576e6,
    duration=pre_time * 6,
)
gz_spoil = pp.make_trapezoid(
    channel="z",
    system=system,
    area=(4 * np.pi) / (42.576e6 * delta_kz * 1e-3) * 42.576e6,
    duration=pre_time * 6,
)

# =========
# Extended trapezoids: gx, gx_spoil
# =========
t_gx_extended = np.array(
    [0, gx.rise_time, gx.flat_time, (gx.rise_time * 2) + gx.flat_time + gx.fall_time]
)
amp_gx_extended = np.array([0, gx.amplitude, gx.amplitude, gx_spoil.amplitude])
t_gx_spoil_extended = np.array(
    [
        0,
        gx_spoil.rise_time + gx_spoil.flat_time,
        gx_spoil.rise_time + gx_spoil.flat_time + gx_spoil.fall_time,
    ]
)
amp_gx_spoil_extended = np.array([gx_spoil.amplitude, gx_spoil.amplitude, 0])

gx_extended = pp.make_extended_trapezoid(
    channel="x", times=t_gx_extended, amplitudes=amp_gx_extended
)
gx_spoil_extended = pp.make_extended_trapezoid(
    channel="x", times=t_gx_spoil_extended, amplitudes=amp_gx_spoil_extended
)

# =========
# Delays
# =========
TE, TI, TR, T_recovery = 4e-3, 140e-3, 10e-3, 1e-3
delay_TE = (
    TE - pp.calc_duration(rf) / 2 - pp.calc_duration(gx_pre) - pp.calc_duration(gx) / 2
)
delay_TI = TI - pp.calc_duration(rf_prep) / 2 - pp.calc_duration(gx_spoil)
delay_TR = (
    TR
    - pp.calc_duration(rf)
    - pp.calc_duration(gx_pre)
    - pp.calc_duration(gx)
    - pp.calc_duration(gx_spoil)
)

for i in range(Ny):
    gy_pre = pp.make_trapezoid(
        channel="y", system=system, area=phase_areas[i], duration=2e-3
    )

    seq.add_block(rf_prep)
    seq.add_block(gx_spoil, gy_spoil, gz_spoil)
    seq.add_block(pp.make_delay(delay_TI))

    for j in range(Nz):
        gz_pre = pp.make_trapezoid(
            channel="z", system=system, area=slice_areas[j], duration=2e-3
        )
        gz_reph = pp.make_trapezoid(
            channel="z", system=system, area=-slice_areas[j], duration=2e-3
        )

        seq.add_block(rf)
        seq.add_block(gx_pre, gy_pre, gz_pre)
        # Skip TE: readout_time = 3.5e3 --> TE = -2.168404344971009e-19
        # seq.add_block(pp.make_delay(delay_TE))
        seq.add_block(gx_extended, adc)
        seq.add_block(gx_spoil_extended, gz_reph)
        seq.add_block(pp.make_delay(delay_TR))

    seq.add_block(pp.make_delay(T_recovery))

seq.set_definition(key="Name", value="3D T1 MPRAGE")

seq.write("256_3d_t1_mprage_pypulseq.seq")
# seq.plot(time_range=(0, TI + TR + 2e-3))
