using Symbolics: Sym, FnType, Term, value, scalarize
using Symbolics
using LinearAlgebra
using SparseArrays: sparse, sparsevec
using StaticArraysCore: SArray
import SpecialFunctions
using Test
using SymbolicUtils: BasicSymbolic, evaluate

a, b, c = :runtime_symbol_value, :value_b, :value_c
vars = @variables t $a $b(t) $c(t)[1:3]
@test t isa Num
@test a === :runtime_symbol_value
@test b === :value_b
@test c === :value_c
@test isequal(vars[1], t)
@test isequal(vars[2], Num(Sym{SymReal}(a; type = Real)))
@test isequal(vars[3], Num(Sym{SymReal}(b; type = FnType{Tuple,Real,Nothing})(value(t))))

vars = @variables a,b,c,d,e,f,g,h,i
@test isequal(vars, [a,b,c,d,e,f,g,h,i])
@test isequal(transpose(a), a)
@test isequal(a', a)
@test isequal(sincos(a), (sin(a), cos(a)))

# https://github.com/JuliaSymbolics/Symbolics.jl/issues/1763
@test isequal(sincospi(a), (sinpi(a), cospi(a)))
@test isinteger(Num(2))
@test !isinteger(Num(2.5))
@test !isinteger(a)
@variables nint::Int
@test isinteger(nint)
@variables zcplx::Complex
# `sinpi(::Complex{Num})` / `cospi(::Complex{Num})` go through `Base`'s
# complex implementations, which dispatch on `isinteger(::Num)`.
@test sinpi(zcplx) isa Complex{Num}
@test cospi(zcplx) isa Complex{Num}
@test sincospi(zcplx) isa Tuple{Complex{Num}, Complex{Num}}

@test substitute(a ~ b, Dict(a=>1, b=>c)) == (1 ~ c)

# test hashing
aa = a; # old a

@variables a

@test isequal(a, aa)
@test hash(a) == hash(aa)

@test isequal(Symbolics.get_variables(a+aa+1), Set([a]))

@test hash(a+b ~ c+d) == hash(a+b ~ c+d)

# test some matrix operations don't throw errors
X = [0 b c;
     d e f;
     g h i]

@test iszero(expand(det(X) - (-b * (d*i-f*g) + c * (d*h - e*g))))


F = lu(X)
@test_nowarn lu(X'), lu(transpose(X))
@test F.p == [2, 1, 3]
R = simplify_fractions.(F.L * F.U - X[F.p, :])
@test iszero(R)
@test simplify_fractions.(F \ X) == I
@test Symbolics._solve(X, X, true) == I
inv(X)
qr(X)

X2 = [0 b c; 0 0 0; 0 h 0]
@test_throws SingularException lu(X2)
F2 = lu(X2, check=false)
@test F2.info == 1

# test operations with sparse arrays
# note `isequal` instead of `==` because `==` would give another symbolic type

# test that we can create a symbolic sparse array
Oarray = zeros(Num, 2,2)
Oarray[2,2] = a
@test isequal(sparse(Oarray), sparse([2], [2], [a]))

@test isequal(a * sparse([2], [2], [1]), sparse([2], [2], [a * 1]))

A = sparse([2], [2], [a])
B = sparse([2], [2], [b])
@test isequal(A + B, sparse([2], [2], [a+b]))

C = sparse([1, 2], [2, 1], [c, c])
D = sparse([1, 2], [2, 1], [d, d])

@test isequal(C * D, sparse([1,2], [1,2], [c * d, c * d]))

@variables t σ ρ β
@variables x(t) y(t) z(t)
D = Differential(t)
Dx = Differential(x)
Dy = Differential(y)
Dz = Differential(z)

eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]
J = Num[Dx(eqs[1].rhs) Dy(eqs[1].rhs) Dz(eqs[1].rhs)
 Dx(eqs[2].rhs) Dy(eqs[2].rhs) Dz(eqs[2].rhs)
 Dx(eqs[3].rhs) Dy(eqs[3].rhs) Dz(eqs[3].rhs)]

J = expand_derivatives.(J)
using LinearAlgebra
luJ = lu(J,Val(false))

@variables M[1:2,1:2]
inv(M)

@variables b[1:2]
M = [1 0; 0 2]
M \ b
M \ reshape(b,2,1)
M = [1 1; 0 2]
M \ reshape(b,2,1)


M = [1 a; 0 2]
M \ b
M \ [1, 2]

# test det
@variables X[1:4,1:4]
d1 = det(X, laplace=true)
_det1 = eval(build_function(d1,X))
A = [1 1 1 1
     1 0 1 1
     1 1 0 1
     1 1 1 0]
@test _det1(map(Num, A)) == -1

@variables X[1:3,1:3]
d1 = det(X, laplace=true)
_det1 = eval(build_function(d1, X))
A = [1 1 1
     1 0 1
     1 1 1]
@test _det1(map(Num, A)) == 0

@variables a b c d
z1 = a + b * im
z2 = c + d * im
@test isequal(a/im, - a*im)
@test z1 * 2 - Complex(2a, 2b) == 0
@test isequal(2z1, Complex(2a, 2b))
@test isequal(simplify_fractions(z1 / z1), 1)
@test isequal(z1 / z2, Complex((a*c + b*d)/(c^2 + d^2), (b*c - a*d)/(c^2 + d^2)))
@test isequal(1 / z2, Complex(c/(c^2 + d^2), -d/(c^2 + d^2)))
@test isequal(z1 / c, Complex(a/c, b/c))
@test isequal(a / z2, Complex(a*c/(c^2 + d^2), -a*d/(c^2 + d^2)))
@test isequal(z1 * z2, Complex(a*c - b*d, a*d + b*c))
@test isequal(z1 - z2, Complex(a - c, b - d))
@test isequal(z1 + z2, Complex(a + c, b + d))
@test isequal(z1 + 2, Complex(a + 2, b))
@test isequal(2 + z1, Complex(2 + a, b))
@test isequal(z1 - 2, Complex(a - 2, b))
@test isequal(2 - z1, Complex(2 - a, -b))
@test isequal(z1 ^ 2, a^2 - b^2 + 2a*b*im)

@testset "Num dispatch intersections" begin
    @test Num(1 + 0im) isa Num
    @test_throws InexactError Num(1 + 2im)
    @test_throws InexactError convert(Num, 1 + 2im)
    @test (1 + 2im == a) isa Num
    @test (a == 1 + 2im) isa Num
    @test (Base.MathConstants.pi == a) isa Num
    @test (a == Base.MathConstants.pi) isa Num
    @test !isequal(1 + 2im, a)
    @test !isequal(a, 1 + 2im)
    @test isequal((Complex(a, b)^false), Complex(1, 0))
    @test promote_type(Complex{Float64}, Num) === Complex{Num}

    for value in (1.0, 1 + 2im, unwrap(a))
        @test (SymbolicUtils.:<ₑ(a, value)) isa Bool
        @test (SymbolicUtils.:<ₑ(value, a)) isa Bool
    end
end

@testset "Num package-array intersections" begin
    bits = BitVector([true, false])
    sparse_vector = sparsevec([1], [1.0], 2)
    static_vector = SArray{Tuple{2}}((1.0, 2.0))

    @test bits / a isa BasicSymbolic
    @test a / bits isa BasicSymbolic
    @test sparse_vector / a isa BasicSymbolic
    @test static_vector / a isa BasicSymbolic
    @test a \ static_vector isa BasicSymbolic
    @test view([1.0 2.0; 3.0 4.0], :, :) / a isa BasicSymbolic
    @test a \ view([1.0, 2.0], :) isa BasicSymbolic
    @test sparse([1.0 0.0; 0.0 2.0]) / a isa BasicSymbolic
    @test a / (1:3) isa BasicSymbolic
    @test (a in 1:3) isa BasicSymbolic
    @test (a in static_vector) isa BasicSymbolic
    for values in (
            view([1.0, 2.0], :), Diagonal([1.0, 2.0]),
            Symmetric([1.0 0.0; 0.0 2.0]), adjoint([1.0, 2.0]),
        )
        @test (a in values) isa BasicSymbolic
    end

    upper = UpperTriangular(Num[1 1; 0 1])
    upper_rhs = sparsevec([1, 2], Num[2, 1], 2)
    @test ldiv!(upper, upper_rhs) == Num[1, 1]
    upper_rhs = sparsevec([1, 2], Num[2, 1], 2)
    upper_output = Num[0, 0]
    @test ldiv!(upper, upper_rhs, upper_output) == Num[1, 1]

    unit_lower = UnitLowerTriangular(Num[1 0; 1 1])
    lower_rhs = sparsevec([1, 2], Num[1, 2], 2)
    @test ldiv!(unit_lower, lower_rhs) == Num[1, 1]
    lower_rhs = sparsevec([1, 2], Num[1, 2], 2)
    lower_output = Num[0, 0]
    @test ldiv!(unit_lower, lower_rhs, lower_output) == Num[1, 1]
end

@testset "Num scalar method intersections" begin
    for f in (
            SpecialFunctions.besseli, SpecialFunctions.besselj,
            SpecialFunctions.besselk, SpecialFunctions.bessely,
        )
        @test f(a, 1.0) isa Num
        @test f(a, 1.0im) isa Num
    end
    @test copysign(1, a) isa Num
    @test copysign(1.0f0, a) isa Num
    @test copysign(1.0, a) isa Num
    @test SpecialFunctions.polygamma(1, a) isa Num
end

@testset "Num structured-array intersections" begin
    structured = (
        Bidiagonal([1.0, 2.0], [3.0], :U),
        Diagonal([1.0, 2.0]),
        Tridiagonal([1.0], [2.0, 3.0], [4.0]),
        SymTridiagonal([1.0, 2.0], [3.0]),
        Symmetric([1.0 2.0; 2.0 3.0]),
        Hermitian([1.0 2.0; 2.0 3.0]),
        UpperHessenberg([1.0 2.0; 3.0 4.0]),
        UpperTriangular([1.0 2.0; 0.0 3.0]),
        LowerTriangular([1.0 0.0; 2.0 3.0]),
        UnitUpperTriangular([1.0 2.0; 0.0 1.0]),
        UnitLowerTriangular([1.0 0.0; 2.0 1.0]),
    )
    for matrix in structured
        @test (matrix / a) isa BasicSymbolic
        @test (a \ matrix) isa BasicSymbolic
    end
    for matrix in (structured[2], structured[4], structured[5], structured[6])
        @test (matrix^a) isa BasicSymbolic
    end
    @test (a^[1.0 0.0; 0.0 1.0]) isa BasicSymbolic
end

@test isequal((0 ~ a+0*im), 0 ~ a)
@test isequal((im ~ b+c*im), [0 ~ b; 1 ~ c])
@test isequal((0 ~ z1), [0 ~ a, 0 ~ b])
@test isequal((z1 ~ z2), [a ~ c, b ~ d])

@test a + im === Complex(a, Num(1))
@test real(a) === a
@test conj(a) === a
@test imag(a) === Num(0)

@test isequal(sign(x), Num(SymbolicUtils.Term{SymReal}(sign, [Symbolics.value(x)]; type = Real)))
@test sign(Num(1)) isa Num
@test isequal(evaluate(sign(Num(1))), Num(1))
@test isequal(evaluate(sign(Num(-1))), Num(-1))

@test isequal(ℯ^a, exp(a))

@test isequal(Symbolics.derivative(abs(x), x), ifelse(signbit(x), -1, 1))
@test isequal(Symbolics.derivative(sign(x), x), 0)
@test isequal(Symbolics.derivative(signbit(x), x), 0)

@test iszero(Num(0.0))
@test isone(Num(1.0))
@test isone(complex(Num(1), Num(0)))
@test iszero(complex(Num(0), Num(0)))

x = Num.(randn(10))
@test value(evaluate(norm(x))) ≈ norm(Symbolics.value.(x))
@test value(evaluate(norm(x, Inf))) == norm(Symbolics.value.(x), Inf)
@test value(evaluate(norm(x, 1))) ≈ norm(Symbolics.value.(x), 1)
@test value(evaluate(norm(x, 1.2))) ≈ norm(Symbolics.value.(x), 1.2)

@test evaluate.(clamp.(x, 0, 1)) == clamp.(Symbolics.value.(x), 0, 1)
@test isequal(Symbolics.derivative(clamp(a, 0, 1), a), ifelse(a < 0, 0, ifelse(a>1, 0, 1)))

@variables x[1:2]
@test isequal(scalarize(norm(x)), sqrt(abs2(x[1]) + abs2(x[2])))
@test isequal(scalarize(norm(x, Inf)), max(abs(x[1]), abs(x[2])))
@test isequal(scalarize(norm(x, 1)), abs(x[1]) + abs(x[2]))
@test isequal(scalarize(norm(x, 1.2)), (abs(x[1])^1.2 + abs(x[2])^1.2)^(1/1.2))

@variables x y
@test isequal(expand((x+y)^2), x^2 + y^2 + 2x*y)

@variables t p x(t) y(t) z(t)
@test isequal(substitute(y ~ x*p, Dict(x => z, y => t)), t ~ z*p)
@test ~(!((1 < x) & (x < 2) | (x >= 100) ⊻ (x <= 1000) & (x != 100))) isa Num

# Maybe move me

@variables x[1:3]
ex = x[1]+x[2]
@test issetequal(Symbolics.get_variables(ex), Symbolics.scalarize(x[1:2]))

@variables x
A = [x[1] 2
     2    0.0]
B = [x[1] 1.0
    2.0 0.0]
@test_throws MethodError Matrix{Float64}(A)
@test_broken Matrix{Float64}(A-B) isa Matrix{Float64}
@test_broken Matrix{Float64}(A-B) == [0.0 1.0;0.0 0.0]

@test isequal(simplify(cos(x)^2 + sin(x)^2 + im * x), 1 + x*im)

using Base.MathConstants: catalan, γ, π, φ, ℯ
for q in (catalan, γ, π, φ, ℯ)
    nq = Num(q)
    @test 3nq^5/7 isa Num
    @test Symbolics.value(substitute(3nq^5/7, Dict(nq=>big(q)))) ≈ 3big(q)^5/7
end

using Markdown
@variables x
d = Base.Docs.getdoc(x)
@test d isa Markdown.MD
stringcontent = string(d.content)
@test occursin("Metadata", stringcontent)
@test occursin("(:variables, :x)", stringcontent)

@variables t
for f in [<, <=, >, >=, isless]
    @test_nowarn f(t, 1.0)
end

@test_nowarn binomial(t, 1)

# test for https://github.com/JuliaSymbolics/Symbolics.jl/issues/1028
let
    @variables t A(t) B
    @test try binomial(A, 2*B^2)
        true
    catch
        false
    end
    @test try binomial(Symbolics.value(A), Symbolics.value(2*B^2))
        true
    catch
        false
    end
end

using Symbolics: scalarize
@variables X[1:3, 1:3] x
sX = fill(x, 3, 3)
sx = fill(x, 3)
@test isequal(scalarize(X + sX), scalarize(X) + sX)
@test isequal(scalarize(X * sX), scalarize(X) * sX)
@test isequal(scalarize(X * sx), scalarize(X) * sx)

@testset "Equations from nonsymbolic operands" begin
    @variables x y
    @test (1 ~ 3) isa Equation
    @test (nothing ~ nothing) isa Equation
    @test ([x, y] ~ [1, 2]) isa Equation
    @test isequal(substitute(x ~ y, Dict(x => 1, y => 2)), Equation(1, 2))
    @test copysign(1 // 2, x) isa Num
end
