local function octal_to_number(str)
    str = str:gsub("%z", ""):match("^%s*(.-)%s*$")
    return tonumber(str, 8) or 0
end

local function dedupe_path(path)
            
    local parts = {}
    for p in path:gmatch("[^/]+") do table.insert(parts, p) end

        for prefix_len = 1, math.floor(#parts / 2) do
        local ok = true
        for i = 1, prefix_len do
            if parts[i] ~= parts[i + prefix_len] then
                ok = false
                break
            end
        end
        if ok then
                        local cleaned = {}
            for i = 1, #parts - prefix_len do
                cleaned[#cleaned + 1] = parts[i + prefix_len]
            end
            return table.concat(cleaned, "/")
        end
    end

    return path
end


local function make_dirs(root, path)
    local cur = root
    for part in path:gmatch("([^/]+)/") do
        if not cur[part] then
            cur[part] = { __type = "dir", __entries = {} }
        end
        cur = cur[part].__entries
    end
    return cur
end

local function flatten(node, prefix)
    local out = {}
    prefix = prefix or ""

    for name, obj in pairs(node) do
        local full = prefix .. name
        if obj.__type == "file" then
            out[#out+1] = {
                name = full,
                type = "file",
                contents = obj.__contents
            }
        elseif obj.__type == "dir" then
            out[#out+1] = {
                name = full .. "/",
                type = "dir",
                contents = flatten(obj.__entries, full .. "/")
            }
        end
    end

    return out
end

local function unpack_tar(tarstr)
    local i = 1
    local len = #tarstr
    local root = {}

    while i + 512 <= len do
        local header = tarstr:sub(i, i + 511)

                if header:match("^\0+$") then break end

                local name_raw   = header:sub(1, 100):gsub("%z.*", "")
        local prefix_raw = header:sub(346, 500):gsub("%z.*", "")

        local name
        if prefix_raw ~= "" then
            name = prefix_raw .. "/" .. name_raw
        else
            name = name_raw
        end

                name = name:gsub("^%./", ""):gsub("/+", "/")

                name = dedupe_path(name)

        local size = octal_to_number(header:sub(125,136))
        local typeflag = header:sub(157,157)

        i = i + 512

        local contents = tarstr:sub(i, i + size - 1)
        local pad = (512 - (size % 512)) % 512
        i = i + size + pad

        if name ~= "" then

            local is_dir = typeflag == "5" or name:sub(-1) == "/"
            local clean_name = name:gsub("/$", "")
            
            if clean_name ~= "" then

                local parent_path = clean_name:match("(.+)/")
                local fname = clean_name:match("([^/]+)$")
                if not fname then

                    local parent = root
                    if parent_path then
                        parent = make_dirs(root, parent_path .. "/")
                    end
                
                    if is_dir then
                        parent[fname] = parent[fname] or { __type = "dir", __entries = {} }
                    else
                        parent[fname] = { __type = "file", __contents = contents }
                    end
                end
            end
        end
    end

    return flatten(root)
end

local function write_directory(prefix, items)
    for _, v in ipairs(items) do
        if v.type == "dir" then
            fs.makeDir(prefix..v.name)
            write_directory(prefix, v.contents)
        elseif v.type == "file" then
            local file = fs.open(prefix..v.name, "w")
            file.write(v.contents)
            file.close()
        end
    end
end

local in_tar  = ({...})[1]
local out_dir = ({...})[2]

if not in_tar or not out_dir then
    print("Usage: unpack_tar <tarfile> <output_dir>")
    return
end

local f = fs.open(in_tar, "r")
local tarstr = f.readAll()
f.close()

local list = unpack_tar(tarstr)
write_directory(out_dir, list)

print("TAR extracted into: " .. out_dir)
