--:Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH=apis.BOOT_DRIVE_PATH or "/$"
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}

function peripheral.getType(name)
    if native.isPresent(name) then
        return native.getType(name)
    end
    for n = 1, #sides do
        local side = sides[n]
        if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
            return native.call(side, "getTypeRemote", name)
        end
    end
    return nil
end

function peripheral.getNames()
    local names = {}
    for n = 1, #sides do
        local side = sides[n]
        if native.isPresent(side) then
            table.insert(names, side)
        end
        if native.hasType(side, "peripheral_hub") then
            local hubSides = native.call(side, "getConnectedSides")
            for _, hubSide in ipairs(hubSides) do
                table.insert(names, hubSide)
            end
        end
    end
    return names
end

---------------------------------------------------------
-- STORAGE
---------------------------------------------------------
local disks = {}      -- real disks
local internal = {}   -- "$" disk

---------------------------------------------------------
-- HELPERS
---------------------------------------------------------
local function norm(path)
    if not path or path == "" then return "/" end
    return fs.combine("/", path)
end

--- Creates a disk object given a base path
local function createDisk(id, basePath, readonly, periph)
    basePath = norm(basePath)

    local disk = {address=id,isReadOnly=function() return readonly end}

    function disk:spaceUsed()
        return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
    end

    function disk:spaceTotal()
        return fs.getCapacity(basePath)
    end

    function disk:list(path)
        local p = fs.combine(basePath, path)
        if not fs.exists(p) or not fs.isDir(p) then return nil, "not directory" end
        return fs.list(p)
    end

    function disk:fileExists(path)
        local p = fs.combine(basePath, path)
        return fs.exists(p) and not fs.isDir(p)
    end

    function disk:directoryExists(path)
        local p = fs.combine(basePath, path)
        return fs.exists(p) and fs.isDir(p)
    end

    function disk:type(path)
        local p = fs.combine(basePath, path)
        if not fs.exists(p) then
            return nil
        elseif fs.isDir(p) then
            return "directory"
        else
            return "file"
        end
    end

    function disk:makeDirectory(path)
        local p = fs.combine(basePath, path)
        fs.makeDir(p)
        return true
    end

    function disk:remove(path)
        local p = fs.combine(basePath, path)
        if fs.exists(p) then fs.delete(p) end
        return true
    end

    function disk:setLabel(label)
        periph.setLabel(label)
    end

    function disk:getLabel(label)
        return periph.getLabel()
    end

    function disk:attributes(path)
        local p = fs.combine(basePath, path)
        return fs.attributes(p)
    end

    function disk:open(path, mode)
        local p = fs.combine(basePath, path)
        return fs.open(p, mode)
    end
    
    return disk
end

---------------------------------------------------------
-- INTERNAL DISK "$" (mapped to "/")
---------------------------------------------------------
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
    setLabel=function(label)
        local h = fs.open("/.label", "w")
        h.write(label)
        h.close()
    end,
    getLabel=function()
        local h = fs.open("/.label", "r")
        local label = h.readAll()
        h.close()
        return label
    end
})

---------------------------------------------------------
-- SCAN REAL DISK PERIPHERALS
---------------------------------------------------------
local function refresh()
    -- remove disks that no longer exist
    for id, _ in pairs(disks) do
        if not peripheral.getType(id) then
            disks[id] = nil
        end
    end

    -- detect new disks
    for _, name in ipairs(peripheral.getNames()) do
        if peripheral.getType(name) == "disk" then
            if not disks[name] then
                local mount = disk.getMountPath(name)
                if mount then
                    disks[name] = createDisk(name, mount, false, disk)
                end
            end
        end
    end
end

---------------------------------------------------------
-- ITERATOR
---------------------------------------------------------
local function iter()
    refresh()
    -- first internal
    local combined = {}

    for id, obj in pairs(internal) do
        combined[id] = obj
    end
    for id, obj in pairs(disks) do
        combined[id] = obj
    end

    return pairs(combined)
end

---------------------------------------------------------
-- MODULE RETURN
---------------------------------------------------------
return {
    refresh = refresh,
    list = iter
}
