local env    = environment()
local l      = param_univ("l")
local nat    = Const("nat")
local real   = Const("real")
local one    = Const("one")
local Ul     = mk_sort(l)
local lst_l  = Const("lst", {l})
local vec_l  = Const("vec", {l})
local mat_l  = Const("mat", {l})
local A      = Local("A", Ul)
local n      = Local("n", nat)
local ll     = Local("ll", lst_l(A))
local len_l  = Const("len", {l})
local lst_1  = Const("lst", {1})
local l1     = param_univ("l1")
local l2     = param_univ("l2")
local m      = Local("m", nat)
env = add_decl(env, mk_var_decl("nat", Type))
env = add_decl(env, mk_var_decl("real", Type))
env = add_decl(env, mk_var_decl("one", nat))
env = add_decl(env, mk_var_decl("lst", {l}, mk_arrow(Ul, Ul)))
env = add_decl(env, mk_var_decl("len", {l}, Pi(A, mk_arrow(lst_l(A), nat))))
env = add_decl(env, mk_var_decl("vec", {l}, mk_arrow(Ul, nat, Ul)))
env = add_decl(env, mk_var_decl("mat", {l}, mk_arrow(Ul, nat, nat, Ul)))
env = add_decl(env, mk_var_decl("dlst", {l1, l2}, mk_arrow(mk_sort(l1), mk_sort(l2), mk_sort(max_univ(l1, l2)))))
env = add_decl(env, mk_var_decl("vec2lst", {l}, Pi(A, n, mk_arrow(vec_l(A, n), lst_l(A)))))
env = add_decl(env, mk_var_decl("lst2vec", {l}, Pi(A, ll, vec_l(A, len_l(A, ll)))))
env = add_decl(env, mk_var_decl("vec2mat", {l}, Pi(A, n, mk_arrow(vec_l(A, n), mat_l(A, n, one)))))
env = add_decl(env, mk_var_decl("mat2dlst", {l}, Pi(A, n, m, mk_arrow(mat_l(A, n, m), Const("dlst", {l, 1})(A, nat)))))
env = add_decl(env, mk_var_decl("nat2lst", mk_arrow(nat, lst_1(nat))))
env = add_coercion(env, "lst2vec")
assert(is_coercion(env, Const("lst2vec", {l})))
assert(has_coercions_from(env, "lst"))
local lst_nat = lst_1(nat)

function display_coercions(coes)
   if not coes:is_nil() then
      print(coes:head())
      display_coercions(coes:tail())
   end
end

display_coercions(get_coercions(env, lst_nat, "vec"))
env = add_coercion(env, "vec2mat")
display_coercions(get_coercions(env, lst_nat, "mat"))
assert(env:type_check(get_coercions(env, lst_nat, "mat"):head()))
for_each_coercion_user(env, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D) .. " : " .. tostring(f)) end)
env = add_coercion(env, "nat2lst")
print("---------")
for_each_coercion_user(env, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D) .. " : " .. tostring(f)) end)
print(get_coercions(env, nat, "mat"):head())
assert(env:type_check(get_coercions(env, nat, "mat"):head()))
env = add_coercion(env, "mat2dlst")
print("---------")
for_each_coercion_user(env, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D) .. " : " .. tostring(f)) end)
print(get_coercions(env, lst_nat, "dlst"):head())
assert(env:type_check(get_coercions(env, lst_nat, "dlst"):head()))

env:export("coe1_mod.olean")
local env2 = import_modules("coe1_mod")
print(get_coercions(env2, lst_nat, "dlst"):head())
assert(env2:type_check(get_coercions(env2, lst_nat, "dlst"):head()))
assert(is_coercion(env2, "vec2mat"))
assert(is_coercion(env2, "lst2vec"))
env2 = add_decl(env2, mk_var_decl("lst2vec2", {l}, Pi(A, ll, vec_l(A, len_l(A, ll)))))
print("======")
env2 = add_coercion(env2, "lst2vec2")
print("======")
print(get_coercions(env2, lst_nat, "dlst"):head())
print("---------")
for_each_coercion_user(env2, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D) .. " : " .. tostring(f)) end)
env2 = add_coercion(env2, "vec2lst")
env2 = add_decl(env2, mk_var_decl("lst2nat", {l}, Pi(A, mk_arrow(lst_l(A), nat))))
env2 = add_coercion(env2, "lst2nat")
print("---------")
for_each_coercion_user(env2, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D)) end)
for_each_coercion_user(env2, function(C, D, f) print(tostring(C) .. " >-> " .. tostring(D) .. " : " .. tostring(f)) end)

assert(has_coercions_from(env2, lst_nat))
assert(not has_coercions_from(env2, Const("foo")))
assert(not has_coercions_from(env2, Const("lst", {1})))
assert(has_coercions_from(env2, Const("vec", {1})(nat, one)))
assert(not has_coercions_from(env2, Const("vec", {1})(nat)))
assert(not has_coercions_from(env2, Const("vec")(nat, one)))

print("Coercions (vec nat one): ")
cs = get_coercions_from(env2, Const("vec", {1})(nat, one))
for i = 1, #cs do
   print(tostring(cs[i][2]) .. " : " .. tostring(cs[i][4]) .. " : " .. tostring(cs[i][3]))
end
