if not term.isColor() then
  print("Advanced computer required")
  exit()
end
print("loading...")

monitor_textScale = 0.5

Style = {
  CDefault = colors.white,
  BGDefault = colors.blue,

  CTitle = colors.black,
  BGTitle = colors.cyan,

  CWarning = colors.white,
  BGWarning = colors.red,

  CSuccess = colors.white,
  BGSuccess = colors.lime,

  CDisabled = colors.gray,
  BGDisabled = colors.blue,

  CRadarmap = colors.gray,
  BGRadarmap = colors.green,

  CRadarborder = colors.white,
  BGRadarborder = colors.black,

  CRadarself = colors.white,
  BGRadarself = colors.lime,
  TextRadarself = "R",
  
  CRadarother = colors.black,
  BGRadarother = colors.red,
  TextRadarother = "#"
}

----------- Monitor support

function SetMonitorColorFrontBack(frontColor, backgroundColor)
  term.setBackgroundColor(backgroundColor)
  term.setTextColor(frontColor)
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      monitor.setTextColor(frontColor)
      monitor.setBackgroundColor(backgroundColor)
    end
  end
end

function Write(text)
  term.write(text)
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      if key ~= data.radar_monitorIndex then
        monitor.write(text)
      end
    end
  end
end

function SetCursorPos(x, y)
  term.setCursorPos(x, y)
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      if key ~= data.radar_monitorIndex then
        monitor.setCursorPos(x, y)
      end
    end
  end
end

function SetColorDefault()
  SetMonitorColorFrontBack(Style.CDefault, Style.BGDefault)
end

function SetColorTitle()
  SetMonitorColorFrontBack(Style.CTitle, Style.BGTitle)
end

function SetColorWarning()
  SetMonitorColorFrontBack(Style.CWarning, Style.BGWarning)
end

function SetColorSuccess()
  SetMonitorColorFrontBack(Style.CSuccess, Style.BGSuccess)
end

function SetColorDisabled()
  SetMonitorColorFrontBack(Style.CDisabled, Style.BGDisabled)
end

function SetColorRadarmap()
  SetMonitorColorFrontBack(Style.CRadarmap, Style.BGRadarmap)
end

function SetColorRadarborder()
  SetMonitorColorFrontBack(Style.CRadarborder, Style.BGRadarborder)
end

function Clear()
  clearWarningTick = -1
  SetColorDefault()
  term.clear()
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      if key ~= data.radar_monitorIndex then
        monitor.clear()
      end
    end
  end
  SetCursorPos(1, 1)
end

function ClearLine()
  SetColorDefault()
  term.clearLine()
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      if key ~= data.radar_monitorIndex then
        monitor.clearLine()
      end
    end
  end
  SetCursorPos(1, 1)
end

function WriteLn(text)
  Write(text)
  local x, y = term.getCursorPos()
  local width, height = term.getSize()
  if y > height - 1 then
    y = 1
  end
  SetCursorPos(1, y + 1)
end

function WriteCentered(y, text)
  SetCursorPos((51 - text:len()) / 2, y)
  term.write(text)
  if monitors ~= nil then
    for key,monitor in pairs(monitors) do
      if key ~= data.radar_monitorIndex then
        local sizeX, sizeY = monitor.getSize()
        monitor.setCursorPos((sizeX - text:len()) / 2, y)
        monitor.write(text)
      end
    end
  end
  local xt, yt = term.getCursorPos()
  SetCursorPos(1, yt + 1)
end

function ShowTitle(text)
  Clear()
  SetColorTitle()
  WriteCentered(1, text)
  SetColorDefault()
end

function ShowMenu(text)
  Write(text)
  local sizeX, sizeY = term.getSize()
  local xt, yt = term.getCursorPos()
  for i = xt, sizeX do
    Write(" ")
  end
  SetCursorPos(1, yt + 1)
end

local clearWarningTick = -1
function ShowWarning(text)
  local sizeX, sizeY = term.getSize()
  SetCursorPos(1, sizeY)
  ClearLine()
  SetColorWarning()
  SetCursorPos((sizeX - text:len() - 2) / 2, sizeY)
  Write(" " .. text .. " ")
  SetColorDefault()
  clearWarningTick = 5
end
function ClearWarning()
  if clearWarningTick > 0 then
    clearWarningTick = clearWarningTick - 1
  elseif clearWarningTick == 0 then
    SetColorDefault()
    local sizeX, sizeY = term.getSize()
    SetCursorPos(1, sizeY)
    ClearLine()
    clearWarningTick = -1
  end
end

----------- Formatting & popups

function FormatFloat(value, nbchar)
  local str = "?"
  if value ~= nil then
    str = string.format("%g", value)
  end
  if nbchar ~= nil then
    str = string.sub("               " .. str, -nbchar)
  end
  return str
end
function FormatInteger(value, nbchar)
  local str = "?"
  if value ~= nil then
    str = string.format("%d", value)
  end
  if nbchar ~= nil then
    str = string.sub("               " .. str, -nbchar)
  end
  return str
end

function boolToYesNo(bool)
  if bool then
    return "YES"
  else
    return "no"
  end
end

function readInputNumber(currentValue)
  local inputAbort = false
  local input = string.format(currentValue)
  if input == "0" then
    input = ""
  end
  local x, y = term.getCursorPos()
  repeat
    ClearWarning()
    SetColorDefault()
    SetCursorPos(x, y)
    Write(input .. "            ")
    input = string.sub(input, -9)
    
    local params = { os.pullEventRaw() }
    local eventName = params[1]
    local address = params[2]
    if address == nil then address = "none" end
    if eventName == "key" then
      local keycode = params[2]
      if keycode >= 2 and keycode <= 10 then -- 1 to 9
        input = input .. string.format(keycode - 1)
      elseif keycode == 11 or keycode == 82 then -- 0 & keypad 0
        input = input .. "0"
      elseif keycode >= 79 and keycode <= 81 then -- keypad 1 to 3
        input = input .. string.format(keycode - 78)
      elseif keycode >= 75 and keycode <= 77 then -- keypad 4 to 6
        input = input .. string.format(keycode - 71)
      elseif keycode >= 71 and keycode <= 73 then -- keypad 7 to 9
        input = input .. string.format(keycode - 64)
      elseif keycode == 14 then -- Backspace
        input = string.sub(input, 1, string.len(input) - 1)
      elseif keycode == 211 then -- Delete
        input = ""
      elseif keycode == 28 then -- Enter
        inputAbort = true
      elseif keycode == 74 or keycode == 12 or keycode == 49 then -- - on numeric keypad or - on US top or n letter
        if string.sub(input, 1, 1) == "-" then
          input = string.sub(input, 2)
        else
          input = "-" .. input
        end
      elseif keycode == 78 then -- +
        if string.sub(input, 1, 1) == "-" then
          input = string.sub(input, 2)
        end
      else
        ShowWarning("Key " .. keycode .. " is invalid")
      end
    elseif eventName == "terminate" then
      inputAbort = true
    elseif not common_event(eventName, params[2]) then
      ShowWarning("Event '" .. eventName .. "', " .. address .. " is unsupported")
    end
  until inputAbort
  SetCursorPos(1, y + 1)
  if input == "" or input == "-" then
    return currentValue
  else
    return tonumber(input)
  end
end

function readInputText(currentValue)
  local inputAbort = false
  local input = string.format(currentValue)
  local x, y = term.getCursorPos()
  Write(input)
  os.pullEventRaw() -- skip first char event
  repeat
    ClearWarning()
    SetColorDefault()
    SetCursorPos(x, y)
    Write(input .. "                              ")
    input = string.sub(input, -30)
    
    local params = { os.pullEventRaw() }
    local eventName = params[1]
    local address = params[2]
    if address == nil then address = "none" end
    if eventName == "key" then
      local keycode = params[2]
      if keycode == 14 then -- Backspace
        input = string.sub(input, 1, string.len(input) - 1)
      elseif keycode == 211 then -- Delete
        input = ""
      elseif keycode == 28 then -- Enter
        inputAbort = true
      else
        ShowWarning("Key " .. keycode .. " is invalid")
      end
    elseif eventName == "char" then
      local char = params[2]
      if char >= ' ' and char <= '~' then -- 1 to 9
        input = input .. char
      else
        ShowWarning("Char #" .. string.byte(char) .. " is invalid")
      end
    elseif eventName == "terminate" then
      inputAbort = true
    elseif not common_event(eventName, params[2]) then
      ShowWarning("Event '" .. eventName .. "', " .. address .. " is unsupported")
    end
  until inputAbort
  SetCursorPos(1, y + 1)
  if input == "" then
    return currentValue
  else
    return input
  end
