from numbers import Number

import sympy as sp

x, y, z = sp.symbols("x, y, z", real=True, finite=True)
#: Beam waist in x-direction (collimated).
waist_x0 = sp.Symbol("W_{x0}", real=True, positive=True, finite=True, nonzero=True)
#: Beam waist in z-direction at the focus.
waist_z0 = sp.Symbol("W_{z0}", real=True, positive=True, finite=True, nonzero=True)
#: Offset from waist at origin

#: Power per beam. Total power is 2x.
power = sp.Symbol("P", real=True, positive=True, finite=True, nonzero=True)
#: Magnetic field gradients.
grad_r, grad_z = sp.symbols(
    "\\nabla{B_x}, \\nabla{B_z}", real=True, finite=True, nonzero=True
)
#: Real part of polarizability :math:`\alpha`
a = sp.Symbol("a", real=True, finite=True, nonzero=True)
d = sp.Symbol("d", real=True, positive=True, finite=True, nonzero=True)
mu_b = sp.Symbol("mu_b", real=True, positive=True, finite=True, nonzero=True)
#: Wavelength
wvl = sp.Symbol("lambda", real=True, positive=True, finite=True, nonzero=True)
#: Angle of the incident beams
theta = sp.Symbol("theta", real=True, positive=True, finite=True, nonzero=True)


def BeamWaistZ(length: sp.Symbol | Number) -> sp.Expr:  # noqa: N802
    """The beam is focused in z-direction, leading to a waist that
    depends on the distance :math:`l` from the lens.

    Depending on which beam (upper or lower), the distance from the
    lens :math:`l` is given by
    :math:`y \\cos(\\theta) \\mp z \\sin(\\theta)`

    :param length: Distance :math:`l` from the lens.
    """
    return waist_z0 * sp.sqrt(1 + (wvl * length / (sp.pi * waist_z0**2)) ** 2)


def UpperBeamIntensity() -> sp.Expr:  # noqa: N802
    """Intensity distribution of the upper beam"""
    length = y * sp.cos(theta) - z * sp.sin(theta)
    waist_z = BeamWaistZ(length)
    # Intensity of the beam at the origin
    i_0 = 2 * power / (sp.pi * waist_x0 * waist_z0)
    # Turn off automatic formatting to preserve readability
    # fmt: off
    return i_0 * waist_z0 / waist_z * sp.exp(
        - 2 * x**2 / waist_x0**2 
        - 2 * (z * sp.cos(theta) + y * sp.sin(theta)) ** 2 / waist_z**2
    )  # fmt: on


def LowerBeamIntensity() -> sp.Expr:  # noqa: N802
    """Intensity distribution of the lower beam"""
    length = y * sp.cos(theta) + z * sp.sin(theta)
    waist_z = BeamWaistZ(length)
    # Intensity of the beam at the origin
    i_0 = 2 * power / (sp.pi * waist_x0 * waist_z0)
    # Turn off automatic formatting to preserve readability
    # fmt: off
    return i_0 * waist_z0 / waist_z * sp.exp(
        - 2 * x**2 / waist_x0**2 
        - 2 * (z * sp.cos(theta) - y * sp.sin(theta)) ** 2 / waist_z**2
    )  # fmt: on


def TotalIntensity() -> sp.Expr:  # noqa: N802
    i_1 = UpperBeamIntensity()
    i_2 = LowerBeamIntensity()
    return i_1 + i_2 + 2 * sp.sqrt(i_1 * i_2) * sp.cos(2 * sp.pi * z / d)


def TotalPotential() -> sp.Expr:  # noqa: N802
    return -a * TotalIntensity()
