#!/usr/bin/env python3
import mpmath as mp


# APPLIED OPTICS / Vol. 53, No. 31 / 1 November 2014, eq(13)
def LeRu_cutoff(z):
    x = mp.fabs(z)
    return int(x + 11 * x**(1/3) + 1)


# Wu, Wang, Radio Science, Volume 26, Number 6, Pages 1393-1401, November-December 1991,
# after eq 13f.
# Riccati-Bessel z*j_n(z)
def psi(n, z):
    return mp.sqrt((mp.pi * z)/2) * mp.autoprec(mp.besselj)(n+1/2, z)
# Riccati-Bessel -z*y_n(z)


def xi(n, z):
    return -mp.sqrt((mp.pi * z)/2) * mp.autoprec(mp.bessely)(n+1/2, z)
# Riccati-Bessel psi - i* xi


def ksi(n, z):
    return psi(n, z) - 1.j * xi(n, z)


def psi_div_ksi(n, z):
    return psi(n, z)/ksi(n, z)


def psi_mul_ksi(n, z):
    return psi(n, z)*ksi(n, z)


def psi_div_xi(n, z):
    return psi(n, z)/xi(n, z)

# to compare r(n,z) with Wolfram Alpha
# n=49, z=1.3-2.1i,  SphericalBesselJ[n-1,z]/SphericalBesselJ[n,z]


def r(n, z):
    if n > 0:
        return psi(n-1, z)/psi(n, z)
    return mp.cos(z)/mp.sin(z)


def D(n, z, f):
    return f(n-1, z)/f(n, z) - n/z


def D1(n, z):
    if n == 0:
        return mp.cos(z)/mp.sin(z)
    return D(n, z, psi)

# Wolfram Alpha example D2(10, 10-10j): SphericalBesselY[9, 10-10i]/SphericalBesselY[10,10-10i]-10/(10-10i)


def D2(n, z):
    if n == 0:
        return -mp.sin(z)/mp.cos(z)
    return D(n, z, xi)


def D3(n, z):
    if n == 0:
        return 1j
    return D(n, z, ksi)


# bulk sphere

# Le Ru
def an_lr(n, x, m):
    print(f'D1(x) = {D1(n, x)}\n\
            D1(mx) = {D1(n, m*x)}\n\
            D3(x) = {D3(n, x)}\n\
            psi_n = {psi(n,x)}\n\
            ksi_n = {ksi(n,x)}\n\
    ')

    return (
        (
            psi(n, x)*(m*D1(n, x)-D1(n, m*x))
        ) /
        (
            ksi(n, x)*(m * D3(n, x)-D1(n, m*x))
        )
    )

# Ovidio


def an(n, x, m):
    # print(f'D1 = {D1(n, m*x)}\n\
    # psi_n = {psi(n,x)}\n\
    # psi_nm1 = {psi(n-1,x)}\n\
    # ksi_n = {ksi(n,x)}\n\
    # ksi_nm1 = {ksi(n-1,x)}\n\
    # ')
    return (
        ((D1(n, m*x)/m + n/x)*psi(n, x) - psi(n-1, x)) /
        ((D1(n, m*x)/m + n/x)*ksi(n, x) - ksi(n-1, x))
    )


def bn(n, x, m):
    # print(f'D1 = {D1(n, m*x)}\n\
    # psi_n = {psi(n,x)}\n\
    # psi_nm1 = {psi(n-1,x)}\n\
    # ksi_n = {ksi(n,x)}\n\
    # ksi_nm1 = {ksi(n-1,x)}\n\
    # ')
    return (
        ((D1(n, m*x)*m + n/x) * psi(n, x) - psi(n-1, x)) /
        ((D1(n, m*x)*m + n/x) * ksi(n, x) - ksi(n-1, x))
    )

# # Du
# def an(n, x, m):
#     mx = m*x
#     return (
#         ((r(n,mx)/m + n*(1-1/m**2)/x)*psi(n,x)-psi(n-1,x))/
#         ((r(n,mx)/m + n*(1-1/m**2)/x)*ksi(n,x)-ksi(n-1,x))
#     )
# def bn(n,x,m):
#     mx = m*x
#     return (
#         (r(n,mx)*m*psi(n,x)-psi(n-1,x))/
#         (r(n,mx)*m*ksi(n,x)-ksi(n-1,x))
#     )


def Qext_diff(n, x, m):
    return ((2*n + 1)*2/x**2) * (an(n, x, m) + bn(n, x, m)).real


def Qsca_diff(n, x, m):
    return ((2*n + 1)*2/x**2) * (abs(an(n, x, m))**2 + abs(bn(n, x, m))**2)


def Qany(x, m, nmax, output_dps, Qdiff):
    Qany = 0
    Qlist = []
    Qprev = ""
    convergence_max_length = 35
    i = 0
    for n in range(1, nmax+1):
        diff = Qdiff(n, x, m)
        Qany += diff
        Qnext = mp.nstr(Qany, output_dps)
        Qlist.append(Qany)
        i += 1
        if Qprev != Qnext:
            i = 0
        Qprev = Qnext
        print(n, ' ', end='', flush=True)
        if i >= convergence_max_length:
            break
    print('')
    Qlist.append(Qany)
    return Qlist


def Qsca(x, m, nmax, output_dps):
    return Qany(x, m, nmax, output_dps, Qsca_diff)


def Qext(x, m, nmax, output_dps):
    return Qany(x, m, nmax, output_dps, Qext_diff)