end

function readConfirmation(msg)
  if msg == nil then
    ShowWarning("Are you sure? (y/n)")
  else
    ShowWarning(msg)
  end
  repeat
    local params = { os.pullEventRaw() }
    local eventName = params[1]
    local address = params[2]
    if address == nil then address = "none" end
    if eventName == "key" then
      local keycode = params[2]
      if keycode == 21 then -- Y
        return true
      else
        return false
      end
    elseif eventName == "terminate" then
      return false
    elseif not common_event(eventName, params[2]) then
      ShowWarning("Event '" .. eventName .. "', " .. address .. " is unsupported")
    end
  until false
end

----------- commons: menu, event handlers, etc.

function common_event(eventName, param)
  if eventName == "redstone" then
    redstone_event(param)
  elseif eventName == "timer" then
    if param == radar_timerId then
      radar_timerEvent()
    end
  elseif eventName == "shipCoreCooldownDone" then
    ShowWarning("Ship core cooldown done")
  elseif eventName == "reactorPulse" then
    reactor_pulse(param)
--  elseif eventName == "reactorDeactivation" then
--    ShowWarning("Reactor deactivated")
--  elseif eventName == "reactorActivation" then
--    ShowWarning("Reactor activated")
  elseif eventName == "char" then
  elseif eventName == "key_up" then
  elseif eventName == "mouse_click" then
  elseif eventName == "mouse_up" then
  elseif eventName == "mouse_drag" then
  elseif eventName == "monitor_touch" then
  elseif eventName == "monitor_resize" then
  elseif eventName == "peripheral" then
  elseif eventName == "peripheral_detach" then
  else
    return false
  end
  return true
end

function menu_common()
  SetCursorPos(1, 18)
  SetColorTitle()
  ShowMenu("1 Reactor, 2 Cloak, 3 Mining, 4 Core, 5 Radar, eXit")
end

----------- Redstone support

local tblRedstoneState = {-- Remember redstone state on each side
  ["top"] = rs.getInput("top"),
  ["front"] = rs.getInput("front"),
  ["left"] = rs.getInput("left"),
  ["right"] = rs.getInput("right"),
  ["back"] = rs.getInput("back"),
  ["bottom"] = rs.getInput("bottom"),
}
local tblSides = {-- list all sides and offset coordinates
  ["top"   ] = { 3, 1},
  ["front" ] = { 1, 3},
  ["left"  ] = { 3, 3},
  ["right" ] = { 5, 3},
  ["back"  ] = { 5, 5},
  ["bottom"] = { 3, 5},
}

function redstone_event()
  -- Event only returns nil so we need to check sides manually
  local message = ""
  for side, state in pairs(tblRedstoneState) do
    if rs.getInput(side) ~= state then
      -- print(side .. " is now " .. tostring(rs.getInput(side)))
      message = message .. side .. " "
      tblRedstoneState[side] = rs.getInput(side)
    end
  end
  if message ~= "" then
    message = "Redstone changed on " .. message
    showWarning(message)
  end
end

----------- Cloaking support

cloaking_highTier = false
cloaking_currentKey = 1
function cloaking_key(keycode)
  if keycode == 31 then -- S
    cloaking_start()
    return true
  elseif keycode == 25 then -- P
    cloaking_stop()
    return true
  elseif keycode == 20 then -- T
    cloaking_highTier = not cloaking_highTier
    return true
  end
  return false
end

