@doc raw"""
    gausslegendre([T=Float64,] n::Integer) -> x, w  # nodes, weights

Return nodes `x` and weights `w` of [Gauss-Legendre quadrature](https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_quadrature) with type `T`.

```math
\int_{-1}^{1} f(x) dx \approx \sum_{i=1}^{n} w_i f(x_i)
```

# Examples
```jldoctest
julia> x, w = gausslegendre(3);

julia> f(x) = x^4;

julia> I = dot(w, f.(x));

julia> I ≈ 2/5
true
```
"""
@inline function gausslegendre(::Type{T}, n::Integer) where {T}
    # GAUSSLEGENDRE(n) COMPUTE THE GAUSS-LEGENDRE NODES AND WEIGHTS IN O(n) time.

    if n < 0
        throw(DomainError(n, "Input n must be a non-negative integer"))
    elseif n == 0
        return T[], T[]
    elseif n == 1
        return T[0], T[2]
    elseif n == 2
        sqrt3 = sqrt(T(3))
        return [T(-1) / sqrt3, 1 / sqrt3], T[1, 1]
    elseif n == 3
        sqrt3_5 = sqrt(T(3) / 5)
        return [-sqrt3_5, T(0), sqrt3_5], [5, 8, 5] ./ T(9)
    elseif n == 4
        a = 2 * sqrt(T(6) / 5) / 7
        T3_7 = T(3) / 7
        sqrt30 = sqrt(T(30))
        return (
            [-sqrt(T3_7 + a), -sqrt(T3_7 - a), sqrt(T3_7 - a), sqrt(T3_7 + a)],
            [
                (18 - sqrt30) / 36, (18 + sqrt30) / 36,
                (18 + sqrt30) / 36, (18 - sqrt30) / 36,
            ],
        )
    elseif n == 5
        b = 2 * sqrt(T(10) / 7)
        sqrt70 = sqrt(T(70))
        return (
            [
                -sqrt(5 + b) / 3, -sqrt(5 - b) / 3, T(0),
                sqrt(5 - b) / 3, sqrt(5 + b) / 3,
            ],
            [
                (322 - 13 * sqrt70) / 900, (322 + 13 * sqrt70) / 900, T(128) / 225,
                (322 + 13 * sqrt70) / 900, (322 - 13 * sqrt70) / 900,
            ],
        )
    elseif n ≤ 60 || T != Float64
        # NEWTON'S METHOD WITH THREE-TERM RECURRENCE:
        return rec(T, n)
    else
        # USE ASYMPTOTIC EXPANSIONS:
        return asy(n)
    end
end
gausslegendre(n::Integer) = gausslegendre(Float64, n)

function asy(n)
    # COMPUTE GAUSS-LEGENDRE NODES AND WEIGHTS USING ASYMPTOTIC EXPANSIONS.
    # COMPLEXITY O(n).

    # Nodes and weights:
    m = (n + 1) >> 1
    a = besselZeroRoots(m)
    rmul!(a, 1 / (n + 0.5))
    x = legpts_nodes(n, a)
    w = legpts_weights(n, m, a)
    # Use symmetry to get the others:
    resize!(x, n)
    resize!(w, n)
    @inbounds for i in 1:m
        x[n + 1 - i] = x[i]
        w[n + 1 - i] = w[i]
    end
    @inbounds for i in 1:m
        x[i] = -x[i]
    end
    @inbounds isodd(n) && (x[m] = 0.0)

    return x, w
end

function legpts_nodes(n, a)
    # ASYMPTOTIC EXPANSION FOR THE GAUSS-LEGENDRE NODES.
    vn = 1 / (n + 0.5)
    m = length(a)
    nodes = cot.(a)
    vn² = vn * vn
    vn⁴ = vn² * vn²
    @inbounds if n ≤ 255
        vn⁶ = vn⁴ * vn²
        for i in 1:m
            u = nodes[i]
            u² = u^2
            ai = a[i]
            ai² = ai * ai
            ai³ = ai² * ai
            ai⁵ = ai² * ai³
            node = ai + (u - 1 / ai) / 8 * vn²
            v1 = (6 * (1 + u²) / ai + 25 / ai³ - u * muladd(31, u², 33)) / 384
            v2 = u * evalpoly(u², (2595 / 15360, 6350 / 15360, 3779 / 15360))
            v3 = (1 + u²) * (
                -muladd(31 / 1024, u², 11 / 1024) / ai +
                    u / 512 / ai² + -25 / 3072 / ai³
            )
            v4 = (v2 - 1073 / 5120 / ai⁵ + v3)
            node = muladd(v1, vn⁴, node)
            node = muladd(v4, vn⁶, node)
            nodes[i] = node
        end
    elseif n ≤ 3950
        for i in 1:m
            u = nodes[i]
            u² = u^2
            ai = a[i]
            ai² = ai * ai
            ai³ = ai² * ai
            node = ai + (u - 1 / ai) / 8 * vn²
            v1 = (6 * (1 + u²) / ai + 25 / ai³ - u * muladd(31, u², 33)) / 384
            node = muladd(v1, vn⁴, node)
            nodes[i] = node
        end
    else
        for i in 1:m
            u = nodes[i]
            ai = a[i]
            node = ai + (u - 1 / ai) / 8 * vn²
            nodes[i] = node
        end
    end
    @inbounds for jj in 1:m
        nodes[jj] = cos(nodes[jj])
    end

    return nodes
end

