local computer = computer
local component = component
local unicode = unicode
local kernel = {}

kernel.io = {}
kernel._K = _G

_G._OSVERSION = "Pipes/1.2"

local runlevel, shutdown = "S", computer.shutdown
computer.runlevel = function() return runlevel end
computer.shutdown = function(reboot)
    runlevel = reboot and 6 or 0
    if os.sleep then
        computer.pushSignal("shutdown")
        os.sleep(0.1) -- Allow shutdown processing.
    end
    shutdown(reboot)
end

--Setup initial drive

kernel.initd = {}
function kernel.initd.invoke(method, ...)
    return component.invoke(computer.getBootAddress(), method, ...)
end
function kernel.initd.open(file) return kernel.initd.invoke("open", file) end
function kernel.initd.read(handle) return kernel.initd.invoke("read", handle, math.huge) end
function kernel.initd.close(handle) return kernel.initd.invoke("close", handle) end
function kernel.initd.isDirectory(path) return kernel.initd.invoke("isDirectory", path) end

--Setup early kernel logging

do
    local screen = component.list('screen')()
    for address in component.list('screen') do
        if #component.invoke(address, 'getKeyboards') > 0 then
            screen = address
        end
    end
    
    local gpu = component.list("gpu", true)()
    local w, h
    if gpu and screen then
        component.invoke(gpu, "bind", screen)
        w, h = component.invoke(gpu, "getResolution")
        component.invoke(gpu, "setResolution", w, h)
        h = h - 3
        component.invoke(gpu, "setBackground", 0x555555)
        component.invoke(gpu, "fill", 1, 1, w, 3, " ")
        component.invoke(gpu, "setForeground", 0x000000)
        component.invoke(gpu, "set", 1, 1, _OSVERSION)
        component.invoke(gpu, "setBackground", 0xAAAAAA)
        component.invoke(gpu, "fill", 4, 2, w-7, 1, "-")
        component.invoke(gpu, "setBackground", 0x000000)
        component.invoke(gpu, "setForeground", 0xFFFFFF)
        component.invoke(gpu, "fill", 1, 4, w, h, " ")
    end
    local y = 1
        
    function kernel._println(msg)
        for line in string.gmatch(tostring(msg), "([^\n]+)") do
            pcall(function()kernel.io.debug(msg)end)
        end
        if gpu and screen then
            for line in string.gmatch(tostring(msg), "([^\n]+)") do
                kernel._K.component.invoke(gpu, "set", 1, y + 3, line)
                if y == h then
                    kernel._K.component.invoke(gpu, "copy", 1, 2+3, w, h - 1, 0, -1)
                    kernel._K.component.invoke(gpu, "fill", 1, h+3, w, 1, " ")
                else
                    y = y + 1
                end
            end
        end
    end
    
    function kernel._status(done)
        if gpu and screen then
            component.invoke(gpu, "setBackground", 0xFF0000)
            component.invoke(gpu, "setForeground", 0x000000)
            component.invoke(gpu, "fill", 4, 2, (w-7) * done, 1, "X")
            component.invoke(gpu, "setBackground", 0x000000)
            component.invoke(gpu, "setForeground", 0xFFFFFF)
        end
    end
    
    kernel.io.println = kernel._println
    kernel.io.debug = function()end
end

kernel.io.println(_OSVERSION .. " Starting")
if computer.setArchitecture then
    computer.setArchitecture("Lua 5.3")
end

--The most important function

local panicPull = kernel._K.computer.pullSignal
function kernel.panic()
    kernel._println("--------------------------------------------------------")
    pcall(function()
        for s in string.gmatch(tostring(debug.traceback()), "[^\r\n]+") do
    		kernel._println(s)
    	end
    end)
    pcall(function()
        kernel._println("--------------------------------------------------------")
        kernel._println("System halted: Panic")
    end)
    while true do panicPull(0.5) kernel._K.computer.beep() end
end

-- Custom low-level loadfile/dofile implementation reading from our Init disk.
local function loadfile(file, _, _, env)
    --kernel.io.println("> " .. file)
    local handle, reason = kernel.initd.open(file)
    if not handle then
        error(reason)
    end
    local buffer = ""
    repeat
        local data, reason = kernel.initd.read(handle)
        if not data and reason then
            error(reason)
        end
        buffer = buffer .. (data or "")
    until not data
    kernel.initd.close(handle)
    return load(buffer, "=" .. file, nil, env)
end

local function dofile(file)
    local program, reason = loadfile(file)
    if program then
        local result = table.pack(pcall(program))
        if result[1] then
            return table.unpack(result, 2, result.n)
        else
            error(result[2])
        end
    else
        error(reason)
    end
end

--Setup early managed _G
local _K = _G
_G = _G.setmetatable({}, {__index = _K})
kernel._K = _K
kernel._G = _G

kernel.io.println("Loading base modules")

local modules = {}
for _, file in ipairs(kernel.initd.invoke("list", "lib/modules/base")) do
    local path = "lib/modules/base/" .. file
    if not kernel.initd.isDirectory(path) then
        table.insert(modules, {path = path, file = file,  name = file:match("[^0-9_%.]+")})
    end
end

local moduleEnv = setmetatable({kernel = kernel}, {__index = _K})
kernel.modules = {}
table.sort(modules, function(a, b) return a.file < b.file end)
for i = 1, #modules do
    kernel.io.println("Load module " .. modules[i].name)
    
    kernel.modules[modules[i].name] = {}
    kernel.modules[modules[i].name]._G = kernel.modules[modules[i].name]
    local program, reason = loadfile(modules[i].path, nil, nil, setmetatable(kernel.modules[modules[i].name], {__index = moduleEnv}))
    if program then
        local result = table.pack(pcall(program))
        if not result[1] then
            kernel.io.println(result[2])
            kernel.panic()
        end
        kernel._status((0.5 / (#modules)) * i)
    else
        kernel.io.println(reason)
        kernel.panic()
    end
end

kernel.io.println("Starting base modules")

for i = 1, #modules do
    if kernel.modules[modules[i].name].start then
        kernel.io.println("Start module " .. modules[i].name)
        local result = table.pack(xpcall(kernel.modules[modules[i].name].start, function(e)
            pcall(kernel._println, "Module tb: " .. debug.traceback())
            pcall(kernel._println, "E: " .. tostring(e))
        end))
        
        if not result[1] then
            pcall(kernel._println, "Module start failed: " .. modules[i].name)
            pcall(kernel._println, result[2])
            kernel.panic()
        end
    end
    kernel._status(0.5 + (0.5 / (#modules)) * i)
end

kernel._println("All modules finished")
kernel.panic()