function cloaking_page()
  ShowTitle(label .. " - Cloaking status")

  local cloakingcore = nil
  if cloakingcores ~= nil then
    if cloaking_currentKey > #cloakingcores then
      cloaking_currentKey = 1
    end
    cloakingcore = cloakingcores[cloaking_currentKey]
  end
  
  SetCursorPos(1, 2)
  if #cloakingcores == 0 then
    SetColorDisabled()
    Write("No cloaking core detected...")
  elseif cloakingcore == nil then
    SetColorWarning()
    Write("Cloaking core " .. cloaking_currentKey .. " of " .. #cloakingcores .. " is invalid")
  else
    SetColorDefault()
    Write("Cloaking core " .. cloaking_currentKey .. " of " .. #cloakingcores)
    local isAssemblyValid = cloakingcore.isAssemblyValid()
    local energy, energyMax = cloakingcore.energy()
    local isEnabled = cloakingcore.enable()
    
    if not isAssemblyValid then
      SetColorWarning()
      SetCursorPos(1, 3)
      Write("Invalid assembly!")
      SetColorDefault()
      SetCursorPos(1, 4)
      print("In each direction, you need to place exactly 2 Cloaking device coils, for a total of 12 coils.")
      print("The 6 inner coils shall be exactly one block away from the core.")
      print("The cloaking field will extend 5 blocks past the outer 6 coils.")
      print("Power consumption scales with the amount of cloaked blocks.")
    else
      SetCursorPos(1, 4)
      Write("Assembly is valid")
      
      if energy < 50000 then
        SetColorWarning()
      else
        SetColorDefault()
      end
      SetCursorPos(1, 6)
      Write("Energy level is " .. energy .. " EU")

      SetCursorPos(1, 8)
      if isEnabled then
        if energy <= 100 then
          SetColorWarning()
        else
          SetColorSuccess()
        end
        Write("Cloak is enabled")
      else
        SetColorNormal()
        Write("Cloak is disabled")
      end
    end
  end
  os.sleep(0.1)
  cloaking_currentKey = cloaking_currentKey + 1
  
  SetColorDefault()
  SetCursorPos(1, 12)
  Write("  -----------------------------------------------")
  SetCursorPos(1, 13)
  if cloaking_highTier then
    Write("Cloak tier: HIGH")
  else
    Write("Cloak tier: low")
  end

  SetColorTitle()
  SetCursorPos(1, 16)
  ShowMenu("S - Start cloaking, P - stoP cloaking")
  SetCursorPos(1, 17)
  ShowMenu("T - change low/high Tier")
end

function cloaking_start()
  for key,cloakingcore in pairs(cloakingcores) do
    cloakingcore.enable(false)
    if cloaking_highTier then
      cloakingcore.tier(2)
    else
      cloakingcore.tier(1)
    end
    cloakingcore.enable(true)
  end
end

function cloaking_stop()
  for key,cloakingcore in pairs(cloakingcores) do
    cloakingcore.enable(false)
  end
end

----------- Mining lasers support

mining_currentKey = 1
mining_layerOffset = 1
mining_mineAll = true
mining_useDeuterium = false
function mining_key(keycode)
  if keycode == 31 then -- S
    mining_start()
    return true
  elseif keycode == 25 then -- P
    mining_stop()
    return true
  elseif keycode == 30 then -- A
    mining_mineAll = not mining_mineAll
    return true
  elseif keycode == 32 then -- D
    mining_useDeuterium = not mining_useDeuterium
    return true
  elseif keycode == 74 then -- -
    mining_layerOffset = mining_layerOffset - 1
    if mining_layerOffset < 1 then
      mining_layerOffset = 1
    end
    return true
  elseif keycode == 78 then -- +
    mining_layerOffset = mining_layerOffset + 1
    return true
  elseif keycode == 46 then -- C
    mining_page_config()
    return true
  end
  return false
end

function mining_page()
  ShowTitle(label .. " - Mining status")

  local mininglaser = nil
  if mininglasers ~= nil then
    if mining_currentKey > #mininglasers then
      mining_currentKey = 1
    end
    mininglaser = mininglasers[mining_currentKey]
  end
  
  SetCursorPos(1, 2)
  if #mininglasers == 0 then
    SetColorDisabled()
    Write("No mining laser detected...")
  elseif mininglaser == nil then
    SetColorWarning()
    Write("Mining laser " .. mining_currentKey .. " of " .. #mininglasers .. " is invalid")
  else
    SetColorDefault()
    Write("Mining laser " .. mining_currentKey .. " of " .. #mininglasers)
    local status, energy, currentLayer, mined, total = mininglaser.state()
    SetCursorPos(1, 3)
    Write("Status: " .. status .. "   ")
    SetCursorPos(1, 5)
    Write("Energy level is " .. energy .. " EU")
    SetCursorPos(1, 7)
    Write("Mined " .. mined .. " out of " .. total .. " blocks at layer " .. currentLayer .. "   ")
  end
  os.sleep(0.1)
  mining_currentKey = mining_currentKey + 1
  
  SetColorDefault()
  SetCursorPos(1, 11)
  Write("  -----------------------------------------------")
  SetCursorPos(1, 12)
  Write("Layer offset: " .. mining_layerOffset)
  SetCursorPos(1, 13)
  Write("Mine all: " .. boolToYesNo(mining_mineAll))
  SetCursorPos(1, 14)
  Write("Use Deuterium: " .. boolToYesNo(mining_useDeuterium))

  SetColorTitle()
  SetCursorPos(1, 16)
  ShowMenu("S - Start mining, P - stoP mining, A - mine All")
  SetCursorPos(1, 17)
  ShowMenu("D - use Deuterium, +/-/C - adjust offset")
end

function mining_page_config()
  ShowTitle(label .. " - Mining configuration")
  Write(" Layer offset (".. mining_layerOffset ..") : ")
  mining_layerOffset = readInputNumber(mining_layerOffset)
  if mining_layerOffset < 1 then
    mining_layerOffset = 1
  end
end

function mining_start()
  for key,mininglaser in pairs(mininglasers) do
    if not mininglaser.isMining() then
      mininglaser.offset(mining_layerOffset)
      if mining_mineAll then
        if mining_useDeuterium then
          mininglaser.quarry(mining_useDeuterium)
        else
          mininglaser.quarry()
        end
      else
        if mining_useDeuterium then
          mininglaser.mine(mining_useDeuterium)
        else
          mininglaser.mine()
        end
      end
    end
  end
end

function mining_stop()
  if #mininglasers == 0 then
    SetColorWarning()
    Write("No mining laser detected")
  else
    for key,mininglaser in pairs(mininglasers) do
      SetCursorPos(1, 2 + key)
      if not mininglaser.isMining() then
        SetColorDisabled()
        Write("Mining laser " .. key .. " of " .. #mininglasers .. " is already stopped")
      else
        mininglaser.stop()
        SetColorSuccess()
        Write("Mining laser " .. key .. " of " .. #mininglasers .. " has been stopped")
      end
    end
  end
end

----------- Configuration

function data_save()
  local file = fs.open("shipdata.txt", "w")
  if file ~= nil then
    file.writeLine(textutils.serialize(data))
    file.close()
  else
    ShowWarning("No file system")
    os.sleep(3)
  end
end

function data_read()
  data = { }
  if fs.exists("shipdata.txt") then
    local file = fs.open("shipdata.txt", "r")
    data = textutils.unserialize(file.readAll())
    file.close()
	if data == nil then data = {}; end
  end
  if data.core_summon == nil then data.core_summon = false; end
  if data.reactor_mode == nil then data.reactor_mode = 0; end
  if data.reactor_rate == nil then data.reactor_rate = 100; end
  if data.reactor_targetStability == nil then data.reactor_targetStability = 50; end
  if data.reactor_laserAmount == nil then data.reactor_laserAmount = 10000; end
  if data.radar_monitorIndex == nil then data.radar_monitorIndex = 0; end
  if data.radar_radius == nil then data.radar_radius = 500; end
  if data.radar_autoscan == nil then data.radar_autoscan = false; end
  if data.radar_autoscanDelay == nil then data.radar_autoscanDelay = 3; end
  if data.radar_results == nil then data.radar_results = {}; end
  if data.radar_scale == nil then data.radar_scale = 500; end
  if data.radar_offsetX == nil then data.radar_offsetX = 0; end
  if data.radar_offsetY == nil then data.radar_offsetY = 0; end
end

function data_setName()
  if ship ~= nil then
    ShowTitle("<==== Set ship name ====>")
  else
    ShowTitle("<==== Set name ====>")
  end
  
  SetCursorPos(1, 2)
  Write("Enter ship name: ")
  label = readInputText(label)
  os.setComputerLabel(label)
  if ship ~= nil then
    ship.coreFrequency(label)
  end
  os.reboot()
end

function string_split(source, sep)
  local sep = sep or ":"
  local fields = {}
  local pattern = string.format("([^%s]+)", sep)
  source:gsub(pattern, function(c) fields[#fields + 1] = c end)
  return fields
end

----------- Ship support

core_front = 0
core_right = 0
core_up = 0
core_back = 0
core_left = 0
core_down = 0
core_isInHyper = false
core_jumpCost = 0
core_shipSize = 0
core_movement = { 0, 0, 0 }
core_rotationSteps = 0

function core_boot()
  if ship == nil then
    return
  end
  
  Write("Booting Ship Core")
  
  if data.core_summon then
    ship.summon_all()
  end
  
  WriteLn("...")
  core_front, core_right, core_up = ship.dim_positive()
  core_back, core_left, core_down = ship.dim_negative()
  core_isInHyper = ship.isInHyperspace()
  core_rotationSteps = ship.rotationSteps()
  core_movement = { ship.movement() }
  if ship.direction ~= nil then
    ship.direction(666)
    ship.distance(0)
  end
  WriteLn("Ship core detected...")
  
  repeat
    pos = ship.position()
    os.sleep(0.3)
  until pos ~= nil
  X, Y, Z = ship.position()
  WriteLn("Ship position triangulated...")
  
  repeat
    isAttached = ship.isAttached()
    os.sleep(0.3)
  until isAttached ~= false
  WriteLn("Ship core linked...")
  
  repeat
    core_shipSize = ship.getShipSize()
    os.sleep(0.3)
  until core_shipSize ~= nil
  WriteLn("Ship size updated...")
  
  ship.mode(1)
end

function core_writeMovement()
  local message = " Movement         = "
  local count = 0
  if core_movement[1] > 0 then
    message = message .. core_movement[1] .. " front"
    count = count + 1
  elseif core_movement[1] < 0 then
    message = message .. (- core_movement[1]) .. " back"
    count = count + 1
  end
  if core_movement[2] > 0 then
    if count > 0 then message = message .. ", "; end
    message = message .. core_movement[2] .. " up"
    count = count + 1
  elseif core_movement[2] < 0 then
    if count > 0 then message = message .. ", "; end
    message = message .. (- core_movement[2]) .. " down"
    count = count + 1
  end
  if core_movement[3] > 0 then
    if count > 0 then message = message .. ", "; end
    message = message .. core_movement[3] .. " right"
    count = count + 1
  elseif core_movement[3] < 0 then
    if count > 0 then message = message .. ", "; end
    message = message .. (- core_movement[3]) .. " left"
    count = count + 1
  end
  
  if core_rotationSteps == 1 then
    if count > 0 then message = message .. ", "; end
    message = message .. "Turn right"
    count = count + 1
  elseif core_rotationSteps == 2 then
    if count > 0 then message = message .. ", "; end
    message = message .. "Turn back"
    count = count + 1
  elseif core_rotationSteps == 3 then
    if count > 0 then message = message .. ", "; end
    message = message .. "Turn left"
    count = count + 1
  end
  
  if count == 0 then
    message = message .. "(none)"
  end
  WriteLn(message)
end

function core_writeRotation()
  if core_rotationSteps == 0 then
    WriteLn(" Rotation         = Front")
  elseif core_rotationSteps == 1 then
    WriteLn(" Rotation         = Right +90")
  elseif core_rotationSteps == 2 then
    WriteLn(" Rotation         = Back 180")
  elseif core_rotationSteps == 3 then
    WriteLn(" Rotation         = Left -90")
  end
end

function core_computeNewCoordinates(cx, cy, cz)
  local res = { x = cx, y = cy, z = cz }
  local dx, dy, dz = ship.getOrientation()
  local worldMovement = { x = 0, y = 0, z = 0 }
  worldMovement.x = dx * core_movement[1] - dz * core_movement[3]
  worldMovement.y = core_movement[2]
  worldMovement.z = dz * core_movement[1] + dx * core_movement[3]
  core_actualDistance = math.ceil(math.sqrt(worldMovement.x * worldMovement.x + worldMovement.y * worldMovement.y + worldMovement.z * worldMovement.z))
  core_jumpCost = ship.getEnergyRequired(core_actualDistance)
  res.x = res.x + worldMovement.x
  res.y = res.y + worldMovement.y
  res.z = res.z + worldMovement.z
  return res
end

function core_warp()
  -- rs.setOutput(alarm_side, true)
  if readConfirmation() then
    -- rs.setOutput(alarm_side, false)
    ship.movement(core_movement[1], core_movement[2], core_movement[3])
    ship.rotationSteps(core_rotationSteps)
    ship.mode(1)
    ship.jump()
    -- ship = nil
  end
  -- rs.setOutput(alarm_side, false)
end

function core_page_setMovement()
  ShowTitle("<==== Set movement ====>")
  SetCursorPos(1, 15)
  SetColorTitle()
  ShowMenu("Enter 0 to keep position on that axis")
  ShowMenu("Use - or n keys to move in opposite direction")
  ShowMenu("Press Enter to confirm")
  SetColorDefault()
  SetCursorPos(1, 3)
  
  core_movement[1] = core_page_setDistanceAxis(2, "Forward" , "Front", "Back", core_movement[1], math.abs(core_front + core_back + 1))
  core_movement[2] = core_page_setDistanceAxis(4, "Vertical", "Up"   , "Down", core_movement[2], math.abs(core_up + core_down + 1))
  core_movement[3] = core_page_setDistanceAxis(6, "Lateral" , "Right", "Left", core_movement[3], math.abs(core_left + core_right + 1))
  core_movement = { ship.movement(core_movement[1], core_movement[2], core_movement[3]) }
end

function core_page_setDistanceAxis(line, axis, positive, negative, userEntry, shipLength)
  local maximumDistance = shipLength + 127
  if core_isInHyper and line ~= 3 then
    maximumDistance = shipLength + 127 * 100
  end
  SetColorDisabled()
  SetCursorPos(3, line + 1)
  Write(positive .. " is " .. ( shipLength + 1) .. ", maximum is " ..  maximumDistance .. "      ")
  SetCursorPos(3, line + 2)
  Write(negative .. " is " .. (-shipLength - 1) .. ", maximum is " .. -maximumDistance .. "      ")
  
  SetColorDefault()
  repeat
    SetCursorPos(1, line)
    Write(axis .. " movement: ")
    userEntry = readInputNumber(userEntry)
    if userEntry ~= 0 and (math.abs(userEntry) <= shipLength or math.abs(userEntry) > maximumDistance) then
      ShowWarning("Wrong distance. Try again.")
    end
  until userEntry == 0 or (math.abs(userEntry) > shipLength and math.abs(userEntry) <= maximumDistance)
  SetCursorPos(1, line + 1)
  ClearLine()
  SetCursorPos(1, line + 2)
  ClearLine()
  
  return userEntry
end

function core_page_setRotation()
  local inputAbort = false
  local drun = true
  repeat
    ShowTitle("<==== Set rotation ====>")
    core_writeRotation()
    SetCursorPos(1, 16)
    SetColorTitle()
    ShowMenu("Use directional keys")
    ShowMenu("Press Enter to confirm")
    SetColorDefault()
    local params = { os.pullEventRaw() }
    local eventName = params[1]
    local address = params[2]
    if address == nil then address = "none" end
    if eventName == "key" then
      local keycode = params[2]
      if keycode == 200 then
        core_rotationSteps = 0
      elseif keycode == 203 then
        core_rotationSteps = 3
      elseif keycode == 205 then
        core_rotationSteps = 1
      elseif keycode == 208 then
        core_rotationSteps = 2
      elseif keycode == 28 then
        inputAbort = true
      else
        ShowWarning("Key " .. keycode .. " is invalid")
      end
    elseif eventName == "terminate" then
      inputAbort = true
    elseif not common_event(eventName, params[2]) then
      ShowWarning("Event '" .. eventName .. "', " .. address .. " is unsupported")
    end
  until inputAbort
  core_rotationSteps = ship.rotationSteps(core_rotationSteps)
end

function core_page_setDimensions()
  ShowTitle("<==== Set dimensions ====>")
  Write(" Front (".. core_front ..") : ")
  core_front = readInputNumber(core_front)
  Write(" Right (".. core_right ..") : ")
  core_right = readInputNumber(core_right)
  Write(" Up    (".. core_up ..") : ")
  core_up = readInputNumber(core_up)
  Write(" Back  (".. core_back ..") : ")
  core_back = readInputNumber(core_back)
  Write(" Left  (".. core_left ..") : ")
  core_left = readInputNumber(core_left)
  Write(" Down  (".. core_down ..") : ")
  core_down = readInputNumber(core_down)
  Write("Setting dimensions...")
  core_front, core_right, core_up = ship.dim_positive(core_front, core_right, core_up)
  core_back, core_left, core_down = ship.dim_negative(core_back, core_left, core_down)
  core_shipSize = ship.getShipSize()
  if core_shipSize == nil then core_shipSize = 0 end
end

function core_page_summon()
  ShowTitle("<==== Summon players ====>")
  local playersString = ship.getAttachedPlayers()
  if playersString == "" then
    WriteLn("~ no players registered ~")
    WriteLn("")
    SetColorTitle()
    ShowMenu("Press enter to exit")
    SetColorDefault()
    readInputNumber("")
    return
  end
  local playersArray = string_split(playersString, ",")
  for i = 1, #playersArray do
    WriteLn(i .. ". " .. playersArray[i])
  end
  SetColorTitle()
  ShowMenu("Enter player number")
  ShowMenu("or press enter to summon everyone")
  SetColorDefault()
  
  Write(":")
  local input = readInputNumber("")
  if input == "" then
    ship.summon_all()
  else
    input = tonumber(input)
    ship.summon(input - 1)
  end
end

function core_page_jumpToBeacon()
  ShowTitle("<==== Jump to beacon ====>")
  
  Write("Enter beacon frequency: ")
  local freq = readInputText("")
  -- rs.setOutput(alarm_side, true)
  if readConfirmation() then
    -- rs.setOutput(alarm_side, false)
    ship.mode(4)
    ship.beaconFrequency(freq)
    ship.jump()
    -- ship = nil
  end
  -- rs.setOutput(alarm_side, false)
end

function core_page_jumpToGate()
  ShowTitle("<==== Jump to Jumpgate ====>")
  
  Write("Enter jumpgate name: ")
  local name = readInputText("")
  -- rs.setOutput(alarm_side, true)
  if readConfirmation() then
    -- rs.setOutput(alarm_side, false)
    ship.mode(6)
    ship.targetJumpgate(name)
    ship.jump()
    -- ship = nil
  end
  -- rs.setOutput(alarm_side, false)
end

function core_page()
  ShowTitle(label .. " - Ship status")
  if ship ~= nil then
    -- WriteLn("")
    X, Y, Z = ship.position()
    WriteLn("Core:")
    WriteLn(" x, y, z          = " .. X .. ", " .. Y .. ", " .. Z)
    local energy, energyMax = ship.energy()
    if energy == nil then energy = 0 end
    if energyMax == nil then energyMax = 1 end
    WriteLn(" Energy           = " .. math.floor(energy / energyMax * 100) .. " % (" .. energy .. "EU)")
    local playersString, playersArray = ship.getAttachedPlayers()
    if playersString == "" then players = "-" end
    WriteLn(" Attached players = " .. playersString)
    -- WriteLn("")
    WriteLn("Dimensions:")
    WriteLn(" Front, Right, Up = " .. FormatInteger(core_front) .. ", " .. FormatInteger(core_right) .. ", " .. FormatInteger(core_up))
    WriteLn(" Back, Left, Down = " .. FormatInteger(core_back) .. ", " .. FormatInteger(core_left) .. ", " .. FormatInteger(core_down))
    WriteLn(" Size             = " .. core_shipSize .. " blocks")
    -- WriteLn("")
    WriteLn("Warp data:")
    core_writeMovement()
    local dest = core_computeNewCoordinates(X, Y, Z)
    WriteLn(" Distance         = " .. core_actualDistance .. " (" .. core_jumpCost .. "EU, " .. math.floor(energy / core_jumpCost) .. " jumps)")
    WriteLn(" Dest.coordinates = " .. FormatInteger(dest.x) .. ", " .. FormatInteger(dest.y) .. ", " .. FormatInteger(dest.z))
    if data.core_summon then
      WriteLn(" Summon after     = Yes")
    else
      WriteLn(" Summon after     = No")
    end
  else
    ShowWarning("No ship controller detected")
  end
  
  SetCursorPos(1, 15)
  SetColorTitle()
  ShowMenu("D - Dimensions, N - set ship Name, M - set Movement")
  ShowMenu("J - Jump, G - jump through Gate, B - jump to Beacon")
  ShowMenu("H - Hyperspace, C - summon Crew, T - Toggle summon")
end

function core_key(char, keycode)
  if keycode == 50 then -- M
    core_page_setMovement()
    core_page_setRotation()
    return true
  elseif keycode == 20 then -- T
    if data.core_summon then
      data.core_summon = false
    else
      data.core_summon = true
    end
    data_save()
    return true
  elseif keycode == 32 then -- D
    core_page_setDimensions()
    return true
  elseif keycode == 36 then -- J
    core_warp()
    return true
  elseif keycode == 46 then -- C
    core_page_summon()
    return true
  elseif keycode == 48 then -- B
    core_page_jumpToBeacon()
    return true
  elseif keycode == 34 then -- G
    core_page_jumpToGate()
    return true
  elseif keycode == 35 then -- H
    -- rs.setOutput(alarm_side, true)
    if readConfirmation() then
      -- rs.setOutput(alarm_side, false)
      ship.mode(5)
      ship.jump()
      -- ship = nil
    end
    -- rs.setOutput(alarm_side, false)
    return true
  elseif keycode == 49 then
    data_setName()
    return true
  end
  return false
end

----------- Reactor support

reactor_output = 0

function reactor_boot()
  if reactor ~= nil then
    WriteLn("Booting Reactor...")
    local isActive, strMode, releaseRate = reactor.active()
    if strMode == "OFF" then
      data.reactor_mode = 0
    elseif strMode == "MANUAL" then
      data.reactor_mode = 1
    elseif strMode == "ABOVE" then
      data.reactor_mode = 2
    elseif strMode == "RATE" then
      data.reactor_mode = 3
    else
      data.reactor_mode = 0
    end
  end
end

function reactor_key(char, keycode)
  if char == 83 or char == 115 or keycode == 31 then -- S
    reactor_start()
    return true
  elseif char == 80 or char == 112 or keycode == 25 then -- P
    reactor_stop()
    return true
  elseif char == 76 or char == 108 or keycode == 38 then -- L
    reactor_laser()
    return true
  elseif char == 79 or char == 111 or keycode == 24 then -- O
    data.reactor_mode = (data.reactor_mode + 1) % 4
    reactor_setMode()
    data_save()
    return true
  elseif char == 71 or char == 103 or keycode == 34 then -- G
    data.reactor_rate = data.reactor_rate - 1000
    reactor_setMode()
    data_save()
    return true
  elseif char == 84 or char == 116 or keycode == 20 then -- T
    data.reactor_rate = data.reactor_rate + 1000
    reactor_setMode()
    data_save()
    return true
  elseif char == 74 or char == 106 or keycode == 36 then -- J
    data.reactor_laserAmount = data.reactor_laserAmount - 500
    reactor_setLaser()
    data_save()
    return true
  elseif char == 85 or char == 117 or keycode == 22 then -- U
    data.reactor_laserAmount = data.reactor_laserAmount + 500
    reactor_setLaser()
    data_save()
    return true
  elseif char == 45 or keycode == 74 then -- -
    data.reactor_targetStability = data.reactor_targetStability - 1
    reactor_setTargetStability()
    data_save()
    return true
  elseif char == 43 or keycode == 78 then -- +
    data.reactor_targetStability = data.reactor_targetStability + 1
    reactor_setTargetStability()
    data_save()
    return true
  elseif char == 67 or char == 99 or keycode == 46 then -- C
    reactor_config()
    data_save()
    return true
  end
  return false
end

function reactor_page()
  ShowTitle(label .. " - Reactor status")
  
  SetCursorPos(1, 2)
  if reactor == nil then
    SetColorDisabled()
    Write("Reactor not detected")
  else
    SetColorDefault()
    Write("Reactor stability")
    instabilities = { reactor.instability() }
    for key,instability in pairs(instabilities) do
      SetCursorPos(12, 2 + key)
      stability = math.floor((100.0 - instability) * 10) / 10
      if stability >= data.reactor_targetStability then
        SetColorSuccess()
      else
        SetColorWarning()
      end
      Write(FormatFloat(stability, 5) .. " %")
    end
    
    SetColorDefault()
    local energy = { reactor.energy() }
    SetCursorPos(1, 7)
    Write("Energy   : ")
    if energy[2] ~= nil then
      Write(FormatInteger(energy[1], 10) .. " / " .. energy[2] .. " RF +" .. FormatInteger(reactor_output, 5) .. " RF/t")
    else
      Write("???")
    end
    SetCursorPos(1, 8)
    Write("Outputing: ")
    if energy[3] ~= nil then
      Write(energy[3] .. " RF/t")
    end
    
    SetColorDefault()
    SetCursorPos(1, 9)
    Write("Activated: ")
    isActive = reactor.active()
    if isActive then SetColorSuccess() else SetColorDefault() end
    Write(boolToYesNo(isActive))
  end
  
  if #reactorlasers == 0 then
    SetColorDisabled()
    SetCursorPos(30, 2)
    Write("Lasers not detected")
  else
    SetColorDefault()
    SetCursorPos(30, 2)
    Write("Lasers")
    
    for key,reactorlaser in pairs(reactorlasers) do
      local side = reactorlaser.side
      if side ~= nil then
        side = side % 4
        SetColorDefault()
        SetCursorPos(4, 3 + side)
        Write("Side " .. side .. ":")
        SetCursorPos(30, 3 + side)
        local energy = reactorlaser.wrap.energy()
        if not reactorlaser.wrap.hasReactor() then
          SetColorDisabled()
        elseif energy > 3 * data.reactor_laserAmount then
          SetColorSuccess()
        else
          SetColorWarning()
        end
        Write(FormatInteger(energy, 6))
      end
    end
  end
  
  SetColorDefault()
  SetCursorPos(1, 10)
  Write("  -----------------------------------------------")
  SetCursorPos(1, 11)
  Write("Output mode     : ")
  if data.reactor_mode == 0 then
    SetColorDisabled()
    Write("hold")
  elseif data.reactor_mode == 1 then
    Write("manual/unlimited")
  elseif data.reactor_mode == 2 then
    Write("surplus above " .. data.reactor_rate .. " RF")
  else
    Write("rated at " .. data.reactor_rate .. " RF")
  end
  SetColorDefault()
  SetCursorPos( 1, 12)
  Write("Target stability: " .. data.reactor_targetStability .. "%")
  SetCursorPos(30, 12)
  Write("Laser amount: " .. data.reactor_laserAmount)
  
  SetColorTitle()
  SetCursorPos(1, 14)
  ShowMenu("S - Start reactor, P - Stop reactor, L - Use lasers")
  SetCursorPos(1, 15)
  ShowMenu("O - Output mode, C - Configuration")
  SetCursorPos(1, 16)
  ShowMenu("+/- - Target stability, U/J - Laser amount")
  SetCursorPos(1, 17)
  ShowMenu("G/T - Output rate/threshold")
end

function reactor_setMode()
  if data.reactor_rate < 1 then
    data.reactor_rate = 1
  elseif data.reactor_rate > 100000 then
    data.reactor_rate = 100000
  end
  if reactor ~= nil then
    if data.reactor_mode == 0 then
      reactor.release(false)
    elseif data.reactor_mode == 1 then
      reactor.release(true)
    elseif data.reactor_mode == 2 then
      reactor.releaseAbove(data.reactor_rate)
    else
      reactor.releaseRate(data.reactor_rate)
    end
  end
end

function reactor_setLaser()
  if data.reactor_laserAmount < 1 then
    data.reactor_laserAmount = 1
  elseif data.reactor_laserAmount > 100000 then
    data.reactor_laserAmount = 100000
  end
end

function reactor_setTargetStability()
  if data.reactor_targetStability < 1 then
    data.reactor_targetStability = 1
  elseif data.reactor_targetStability > 100 then
    data.reactor_targetStability = 100
  end
end

function reactor_start()
  if reactor ~= nil then
    reactor_setMode()
    reactor.active(true)
  end
end

function reactor_stop()
  if reactor ~= nil then
    reactor.active(false)
  end
end

function reactor_laser(side)
  for key,reactorlaser in pairs(reactorlasers) do
    if (side == nil) or (reactorlaser.side == side) then
      reactorlaser.wrap.stabilize(data.reactor_laserAmount)
    end
  end
end

local reactor_configPageLoaded = false
function reactor_pulse(output)
  reactor_output = output
  if reactor == nil then
    os.reboot()
  end
  local instabilities = { reactor.instability() }
  for key,instability in pairs(instabilities) do
    local stability = 100.0 - instability
    if stability < data.reactor_targetStability then
      reactor_laser(key - 1)
    end
  end
  if page == reactor_page and (not reactor_configPageLoaded) then
    for key,instability in pairs(instabilities) do
      SetCursorPos(12, 2 + key)
      stability = math.floor((100.0 - instability) * 10) / 10
      if stability >= data.reactor_targetStability then
        SetColorSuccess()
      else
        SetColorWarning()
      end
      Write(FormatFloat(stability, 5) .. " %")
    end
    
    SetColorDefault()
    local energy = { reactor.energy() }
    SetCursorPos(12, 7)
    if energy[2] ~= nil then
      Write(FormatInteger(energy[1], 10))
      SetCursorPos(39, 7)
      Write(FormatInteger(reactor_output, 5))
    else
      Write("???")
    end
    if energy[3] ~= nil then
      SetCursorPos(12, 8)
      Write(energy[3] .. " RF/t    ")
    end
    
    if #reactorlasers ~= 0 then
      for key,reactorlaser in pairs(reactorlasers) do
        local side = reactorlaser.side
        if side ~= nil then
          side = side % 4
          SetCursorPos(30, 3 + side)
          local energy = reactorlaser.wrap.energy()
          if not reactorlaser.wrap.hasReactor() then
            SetColorDisabled()
          elseif energy > 3 * data.reactor_laserAmount then
            SetColorSuccess()
          else
            SetColorWarning()
          end
          Write(FormatInteger(energy, 6))
        end
      end
    end
  end
end

function reactor_config()
  reactor_configPageLoaded = true
  ShowTitle(label .. " - Reactor configuration")
  
  SetCursorPos(1, 2)
  if reactor == nil then
    SetColorDisabled()
    Write("Reactor not detected")
  else
    SetColorDefault()
    SetCursorPos(1, 4)
    Write("Reactor output rate (" .. data.reactor_rate .. " RF): ")
    data.reactor_rate = readInputNumber(data.reactor_rate)
    reactor_setMode()
    SetCursorPos(1, 5)
    Write("Reactor output rate set")
    
    SetCursorPos(1, 7)
    Write("Laser energy level (" .. data.reactor_laserAmount .. "): ")
    data.reactor_laserAmount = readInputNumber(data.reactor_laserAmount)
    reactor_setLaser()
    SetCursorPos(1, 8)
    Write("Laser energy level set")
    
    SetCursorPos(1, 10)
    Write("Reactor target stability (" .. data.reactor_targetStability .. "%): ")
    data.reactor_targetStability = readInputNumber(data.reactor_targetStability)
    reactor_setTargetStability()
    SetCursorPos(1, 11)
    Write("Reactor target stability set")
  end
  reactor_configPageLoaded = false
end

----------- Radar support

radar_listOffset = 0
radar_timerId = -1
radar_timerLength = 1
radar_x = 0
radar_y = 0
radar_z = 0

function radar_boot()
  if radar ~= nil then
    WriteLn("Booting Radar...")
    if data.radar_monitorIndex > 0 then
      radar_x, radar_y, radar_z = radar.pos()
      radar_drawMap()
    end
    if data.radar_autoscan then
      radar_scan()
    end
  end
end

function radar_key(keycode)
  if keycode == 31 then -- S
    data.radar_autoscan = false
    radar_scan()
    return true
  elseif keycode == 30 then -- A
    data.radar_autoscan = true
    radar_scan()
    return true
  elseif keycode == 25 then -- P
    data.radar_autoscan = false
    return true
  elseif keycode == 49 then -- N
    radar_setMonitorIndex(data.radar_monitorIndex + 1)
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 34 then -- G
    radar_setRadius(data.radar_radius - 100)
    data_save()
    return true
  elseif keycode == 20 then -- T
    if data.radar_radius < 100 then
      radar_setRadius(100)
    else
      radar_setRadius(data.radar_radius + 100)
    end
    data_save()
    return true
  elseif keycode == 36 then -- J
    radar_listOffset = math.max(0, radar_listOffset - 3)
    return true
  elseif keycode == 22 then -- U
    radar_listOffset = math.min(#data.radar_results - 1, radar_listOffset + 3)
    return true
  elseif keycode == 74 then -- -
    data.radar_scale = math.min(10000, math.floor(data.radar_scale * 1.2 + 0.5))
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 78 then -- +
    data.radar_scale = math.max(20, math.floor(data.radar_scale * 0.8 + 0.5))
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 199 then -- home
    data.radar_offsetX = 0;
    data.radar_offsetY = 0;
    data.radar_scale = 20
    for i = 0, #data.radar_results - 1 do
      data.radar_scale = math.max(data.radar_scale, math.max(math.abs(radar_x - data.radar_results[i].x), math.abs(radar_z - data.radar_results[i].z)))
    end
    data.radar_scale = math.min(10000, math.floor(data.radar_scale * 1.1 + 0.5))
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 203 then -- left
    data.radar_offsetX = data.radar_offsetX - 0.20 * data.radar_scale;
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 205 then -- right
    data.radar_offsetX = data.radar_offsetX + 0.20 * data.radar_scale;
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 200 then -- up
    data.radar_offsetY = data.radar_offsetY - 0.20 * data.radar_scale;
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 208 then -- down
    data.radar_offsetY = data.radar_offsetY + 0.20 * data.radar_scale;
    data_save()
    radar_drawMap()
    return true
  elseif keycode == 46 then -- C
    radar_page_config()
    data_save()
    return true
  end
  return false
end

function radar_page()
  local radar_resultsPerPage = 9
  
  ShowTitle(label .. " - Radar map")

  SetCursorPos(1, 2)
  if radar == nil then
    SetColorDisabled()
    Write("Radar not detected")
  else
    SetColorDefault()
    if #data.radar_results == 0 then
      Write("No contacts in range...")
    else
      local lastResultShown = radar_listOffset + radar_resultsPerPage - 1
      if lastResultShown >= #data.radar_results then
        lastResultShown = #data.radar_results - 1
      end
      Write("Displaying results " .. (radar_listOffset + 1) .. " to " .. (lastResultShown + 1) .. " of " .. #data.radar_results)
      for i = radar_listOffset, lastResultShown do
        SetCursorPos(4, 3 + i - radar_listOffset)
        if data.radar_results ~= nil then
          Write(FormatInteger(data.radar_results[i].x, 7) .. ", " .. FormatInteger(data.radar_results[i].y, 4) .. ", " .. FormatInteger(data.radar_results[i].z, 7))
          Write(": " .. data.radar_results[i].frequency)
        else
          Write("~nil~")
        end
      end
    end

    SetColorDefault()
    local energy = { radar.energy() }
    SetCursorPos(1, 12)
    Write("Energy: ")
    if energy ~= nil then
      if energy[1] > (data.radar_radius * data.radar_radius) then
        SetColorSuccess()
      else
        SetColorWarning()
      end
      Write(FormatInteger(energy[1], 10) .. " EU")
    else
      SetColorDisabled()
      Write("???")
    end
    SetColorDefault()
    SetCursorPos(1, 13)
    Write("Radius: " .. data.radar_radius)

    SetCursorPos(30, 13)
    Write("Monitor# " .. data.radar_monitorIndex .. "/" .. #monitors)

    SetCursorPos(1, 14)
    Write("Autoscan: ")
    if data.radar_autoscan then SetColorSuccess() else SetColorDefault() end
    Write(boolToYesNo(data.radar_autoscan))

    SetColorDefault()
    SetCursorPos(30, 14)
    Write("Delay " .. data.radar_autoscanDelay .. "s")
  end
  
  SetColorTitle()
  SetCursorPos(1, 15)
  ShowMenu("S - Scan once, A - Autoscan, P - Stop autoscan")
  SetCursorPos(1, 16)
  ShowMenu("T/G - Scan radius, U/J - Scroll list")
  SetCursorPos(1, 17)
  ShowMenu("+/- - Scale, N - Next monitor, C - Configuration")
end

function radar_page_config()
  ShowTitle(label .. " - Radar configuration")
  
  SetCursorPos(1, 2)
  if radar == nil then
    SetColorDisabled()
    Write("No radar detected")
  else
    SetColorDefault()
    SetCursorPos(1, 3)
    Write("Radar scan radius (" .. data.radar_radius .. " blocks): ")
    radar_setRadius(readInputNumber(data.radar_radius))
    
    SetCursorPos(1, 5)
    Write("Autoscan delay in seconds (" .. data.radar_autoscanDelay .. "): ")
    radar_setAutoscanDelay(readInputNumber(data.radar_autoscanDelay))
    
    SetCursorPos(1, 7)
    Write("Output monitor (" .. data.radar_monitorIndex .. "/" .. #monitors .. "): ")
    radar_setMonitorIndex(readInputNumber(data.radar_monitorIndex))
    
    data_save()
  end
end

function radar_setRadius(newRadius)
  if newRadius < 1 then
    data.radar_radius = 1
  elseif newRadius >= 10000 then
    data.radar_radius = 10000
  else
    data.radar_radius = newRadius
  end
end

function radar_setAutoscanDelay(newAutoscanDelay)
  if newAutoscanDelay < 1 then
    data.radar_autoscanDelay = 1
  elseif newAutoscanDelay >= 3600 then  -- 1 hour
    data.radar_autoscanDelay = 3600
  else
    data.radar_autoscanDelay = newAutoscanDelay
  end
end

function radar_setMonitorIndex(newIndex)
  if #monitors == 0 or newIndex < 0 or newIndex > #monitors then
    data.radar_monitorIndex = 0
  else
    data.radar_monitorIndex = newIndex
  end
end

function radar_getMonitor()
  if data.radar_monitorIndex > 0 and data.radar_monitorIndex <= #monitors then
    return monitors[data.radar_monitorIndex]
  else
    return nil
  end
end

function radar_scan()
  local monitor = radar_getMonitor()
  if radar == nil then
    draw_warning(monitor, "No radar")
    return false
  end
  if radar.energy() < (data.radar_radius * data.radar_radius) then
    draw_warning(monitor, "LOW POWER")
    return false
  end
  if radar_timerId ~= -1 and radar.getResultsCount() == -1 then
    draw_warning(monitor, "Already scanning...")
    return false
  end
  radar_timerId = os.startTimer(radar_timerLength)
  
  radar.radius(data.radar_radius)
  if radar.start() then
    draw_warning(monitor, "Scanning...")
  end
  return false
end

function radar_scanDone()
  local numResults = radar.getResultsCount();
  data.radar_results = {}
  if (numResults ~= 0) then
    for i = 0, numResults do
      local success, type, name, x, y, z = radar.getResult(i)
      if success then
        if name == "default" then
          name = "?"
        end
        data.radar_results[i] = { x = x, y = y, z = z, name = name, type = type }
      end
    end
    data.radar_scale = data.radar_radius
  end
  data_save()
  radar_drawMap()
end

function draw_text(monitor, x, y, text, textColor, backgroundColor)
  if monitor == nil then
    term.setCursorPos(x, y)
    if textColor ~= nil then term.setTextColor(textColor) end
    if backgroundColor ~= nil then term.setBackgroundColor(backgroundColor) end
    term.write(text)
    local xt, yt = term.getCursorPos()
    term.setCursorPos(1, yt + 1)
  else
    monitor.setCursorPos(x, y)
    if textColor ~= nil then monitor.setTextColor(textColor) end
    if backgroundColor ~= nil then monitor.setBackgroundColor(backgroundColor) end
    monitor.write(text)
    local xt, yt = monitor.getCursorPos()
    monitor.setCursorPos(1, yt + 1)
  end
end  

function draw_warning(monitor, text)
  local screenWidth, screenHeight
  if monitor == nil then
    screenWidth, screenHeight = term.getSize()
  else
    screenWidth, screenHeight = monitor.getSize()
  end
  local centerX = math.floor(screenWidth / 2);
  local centerY = math.floor(screenHeight / 2);
  local halfWidth = math.ceil(string.len(text) / 2);
  local blank = string.sub("                              ", - (string.len(text) + 2))

  draw_text(monitor, centerX - halfWidth - 1, centerY - 1, blank, colors.white, colors.red);
  draw_text(monitor, centerX - halfWidth - 1, centerY    , " " .. text .. " ", colors.white, colors.red);
  draw_text(monitor, centerX - halfWidth - 1, centerY + 1, blank, colors.white, colors.red);
end

function draw_centeredText(monitor, y, text)
  local screenWidth, screenHeight
  if monitor == nil then
    screenWidth, screenHeight = term.getSize()
  else
    screenWidth, screenHeight = monitor.getSize()
  end
  local x = math.floor(screenWidth / 2 - string.len(text) / 2 + 0.5);

  draw_text(monitor, x, y, text, nil, nil);
end

function radar_drawContact(monitor, contact)
  local screenWidth, screenHeight
  if monitor == nil then
    screenWidth, screenHeight = term.getSize()
  else
    screenWidth, screenHeight = monitor.getSize()
  end
  
  local screenX = (radar_x + data.radar_offsetX - contact.x) / data.radar_scale
  local screenY = (radar_z + data.radar_offsetY - contact.z) / data.radar_scale
  local visible = true
  
  if screenX <= -1 or screenX >= 1 or screenY <= -1 or screenY >= 1 then
    screenX = math.min(1, math.max(-1, screenX))
    screenY = math.min(1, math.max(-1, screenY))
    visible = false
  end
  
  screenX = math.floor(screenX * (screenWidth  - 3) / 2 + ((screenWidth  - 1)  / 2) + 1.5)
  screenY = math.floor(screenY * (screenHeight - 3) / 2 + ((screenHeight - 1) / 2) + 1.5)
  
  if contact.type == "self" then
    draw_text(monitor, screenX, screenY, Style.TextRadarself, Style.CRadarself, Style.BGRadarself)
  else
    draw_text(monitor, screenX, screenY, Style.TextRadarother, Style.CRadarother, Style.BGRadarother)
  end
  if visible then
    local text = contact.name
    screenX = math.min(screenWidth - 1 - string.len(text), math.max(2, math.floor(screenX - string.len(text) / 2 + 0.5)))
    if screenY == (screenHeight - 1) then
      screenY = screenY - 1
    else
      screenY = screenY + 1
    end
    draw_text(monitor, screenX, screenY, text, Style.CRadarother, Style.BGRadarother)
  end
end

function radar_drawMap()
  local screenWidth, screenHeight, x, y
  local monitor = radar_getMonitor()
  -- center area
  SetColorRadarmap()
  if monitor == nil then
    term.clear()
    screenWidth, screenHeight = term.getSize()
  else
    monitor.clear()
    screenWidth, screenHeight = monitor.getSize()
  end
  -- borders
  SetColorRadarborder()
  for x = 1, screenWidth do
    if monitor == nil then
      term.setCursorPos(x, 1)
      term.write(" ")
      term.setCursorPos(x, screenHeight)
      term.write(" ")
    else
      monitor.setCursorPos(x, 1)
      monitor.write(" ")
      monitor.setCursorPos(x, screenHeight)
      monitor.write(" ")
    end
  end
  for y = 2, screenHeight - 1 do
    if monitor == nil then
      term.setCursorPos(1, y)
      term.write(" ")
      term.setCursorPos(screenWidth, y)
      term.write(" ")
    else
      monitor.setCursorPos(1, y)
      monitor.write(" ")
      monitor.setCursorPos(screenWidth, y)
      monitor.write(" ")
    end
  end
  -- title
  local text = label .. " - Radar map"
  if #data.radar_results == 0 then
    text = text .. " (no contacts)"
  else
    text = text .. " (" .. #data.radar_results .. " contacts)"
  end
     draw_centeredText(monitor, 1, text)
  -- status
  local text = "Scan radius: " .. data.radar_radius
  if radar ~= nil then
    text = text .. " | Energy: " .. radar.energy() .. " EU"
  end
  text = text .. " | Scale: " .. data.radar_scale
  draw_centeredText(monitor, screenHeight, text)
  -- results
  SetCursorPos(1, 12)
  radar_drawContact(monitor, {x = radar_x, y = radar_y, z = radar_z, name = "", type = "self"})
  for i = 0, #data.radar_results - 1 do
    radar_drawContact(monitor, data.radar_results[i])
  end
  
  -- restore defaults
  SetColorDefault()
end

radar_waitingNextScan = false
function radar_timerEvent()
  radar_timerId = -1
  if radar_waitingNextScan then
    radar_waitingNextScan = false
    radar_scan() -- will restart timer
  else
    local numResults = radar.getResultsCount();
    if numResults ~= -1 then
      radar_scanDone()
      if data.radar_autoscan then
        radar_waitingNextScan = true
        radar_timerId = os.startTimer(data.radar_autoscanDelay)
      end
    else -- still scanning
      radar_timerId = os.startTimer(radar_timerLength)
    end
  end
end

----------- Boot sequence
math.randomseed(os.time())
label = os.getComputerLabel()
if not label then
  label = "" .. os.getComputerID()
end

-- read configuration
data_read()
Clear()
print("data_read...")

-- initial scanning
monitors = {}
ShowTitle(label .. " - Connecting...")
WriteLn("")

sides = peripheral.getNames()
reactor = nil
mininglasers = {}
reactorlasers = {}
cloakingcores = {}
ship = nil
radar = nil
for key,side in pairs(sides) do
  os.sleep(0)
  Write("Checking " .. side .. " ")
  local componentType = peripheral.getType(side)
  Write(componentType .. " ")
  if componentType == "warpdriveShipController" then
    Write("wrapping!")
    ship = peripheral.wrap(side)
  elseif componentType == "warpdriveEnanReactorCore" then
    Write("wrapping!")
    reactor = peripheral.wrap(side)
  elseif componentType == "warpdriveEnanReactorLaser" then
    Write("wrapping!")
    local wrap = peripheral.wrap(side)
    table.insert(reactorlasers, { side = wrap.side(), wrap = wrap })
  elseif componentType == "warpdriveMiningLaser" then
    WriteLn("Wrapping " .. side)
    table.insert(mininglasers, peripheral.wrap(side))
  elseif componentType == "warpdriveCloakingCore" then
    Write("wrapping!")
    table.insert(cloakingcores, peripheral.wrap(side))
  elseif componentType == "warpdriveRadar" then
    Write("wrapping!")
    radar = peripheral.wrap(side);
  elseif componentType == "monitor" then
    Write("wrapping!")
    lmonitor = peripheral.wrap(side)
    table.insert(monitors, lmonitor)
    lmonitor.setTextScale(monitor_textScale)
  end
  WriteLn("")
end

if not os.getComputerLabel() and (ship ~= nil or reactor ~= nil) then
  data_setName()
end

-- peripherals status
function connections_page()
  ShowTitle(label .. " - Connections")
  
  WriteLn("")
  if #monitors == 0 then
    SetColorDisabled()
    WriteLn("No Monitor detected")
  elseif #monitors == 1 then
    SetColorSuccess()
    WriteLn("1 monitor detected")
  else
    SetColorSuccess()
    WriteLn(#monitors .. " Monitors detected")
  end
  
  if ship == nil then
    SetColorDisabled()
    WriteLn("No ship controller detected")
  else
    SetColorSuccess()
    WriteLn("Ship controller detected")
  end
  
  if reactor == nil then
    SetColorDisabled()
    WriteLn("No Enantiomorphic reactor detected")
  else
    SetColorSuccess()
    WriteLn("Enantiomorphic reactor detected")
  end
  
  if #reactorlasers == 0 then
    SetColorDisabled()
    WriteLn("No reactor stabilisation laser detected")
  elseif #reactorlasers == 1 then
    SetColorSuccess()
    WriteLn("1 reactor stabilisation laser detected")
  else
    SetColorSuccess()
    WriteLn(#reactorlasers .. " reactor stabilisation lasers detected")
  end
  
  if #mininglasers == 0 then
    SetColorDisabled()
    WriteLn("No mining laser detected")
  elseif #mininglasers == 1 then
    SetColorSuccess()
    WriteLn("1 mining laser detected")
  else
    SetColorSuccess()
    WriteLn(#mininglasers .. " mining lasers detected")
  end
  
  if #cloakingcores == 0 then
    SetColorDisabled()
    WriteLn("No cloaking core detected")
  elseif #cloakingcores == 1 then
    SetColorSuccess()
    WriteLn("1 cloaking core detected")
  else
    SetColorSuccess()
    WriteLn(#cloakingcores .. " cloaking cores detected")
  end
  
  if radar == nil then
    SetColorDisabled()
    WriteLn("No radar detected")
  else
    SetColorSuccess()
    WriteLn("Radar detected")
  end
  
  WriteLn("")
  SetColorTitle()
  WriteLn("Please refer to below menu for keyboard controls")
  WriteLn("For example, press 1 to access Reactor page")
end

-- peripheral boot up
Clear()
connections_page()
SetColorDefault()
WriteLn("")
os.sleep(0)
radar_boot()
core_boot()
reactor_boot()
os.sleep(0)

-- main loop
abort = false
refresh = true
page = connections_page
keyHandler = nil
repeat
  ClearWarning()
  if refresh then
    Clear()
    page()
    menu_common()
    refresh = false
  end
  params = { os.pullEventRaw() }
  eventName = params[1]
  address = params[2]
  if address == nil then address = "none" end
  -- WriteLn("...")
  -- WriteLn("Event '" .. eventName .. "', " .. address .. ", " .. params[3] .. ", " .. params[4] .. " received")
  -- os.sleep(0.2)
  if eventName == "key" then
    keycode = params[2]
    if char == 88 or char == 120 or keycode == 45 then -- x for eXit
      os.pullEventRaw()
      abort = true
    elseif char == 48 or keycode == 11 or keycode == 82 then -- 0
      page = connections_page
      keyHandler = nil
      refresh = true
    elseif char == 49 or keycode == 2 or keycode == 79 then -- 1
      page = reactor_page
      keyHandler = reactor_key
      refresh = true
    elseif char == 50 or keycode == 3 or keycode == 80 then -- 2
      page = cloaking_page
      keyHandler = cloaking_key
      refresh = true
    elseif char == 51 or keycode == 4 or keycode == 81 then -- 3
      page = mining_page
      keyHandler = mining_key
      refresh = true
    elseif char == 52 or keycode == 5 or keycode == 75 then -- 4
      page = core_page
      keyHandler = core_key
      refresh = true
    elseif char == 53 or keycode == 6  or keycode == 76 then -- 5
      page = radar_page
      keyHandler = radar_key
      refresh = true
    elseif keyHandler ~= nil and keyHandler(char, keycode) then
      refresh = true
      os.sleep(0)
    elseif char == 0 then -- control chars
      refresh = false
      os.sleep(0)
    else
      ShowWarning("Key " .. keycode .. " is invalid")
      os.sleep(0.2)
    end
  elseif eventName == "reactorPulse" then
    reactor_pulse(params[2])
    refresh = (page == reactor_page)
  elseif eventName == "terminate" then
    abort = true
  elseif not common_event(eventName, params[2]) then
    ShowWarning("Event '" .. eventName .. "', " .. address .. " is unsupported")
    refresh = true
    os.sleep(0.2)
  end
until abort

-- exiting
if data.core_summon then
  data.core_summon = false
  data_save()
end

if ship ~= nil then
  ship.mode(0)
end

-- clear screens on exit
SetMonitorColorFrontBack(colors.white, colors.black)
term.clear()
if monitors ~= nil then
  for key,monitor in pairs(monitors) do
    monitor.clear()
  end
end
SetCursorPos(1, 1)
WriteLn("Program terminated")
WriteLn("Type startup to restart it")