function legpts_weights(n, m, a)
    # ASYMPTOTIC EXPANSION FOR THE GAUSS-LEGENDRE WEIGHTS.
    vn = 1 / (n + 0.5)
    vn² = vn^2
    weights = Array{Float64}(undef, m)
    if n ≤ 850000
        @inbounds for i in 1:m
            weights[i] = cot(a[i])
        end
    end
    # Split out the part that can be vectorized by llvm
    @inbounds if n ≤ 170
        for i in 1:m
            u = weights[i]
            u² = u^2
            ai = a[i]
            ai⁻¹ = 1 / ai
            ai² = ai^2
            ai⁻² = 1 / ai²
            ua = u * ai
            W1 = muladd(ua - 1, ai⁻², 1.0) / 8
            W2 = evalpoly(
                ai⁻², (
                    evalpoly(u², (-27.0, -84.0, -56.0)),
                    muladd(-3.0, muladd(u², -2.0, 1.0), 6 * ua),
                    muladd(ua, -31.0, 81.0),
                )
            ) / 384
            W3 = evalpoly(
                ai⁻¹, (
                    evalpoly(u², (153 / 1024, 295 / 256, 187 / 96, 151 / 160)),
                    evalpoly(u², (-65 / 1024, -119 / 768, -35 / 384)) * u,
                    evalpoly(u², (5 / 512, 15 / 512, 7 / 384)),
                    muladd(u², 1 / 512, -13 / 1536) * u,
                    muladd(u², -7 / 384, + 53 / 3072),
                    3749 / 15360 * u, -1125 / 1024,
                )
            )
            weights[i] = evalpoly(vn², (1 / vn² + W1, W2, W3))
        end
    elseif n ≤ 1500
        for i in 1:m
            u = weights[i]
            u² = u^2
            ai = a[i]
            ai² = ai^2
            ai⁻² = 1 / ai²
            ua = u * ai
            W1 = muladd(ua - 1, ai⁻², 1.0) / 8
            W2 = evalpoly(
                ai⁻², (
                    evalpoly(u², (-27.0, -84.0, -56.0)),
                    muladd(-3.0, muladd(u², -2.0, 1.0), 6 * ua),
                    muladd(ua, -31.0, 81.0),
                )
            ) / 384
            weights[i] = muladd(vn², W2, 1 / vn² + W1)
        end
    elseif n ≤ 850000
        for i in 1:m
            u = weights[i]
            u² = u^2
            ai = a[i]
            ai² = ai^2
            ai⁻² = 1 / ai²
            ua = u * ai
            W1 = muladd(ua - 1, ai⁻², 1.0) / 8
            weights[i] = 1 / vn² + W1
        end
    else
        for i in 1:m
            weights[i] = 1 / vn²
        end
    end
    bJ1 = besselJ1(m)
    @inbounds for i in 1:m
        weights[i] = 2 / (bJ1[i] * (a[i] / sin(a[i])) * weights[i])
    end

    return weights
end

function rec(::Type{T}, n) where {T}
    # COMPUTE GAUSS-LEGENDRE NODES AND WEIGHTS USING NEWTON'S METHOD.
    # THREE-TERM RECURENCE IS USED FOR EVALUATION. COMPLEXITY O(n^2).

    # Initial guess:
    x = T.(leg_initial_guess(n))

    # Perform Newton to find zeros of Legendre polynomial:
    PP1, PP2 = similar(x), similar(x)

    # Two iterations of Newton's method
    num_iters = if T == Float64
        2
    else
        ceil(Int, (log(max(20, precision(T)) / 50) + 1) * 2)
    end
    for _ in 1:num_iters
        innerRec!(PP1, PP2, n, x)
        newt_step!(x, PP1, PP2)
    end

    # Use symmetry to get the other Legendre nodes and weights:
    m = length(x)
    resize!(x, n)
    resize!(PP2, n)
    @inbounds for i in 1:(m - 1)
        x[n + 1 - i] = -x[i]
        PP2[n + 1 - i] = -PP2[i]
    end
    @inbounds for i in 1:n
        PP2[i] = T(2) / ((1 - x[i]^2) * PP2[i]^2)
    end

    return x, PP2
end

@inline function innerRec!(myPm1, myPPm1, n, x)
    # EVALUATE LEGENDRE AND ITS DERIVATIVE USING THREE-TERM RECURRENCE RELATION.
    N = length(x)
    T = eltype(x)
    @inbounds for j in 1:N
        xj = x[j]
        Pm2 = T(1)
        Pm1 = xj
        PPm1 = T(1)
        PPm2 = T(0)
        for k in 1:(n - 1)
            Pm2, Pm1 = Pm1, muladd((2 * k + 1) * Pm1, xj, - k * Pm2) / (k + 1)
            PPm2, PPm1 = PPm1, (
                    (2 * k + 1) * muladd(xj, PPm1, Pm2) -
                    k * PPm2
                ) / (k + 1)
        end
        myPm1[j] = Pm1
        myPPm1[j] = PPm1
    end
    return myPm1, myPPm1
end

@inline function newt_step!(x, PP1, PP2)
    # In-place iteration of Newton's method.
    return @inbounds @simd for i in eachindex(x)
        x[i] -= PP1[i] / PP2[i]
    end
end

@inline function leg_initial_guess(n)
    # Returns an approximation of the first n÷2+1 roots of the Legendre polynomial.
    #  The following is equivalent to "x0 = asy(n)[1]; x = x0[1:n ÷ 2 + 1]" but it avoids unnecessary calculations.

    m = (n ÷ 2) + 1
    a = besselZeroRoots(m)
    rmul!(a, 1 / (n + 0.5))
    x = legpts_nodes(n, a)
    rmul!(x, -1.0)
    return x
end
