
-- MEKANISM FISSION REACTOR DASHBOARD
-- Project Infinity | CC:Tweaked
-- Monitor: monitor_13
-- Reactor: fissionreactorLogicAdapter_3

local MONITOR = "monitor_13"
local REACTOR = "fissionreactorLogicAdapter_3"
local UPDATE = 0.25

local mon = peripheral.wrap(MONITOR)
local r = peripheral.wrap(REACTOR)

if not mon then error("Cannot find " .. MONITOR) end
if not r then error("Cannot find " .. REACTOR) end

local C = colors
mon.setTextScale(0.5)
mon.setBackgroundColor(C.black)
mon.setTextColor(C.white)
mon.clear()

local W, H = mon.getSize()
local previous = {}
local lastTime = os.clock()
local last = {}

local function call(method, ...)
    if not r[method] then return nil end
    local ok, result = pcall(r[method], ...)
    if ok then return result end
    return nil
end

local function amount(value)
    if type(value) == "table" then
        return tonumber(value.amount) or 0
    end
    return tonumber(value) or 0
end

local function tank(method)
    return amount(call(method))
end

local function fmt(n)
    if n == nil then return "N/A" end
    n = tonumber(n) or 0
    if math.abs(n) >= 1e9 then
        return string.format("%.2fB", n / 1e9)
    elseif math.abs(n) >= 1e6 then
        return string.format("%.2fM", n / 1e6)
    elseif math.abs(n) >= 1e3 then
        return string.format("%.2fK", n / 1e3)
    end
    return string.format("%.2f", n)
end

local function pct(n)
    if n == nil then return "N/A" end
    n = tonumber(n) or 0
    if n <= 1 then n = n * 100 end
    return string.format("%.1f%%", n)
end

local function line(y, text, color)
    if y > H then return end
    text = tostring(text)
    if #text > W then text = text:sub(1, W) end
    text = text .. string.rep(" ", math.max(0, W - #text))
    mon.setCursorPos(1, y)
    mon.setTextColor(color or C.white)
    mon.write(text)
end

local function section(y, text)
    line(y, "== " .. text .. " ", C.cyan)
end

local function pair(y, label, value, color)
    line(y, label .. ": " .. tostring(value), color)
end

local function bar(y, label, value, maxValue, color)
    if y > H then return end
    local width = math.max(5, W - #label - 10)
    local ratio = 0
    if maxValue and maxValue > 0 then
        ratio = math.max(0, math.min(1, value / maxValue))
    end
    local filled = math.floor(width * ratio)
    local s = label .. " ["
        .. string.rep("#", filled)
        .. string.rep("-", width - filled)
        .. "] " .. pct(ratio)
    line(y, s, color)
end

local function sample()
    local d = {}
    d.status = call("getStatus")
    d.burn = call("getActualBurnRate")
    d.target = call("getBurnRate")
    d.maxBurn = call("getMaxBurnRate")
    d.heat = call("getHeatingRate")
    d.temp = call("getTemperature")
    d.damage = call("getDamagePercent")
    d.fuel = tank("getFuel")
    d.coolant = tank("getCoolant")
    d.heated = tank("getHeatedCoolant")
    d.waste = tank("getWaste")
    d.fuelPct = call("getFuelFilledPercentage")
    d.coolPct = call("getCoolantFilledPercentage")
    d.heatedPct = call("getHeatedCoolantFilledPercentage")
    d.wastePct = call("getWasteFilledPercentage")
    return d
end

local function safeRate(current, old, dt)
    if old == nil or dt <= 0 then return 0 end
    -- Convert mB per second to mB per game tick.
    return (current - old) / dt / 20
end

local function update()
    local now = os.clock()
    local dt = now - lastTime
    if dt <= 0 then dt = UPDATE end

    local d = sample()

    local waterUse = safeRate(d.coolant, last.coolant, dt)
    local fuelUse = safeRate(d.fuel, last.fuel, dt)
    local wasteMade = safeRate(d.waste, last.waste, dt)
    local steamMade = safeRate(d.heated, last.heated, dt)

    -- Ignore negative changes caused by refilling or emptying tanks.
    waterUse = math.max(0, -waterUse)
    fuelUse = math.max(0, -fuelUse)
    wasteMade = math.max(0, wasteMade)
    steamMade = math.max(0, steamMade)

    local status = d.status == true and "ONLINE"
        or d.status == false and "OFFLINE"
        or "UNKNOWN"
    local statusColor = d.status == true and C.lime
        or d.status == false and C.red or C.yellow

    line(1, " MEKANISM // FISSION REACTOR", C.cyan)
    line(2, string.rep("=", W), C.gray)
    pair(3, "REACTOR STATUS", status, statusColor)
    pair(4, "LAST UPDATE", os.date("%H:%M:%S"), C.lightGray)

    section(6, "CORE")
    pair(7, "Actual burn", fmt(d.burn) .. " mB/t", C.orange)
    pair(8, "Set burn", fmt(d.target) .. " mB/t", C.yellow)
    pair(9, "Max burn", fmt(d.maxBurn) .. " mB/t", C.white)
    pair(10, "Heating rate", fmt(d.heat) .. " mB/t", C.orange)
    pair(11, "Temperature", d.temp and
        (fmt(d.temp) .. " K / " ..
        fmt(d.temp - 273.15) .. " C") or "N/A",
        d.temp and d.temp >= 1200 and C.red or C.white)
    pair(12, "Structural damage", pct(d.damage),
        d.damage and d.damage > 0 and C.red or C.lime)

    section(14, "TANKS")
    pair(15, "Fissile fuel", fmt(d.fuel) .. " mB", C.yellow)
    pair(16, "Water / coolant", fmt(d.coolant) .. " mB", C.cyan)
    pair(17, "Heated coolant", fmt(d.heated) .. " mB", C.orange)
    pair(18, "Nuclear waste", fmt(d.waste) .. " mB", C.purple)

    section(20, "LIVE FLOW ESTIMATES")
    pair(21, "Fuel consumption", fmt(fuelUse) .. " mB/t", C.yellow)
    pair(22, "Water consumption", fmt(waterUse) .. " mB/t", C.cyan)
    pair(23, "Waste production", fmt(wasteMade) .. " mB/t", C.purple)
    pair(24, "Steam / heated output", fmt(steamMade) .. " mB/t", C.orange)

    section(26, "TANK LEVELS")
    bar(27, "Fuel", d.fuelPct or 0, 1, C.yellow)
    bar(28, "Coolant", d.coolPct or 0, 1, C.cyan)
    bar(29, "Heated", d.heatedPct or 0, 1, C.orange)
    bar(30, "Waste", d.wastePct or 0, 1, C.purple)

    line(H, "Refresh: " .. UPDATE .. "s | CTRL+T to stop", C.gray)

    last = {
        coolant = d.coolant,
        fuel = d.fuel,
        waste = d.waste,
        heated = d.heated
    }
    lastTime = now
end

-- Initial sample prevents misleading startup flow rates.
local initial = sample()
last = {
    coolant = initial.coolant,
    fuel = initial.fuel,
    waste = initial.waste,
    heated = initial.heated
}

while true do
    local ok, err = pcall(update)
    if not ok then
        line(H, "READ ERROR: " .. tostring(err), C.red)
    end
    sleep(UPDATE)
end
