module Virtual
mutable struct File
    content::String
end
mutable struct Directory
    children::Set{Int}
end

mutable struct FileSystem
    root::Int
    current::Int
    number::Int
    parents::Vector{Int}
    names::Vector{String}
    nodes::Vector{Union{File,Directory,Nothing}}
end

empty_dir() = Directory(Set{Int}())
FileSystem() = FileSystem(1, 1, 1, [1], [""], [empty_dir()])
# rootjail(fs::FileSystem, id)

parent(fs::FileSystem, id) = fs.parents[id]
name(fs::FileSystem, id) = fs.names[id]

errormsg_pos(fs::FileSystem, id) = "#$id ($(name(fs, id)))"
function apply_insert(fs::FileSystem)
    fs.number += 1
    fs.number
end

function show_path(fs::FileSystem, id::Integer, relative::Integer=0)
    if iszero(relative) # use full path
        s = "/$(name(fs, id))"
        while true
            if id == fs.root
                break
            end
            id = parent(fs, id)
            s = "/$(name(fs, id))$s"
        end
    else
    end
    s
end

function reach(fs::FileSystem, id::Integer, path)
    for s in path
        if s == ".."
            id = parent(fs, id)
        elseif s != "."
            chdn = fs.nodes[id].children
            id = 0
            for chd in chdn
                if name(fs, chd) == s
                    id = chd
                    break
                end
            end
            iszero(id) && error("Object name '$s' not found at $(errormsg_pos(fs, id))")
        end
    end
    id
end
function reach(fs::FileSystem, path)
    startswith(path, '/') ? reach(fs, fs.root, path[2:end]) : reach(fs, fs.current, path)
end
function reach_directory(fs::FileSystem, path)
    x = reach(fs, path)
    isa(fs.nodes[x], Directory) || error("Failed to access directory.")
    x
end
function reach_parentit(fs::FileSystem, path)

end

function remove_directory(fs::FileSystem, id)
    fs.nodes[id] = nothing
    delete!(fs.nodes[parent(fs, id)].children, id)
    fs.number -= 1
end

### Function for commands ###
function _pwd(fs::FileSystem)
    @info show_path(fs, fs.current, 0)
end
function _ls(fs::FileSystem, from::Integer=fs.current)
    dir = fs.nodes[from]
    isa(dir, Directory) || error("$(errormsg_pos(fs, id)) is not a directory")
    for id in dir.children
        node = fs.nodes[id]
        name = name(fs, id)
        isa(node, Directory) && print("<DIR>")
        println("\t\t", name)
    end
end
function _cd(fs::FileSystem, path::AbstractString)
    fs.current = reach_directory(fs, path)
end
function _mkdir(fs::FileSystem, path::AbstractString; force::Bool=true)
    par, this = reach_parentit(fs, path)
    set = dirnode(fs, par).children
    detect_childname(set, this) do id
        force || error("An object with the same name already exists.")
        remove_directory(fs, id)
    end
    id = apply_insert(fs)
    push!(parents, par)
    push!(names, this)
    push!(nodes, empty_dir())
    push!(set, id)
end
function _rmdir(fs::FileSystem, path::AbstractString)
    par, this = reach_parentit(fs, path)
    set = fs.nodes[par].children
    detect_childname(set, this) do id
        remove_directory(fs, id)
    end
    pop!(set, id)
end
function _cp(fs::FileSystem, from::AbstractString, to::AbstractString; force::Bool=true)
    fnode = fs.nodes[reach_dir(fs, from)]
    par, this = reach_parentit(fs, to)
    pnode = dirnode(fs, par)
    detect_childname(pnode.children, this) do id
        force || error("An object with the same name already exists.")
        remove_directory(fs, id)
    end

end
function _mv(fs::FileSystem, from::AbstractString, to::AbstractString; force::Bool=true)

end

function __init__()
    @info "当前不支持磁盘、格式等模拟;只支持命令格式,不支持额外参数\n使用 \"sim 目标\" 切换模拟环境"
end

end # module

const simdata = Dict{String,Tuple{UnitRange,UInt8,Symbol}}(
    # "sim"   => (1:1, 0x7, :)
    "pwd" => (0:0, 0x5, :pwd),
    "chdir" => (0:0, 0x2, :pwd),
    "ls" => (0:1, 0x1, :ls),
    "dir" => (0:1, 0x2, :ls),
    "readdir" => (0:1, 0x4, :ls),
    "cd" => (1:1, 0x7, :cd),
    "mkdir" => (1:1, 0x7, :mkdir),
    "md" => (1:1, 0x2, :mkdir),
    "rmdir" => (1:1, 0x3, :rmdir),
    "rm" => (1:1, 0x5, :rm),
    "del" => (1:1, 0x2, :rm),
    "cp" => (2:2, 0x5, :cp),
    "copy" => (2:2, 0x2, :cp),
    "mv" => (2:2, 0x7, :mv),
)
function init()
    fs = FileSystem()
    _mkdir(fs, "home")
    _cd(fs, "home")
    _touch(fs, "/home/hello.txt", "你好")
    sim = 0x0
    while true
        print("vfs> ")
        s = readline()
        try
            v = split(s)
            isempty(v) && continue
            if v[1] == "sim"
                sim = v[2] == "unix" ? 0x0 :
                      v[2] == "windows" ? 0x1 :
                      v[2] == "julia" ? 0x2 :
                      error("不支持的模拟对象")
                println()
                continue
            elseif v[1] == "quit"
                return
            end
            name = String(v[1])
            haskey(simdata, name) || error("不支持的命令")
            @inbounds data = simdata[name]
            vl = length(v) - 1
            in(vl, data[1]) || error("错误的参数个数")
            ((data[2] >> sim & 0x1) != 0x1) && error("该环境下不支持此命令")
            com = data[3]
            com == :pwd ? _pwd(fs) :
            com == :ls ? _ls(fs, vl == 1 ? getdir(fs, v[2]) : fs.current) :
            com == :cd ? _cd(fs, v[2]) :
            com == :mkdir ? _mkdir(fs, v[2]) :
            com == :rmdir ? _rmdir(fs, v[2]) :
            com == :rm ? _rm(fs, v[2]) :
            com == :cp ? _cp(fs, v[2], v[3]) :
            com == :mv ? _mv(fs, v[2], v[3]) :
            nothing
        catch er
            isa(er, ErrorException) && throw(er)
            printstyled(er; color=:light_red)
            println()
        end
        println()
    end
end