using ModelingToolkitBase
using ModelingToolkitBase: SymScope, t_nounits as t, D_nounits as D
using Symbolics: arguments, value, getname, VartypeT
import SymbolicUtils as SU
using Test

@variables a b(t) c d e(t)

b = ParentScope(b)
c = ParentScope(ParentScope(c))
d = GlobalScope(d)
@test all(x -> x isa Num, [b, c, d])

# ensure it works on Term too
LocalScope(e.val)
ParentScope(e.val)
GlobalScope(e.val)

ie = ParentScope(1 / e)
@test getmetadata(arguments(value(ie))[2], SymScope) === ParentScope(LocalScope())

eqs = [
    0 ~ a
    0 ~ b
    0 ~ c
    0 ~ d
]
@named sub4 = System(eqs, [a, b, c, d], [])
@named sub3 = System(eqs, [a, b, c, d], [])
@named sub2 = System(Equation[], [], [], systems = [sub3, sub4])
@named sub1 = System(Equation[], [], [], systems = [sub2])
@named sys = System(Equation[], [], [], systems = [sub1])

names = ModelingToolkitBase.getname.(unknowns(sys))
@test :d in names
@test Symbol("sub1₊c") in names
@test Symbol("sub1₊sub2₊b") in names
@test Symbol("sub1₊sub2₊sub3₊a") in names
@test Symbol("sub1₊sub2₊sub4₊a") in names

@named foo = System(eqs, [a, b, c, d], [])
@named bar = System(eqs, [a, b, c, d], [])
@test ModelingToolkitBase.getname(
    ModelingToolkitBase.namespace_expr(
        ModelingToolkitBase.namespace_expr(
            b,
            foo
        ),
        bar
    )
) == Symbol("bar₊b")

function renamed(nss, sym)
    return ModelingToolkitBase.getname(foldr(ModelingToolkitBase.renamespace, nss, init = sym))
end

@test renamed([:foo :bar :baz], a) == Symbol("foo₊bar₊baz₊a")
@test renamed([:foo :bar :baz], b) == Symbol("foo₊bar₊b")
@test renamed([:foo :bar :baz], c) == Symbol("foo₊c")
@test renamed([:foo :bar :baz], d) == :d

@parameters a b c d
p = [
    a
    ParentScope(b)
    ParentScope(ParentScope(c))
    GlobalScope(d)
]

level0 = System(Equation[], t, [], p; name = :level0)
level1 = System(Equation[], t, [], []; name = :level1) ∘ level0
level2 = System(Equation[], t, [], []; name = :level2) ∘ level1
level3 = System(Equation[], t, [], []; name = :level3) ∘ level2

ps = ModelingToolkitBase.getname.(parameters(level3))

@test isequal(ps[1], :level2₊level1₊level0₊a)
@test isequal(ps[2], :level2₊level1₊b)
@test isequal(ps[3], :level2₊c)
@test isequal(ps[4], :d)

# Issue@2252
# Tests from PR#2354
@parameters xx[1:2]
arr_p = [ParentScope(xx[1]), xx[2]]
arr0 = System(Equation[], t, [], arr_p; name = :arr0)
arr1 = System(Equation[], t, [], []; name = :arr1) ∘ arr0
arr_ps = ModelingToolkitBase.getname.(parameters(arr1))
@test isequal(arr_ps[1], Symbol("xx"))
@test isequal(arr_ps[2], Symbol("arr0₊xx"))

function Foo(; name, p = 1)
    @parameters p = p
    @variables x(t)
    return System(D(x) ~ p, t; name)
end
function Bar(; name, p = 2)
    @parameters p = p
    @variables x(t)
    @named foo = Foo(; p)
    return System(D(x) ~ p + t, t; systems = [foo], name)
end
@named bar = Bar()
bar = complete(bar)
@test length(parameters(bar)) == 1
@test getname.(parameters(bar)) == [:p]
@test value(initial_conditions(bar)[bar.p]) == 2
@test isequal(bindings(bar)[bar.foo.p], bar.p)

@testset "Issue#3101" begin
    @variables x1(t) x2(t) x3(t) x4(t)
    x2 = ParentScope(x2)
    x3 = ParentScope(ParentScope(x3))
    x4 = GlobalScope(x4)
    @parameters p1 p2 p3 p4
    p2 = ParentScope(p2)
    p3 = ParentScope(ParentScope(p3))
    p4 = GlobalScope(p4)

    @named sys1 = System([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t)
    @test isequal(x1, only(unknowns(sys1)))
    @test isequal(p1, only(parameters(sys1)))
    @named sys2 = System(Equation[], t; systems = [sys1])
    @test length(unknowns(sys2)) == 2
    @test any(isequal(x2), unknowns(sys2))
    @test length(parameters(sys2)) == 2
    @test any(isequal(p2), parameters(sys2))
    @named sys3 = System(Equation[], t)
    sys3 = sys3 ∘ sys2
    @test length(unknowns(sys3)) == 3
    @test any(isequal(x3), unknowns(sys3))
    @test length(parameters(sys3)) == 3
    @test any(isequal(p3), parameters(sys3))
    sys4 = complete(sys3)
    @test length(unknowns(sys4)) == 4
    @test length(parameters(sys4)) == 4
    sys5 = mtkcompile(sys3)
    @test length(unknowns(sys5)) == 4
    @test any(isequal(x4), unknowns(sys5))
    @test length(parameters(sys5)) == 4
    @test any(isequal(p4), parameters(sys5))
end

@testset "Array globalscope variables are not discovered early if used scalarized" begin
    @parameters p[1:1]
    p = GlobalScope(p)
    @variables x(t)
    @named sys = System([D(x) ~ p[1]], t)
    @test isempty(parameters(sys))
end

@testset "`discover_globalscoped` searches array equations" begin
    @parameters cond::Bool
    cond = GlobalScope(cond)
    @variables x(t)[1:2]
    @named sys = System([D(x) ~ [ifelse(cond, 0, 1), ifelse(cond, 1, 2)]], t)
    @test isempty(parameters(sys))
    sys2 = ModelingToolkitBase.discover_globalscoped(sys)
    @test isequal(only(parameters(sys2)), cond)
end

@testset "`@named` applies `ParentScope` to arrays of symbolics" begin
    function Foo(; name, k)
        tmp = SU.Const{VartypeT}(k)
        vars = SU.search_variables(tmp)
        for var in vars
            @test SU.getmetadata(var, SymScope, LocalScope()) == ParentScope(LocalScope())
        end
    end
    @parameters k
    @variables x(t)
    @named foo = Foo(; k = [k + 1, x + 2])
end

@testset "Array `GlobalScope` variables are discovered unscalarized" begin
    @parameters p[1:2]
    p = GlobalScope(p)
    @variables x(t)
    @named sys = System([D(x) ~ p[1] * x + p[2]], t)
    @test_nowarn complete(sys)
end
