--[[    cc10 Remote    ]]--
--[[      by Dog       ]]--
--[[ aka HydrantHunter ]]--
--[[  with assistance  ]]--
--[[   from CometWolf  ]]--
--[[ pastebin mbEHZZnh ]]--
local cc10Rver = "1.1.15"
--[[
Tested with/requires:
  - Mincraft 1.6.4 or 1.7.10
  - ComputerCraft 1.63+
    - 1 Advanced Wireless Pocket Computer
    - cc10host running on a Computer (standard or advanced) with a wireless modem
                       or a Wireless Turtle (standard or advanced)
]]--
-- AUTOMATIC/STATIC CONFIGURATION
-- Default Settings
local netSide, runState, err = "none", "init", "NO"
local kernelState, timerDelay = true, false
local drawCLI, drawColorList, tmpState, thisDevice, pollTimer
local termX, termY = term.getSize()
 -- Device List
local allDevices, filteredDevices = { }, { }
local numPages, pageNum = 1, 1
local uFilter, removal = false, false
local viewDeviceEntry, cc10host, newSettings
 -- Color Definitions
local white = colors.white
local black = colors.black
local silver = colors.lightGray
local gray = colors.gray
local brown = colors.brown
local yellow = colors.yellow
local orange = colors.orange
local red = colors.red
local magenta = colors.magenta
local purple = colors.purple
local blue = colors.blue
local sky = colors.lightBlue
local cyan = colors.cyan
local lime = colors.lime
local green = colors.green
local colorX, word
local curX, curY
-- END AUTOMATIC/STATIC CONFIGURATION

local function filterDeviceList()
  filteredDevices = { }
  local deviceCount = 1
  for i = 1, #allDevices, 1 do
    if uFilter then -- if uFilter is TRUE (gray) then show only non-universal devices
      if allDevices[i].outputType ~= "UA" and allDevices[i].outputType ~= "UD" then
        filteredDevices[deviceCount] = allDevices[i]
        filteredDevices[deviceCount].adList = i
        deviceCount = deviceCount + 1
        if deviceCount > #allDevices then break end
      end
    else            -- otherwise show all devices
      filteredDevices[deviceCount] = allDevices[i]
      filteredDevices[deviceCount].adList = i
      deviceCount = deviceCount + 1
      if deviceCount > #allDevices then break end
    end
  end
  numPages = math.max(1, math.ceil(#filteredDevices / 24))
  if numPages < pageNum then pageNum = numPages end
end

local function sortDeviceList()
  for i = 1, #filteredDevices, 1 do
    if cc10host == filteredDevices[i].cc then    -- we're already on the list
      filteredDevices[i] = newSettings           -- update entry with current state of host
      for j = 1, #allDevices, 1 do
        if allDevices[j].cc == filteredDevices[i].cc then -- find device in allDevices
          allDevices[j] = newSettings            -- update entry with current state of host
          filteredDevices[i].adList = j          -- set allDevices ID for the filtered device
          break
        end
      end
      filterDeviceList()
      if runState == "List" then
        drawCLI()
      elseif runState == "Edit" then
        viewDeviceEntry()
      end
      return
    end
  end
  for i = 1, #allDevices, 1 do
    if cc10host == allDevices[i].cc then         -- we're already on the list
      allDevices[i] = newSettings                -- update entry with current state of host
      filterDeviceList()
      return
    end
  end
  if #allDevices == 0 then
    table.insert(allDevices, newSettings)        -- the list is empty, add this device
    filterDeviceList()
  else
    for i = 1, #allDevices, 1 do
      if newSettings.name < allDevices[i].name then -- alphabetize
        table.insert(allDevices, i, newSettings) -- insert
        filterDeviceList()
        break
      end
      if i >= #allDevices then                   -- we've reached the end of the list
        table.insert(allDevices, newSettings)    -- tack it on
        filterDeviceList()
        break
      end
    end
  end
end

local function netSend(targetHost, cmd1, cmd2)
  local tempSettings = { program = "cc10", cmd = cmd1, action = cmd2, }
  if not rednet.isOpen(netSide) then
    rednet.open(netSide)
  end
  if targetHost == "ALL" then
    rednet.broadcast(tempSettings, "cc10")
  else
    rednet.send(targetHost, tempSettings)
  end
end

local function netReceive()
  while true do
    local id, message = rednet.receive()
    if tostring(message.program) == "cc10host" then
      cc10host = id
      newSettings = message
      sortDeviceList()
    end
  end
end

local function assignColor(devNumber)
  local colorBurst = { P = purple, B = blue, S = sky, G = green, R = red, O = orange, N = brown, I = silver, }
  for k, v in pairs(colorBurst) do
    if filteredDevices[devNumber].color == tostring(k) then
      return v
    end
  end
end

local function assignMasterColor(devNumber)
  local colorBurst = { P = purple, B = blue, S = sky, G = green, R = red, O = orange, N = brown, I = silver, }
  for k, v in pairs(colorBurst) do
    if allDevices[devNumber].color == tostring(k) then
      return v
    end
  end
end

local function assignType(devNumber)
  local cc10Types = { D = "Digital", A = "Analog", UD = "U-Digital", UA = "U-Analog", PS = "Pulsed-S", PL = "Pulsed-L", Z = "Zog" }
  for k, v in pairs(cc10Types) do
    --if filteredDevices[devNumber].outputType == tostring(k) then
    if allDevices[devNumber].outputType == tostring(k) then
      return v
    end
  end
end

local function drawElement(x, y, w, h, txColor, bgColor, text)
  local deText = tostring(text)
  term.setCursorPos(x, y)
  if w == "OFF" then                   --
    term.setBackgroundColor(gray)      --
    term.write("  ")                   -- Switch (OFF)
    term.setBackgroundColor(red)       --
    term.write("  ")                   --
  elseif w == "ON" then                --
    term.setBackgroundColor(green)     --
    term.write("  ")                   -- Switch (ON)
    term.setBackgroundColor(gray)      --
    term.write("  ")                   --
  else
    if bgColor then term.setBackgroundColor(bgColor) end
    if w > #deText or h > 1 then       -- We're 'drawing' something more than text
      for i = 1, h, 1 do               --
        term.write(string.rep(" ", w)) -- Draw the 'element' (box/rectangle/line-seg)
        term.setCursorPos(x, y + i)    --
      end
    end
    if deText ~= "" and deText ~= "nil" then
      if txColor then term.setTextColor(txColor) end
      if w < #deText then w = #deText end -- Ensure minimum length
      local xW = (x + math.floor(w / 2)) - math.floor(#deText / 2) -- Center the text horizontally
      local xH = y + math.floor(h / 2)    -- Center the text vertically
      term.setCursorPos(xW, xH)
      term.write(deText)
    end
  end
end

local function drawHeader()
  local title = runState == "Help" and "cc10  " .. cc10Rver or "cc10"
  drawElement(1, 1, termX, 1, white, blue, title) -- title bar
  if runState == "List" then
    drawElement(1, 1, 1, 1, nil, nil, "[ ]")
    drawElement(2, 1, 1, 1, uFilter and gray or green, nil, "U")
    drawElement(termX - 2, 1, 1, 1, white, nil, "[ ]")
    drawElement(termX - 1, 1, 1, 1, red, nil, "X")
    local word1 = string.rep(" ", math.floor(termX / 4 - 2)) .. "All ON" .. string.rep(" ", math.floor(termX / 4 - 2))
    local word2 = string.rep(" ", math.ceil(termX / 4 - 2)) .. "All OFF" .. string.rep(" ", math.ceil(termX / 4 - 2))
    drawElement(1, 2, 1, 1, green, gray, word1)
    drawElement(termX / 2, 2, 1, 1, red, nil, word2)
  elseif runState == "Edit" or runState == "Help" then
    drawElement(1, 2, termX, 1, red, gray, "Close")
  end
end

do
  local tColors = {
                    [1] = { text = "Blue", color = blue, gRating = "B" },
                    [2] = { text = "Sky", color = sky, gRating = "S" },
                    [3] = { text = "Brown", color = brown, gRating = "N" },
                    [4] = { text = "Purple", color = purple, gRating = "P" },
                    [5] = { text = "Green", color = green, gRating = "G" },
                    [6] = { text = "Orange", color = orange, gRating = "O" },
                    [7] = { text = "Red", color = red, gRating = "R" },
                    [8] = { text = "Silver", color = silver, gRating = "I" },
                  }

  drawColorList = function(gRating)
    local longest = 0                     --
    for k, v in ipairs(tColors) do         --
      longest = math.max(#v.text, longest) -- find longest color name for menu width
    end                                   --
    local posY = 4                        -- cursor Y
    local gRatingCol
    for i = 1, #tColors, 1 do
      local v = tColors[i]
      drawElement(7, posY, 1, 1, nil, v.color, " ") -- color pips
      term.setBackgroundColor(gray)   -- menu b/g
      if gRating == v.gRating then    --
        gRatingCol = v.color          -- Selected text color
        term.setTextColor(gRatingCol) --
      else
        term.setTextColor(silver)     -- Unselected text color
      end
      term.write(" " .. v.text .. string.rep(" ", longest - #v.text) .. " ")
      posY = posY + 1                 -- increment cursor Y pos
    end
    local xColorLine = 7 + longest + 3                                   --
    paintutils.drawLine(xColorLine, 4, xColorLine, posY - 1, gRatingCol) -- draw selected color line
  end
end

viewDeviceEntry = function()
  runState = "Edit"
  drawHeader()
  -- Display data for viewing/editing
  if tonumber(thisDevice) <= #allDevices then  -- <= filteredDevices ?
    local teColor = assignMasterColor(thisDevice)
    if teColor ~= nil then
      -- Name
      drawElement(2, 4, #allDevices[thisDevice].name, 1, teColor, black, allDevices[thisDevice].name)
      -- Power state
      drawElement(12, 4, 1, 1, gray, nil, "Power:")
      if allDevices[thisDevice].state == 0 then
        drawElement(22, 4, "OFF")
      else
        drawElement(22, 4, "ON")
      end
      -- cc#
      local tmpCC = tostring(allDevices[thisDevice].cc)
      drawElement(2, 6, 1, 1, gray, black, "cc#")
      drawElement(6, 6, 1, 1, white, nil, tmpCC)
      -- Restore on restart ON/OFF
      if allDevices[thisDevice].outputType:sub(1, 1) ~= "P" then
        drawElement(12, 6, 1, 1, gray, nil, "Restore:")
        if allDevices[thisDevice].restore then
          drawElement(22, 6, "ON")
        else
          drawElement(22, 6, "OFF")
        end
      else
        drawElement(12, 6, 14, 1, nil, black, "")
      end
      -- Short description
      drawElement(2, 8, #allDevices[thisDevice].note, 1, silver, black, allDevices[thisDevice].note)
      -- Output Type (Digital, Analog, Universal Digital, Universal Analog, Pulsed (short), Pulsed (long))
      drawElement(2, 10, 1, 1, gray, nil, "Output type:")
      local cc10Type = assignType(thisDevice)
      drawElement(15, 10, #cc10Type, 1, silver, nil, cc10Type .. "   ")
      -- Output Level (0-15)
      if allDevices[thisDevice].outputType == "A" or allDevices[thisDevice].outputType == "UA" then
        drawElement(2, 12, 1, 1, gray, nil, "Output Level:")
        drawElement(17, 12, 2, 1, white, nil, allDevices[thisDevice].onState)
        drawElement(2, 13, 1, 1, nil, nil, "|---------------|")
        local opX = allDevices[thisDevice].onState + 2
        if allDevices[thisDevice].onState > 0 then
          drawElement(opX, 13, 1, 1, white, green, "+")
        else
          drawElement(opX, 13, 1, 1, white, red, "+")
        end
      else
        drawElement(2, 12, 17, 2, nil, black, "")
      end
      -- Device location (Export to pgps button)
      if allDevices[thisDevice].loc.x ~= "No GPS Fix" then
        drawElement(14, 17, 9, 1, black, gray, "Export")
        drawElement(14, 18, 9, 1, nil, nil, "to pgps")
      end
      -- Device location (dim/x/y/z)
      drawElement(2, 15, 1, 1, silver, black, "Dim:")
      drawElement(2, 16, 1, 1, nil, nil, "x:")
      drawElement(2, 17, 1, 1, nil, nil, "y:")
      drawElement(2, 18, 1, 1, nil, nil, "z:")
      drawElement(7, 15, 1, 1, sky, nil, allDevices[thisDevice].loc.dim)
      drawElement(5, 16, 1, 1, brown, nil, allDevices[thisDevice].loc.x)
      drawElement(5, 17, 1, 1, nil, nil, allDevices[thisDevice].loc.y)
      drawElement(5, 18, 1, 1, nil, nil, allDevices[thisDevice].loc.z)
    end
  end
end

local function drawNaviUI()
  local pNum = tostring(pageNum)
  if pageNum < 100 then pNum = "0" .. pNum end -- Add a "0" before double digit page numbers
  if pageNum < 10 then pNum = "0" .. pNum end  -- Add another "0" before single digit page numbers
  drawElement((termX / 2) - 10, termY, 7, 1, gray, black, pageNum > 1 and "<< BACK" or "       ")
  drawElement((termX / 2) + 4, termY, 7, 1, nil, nil, pageNum < numPages and "NEXT >>" or "       ")
  drawElement((termX / 2) - (math.floor(pNum:len() / 2)), termY, 3, 1, silver, nil, pNum) -- Page Number
end

local function drawHelpUI()
  drawElement(1, 3, termX, termY, black, white, "")
  drawHeader()
  if tmpState == "List" then
    drawElement(2, 4, 22, 1, black, white, "Left click an entry to")
    drawElement(2, 5, 22, 1, nil, nil, "toggle its state")
    drawElement(2, 7, 23, 1, nil, nil, "Right click an entry to")
    drawElement(2, 8, 25, 1, nil, nil, "view that entry's details")
    drawElement(2, 10, 20, 1, nil, nil, "Middle click an entry to")
    drawElement(2, 11, 20, 1, nil, nil, "lock or unlock")
    drawElement(2, 13, 22, 1, nil, nil, "All ON/All OFF toggles")
    drawElement(2, 14, 22, 1, nil, nil, "all Universals in range")
    drawElement(2, 16, 22, 1, nil, nil, "[U] = Universal Filter")
    drawElement(2, 17, 22, 1, nil, nil, "Filters out all")
    drawElement(2, 18, 22, 1, nil, nil, "universal devices")
  elseif tmpState == "Edit" then
    drawElement(2, 4, 21, 1, black, white, "Restore: restore last")
    drawElement(2, 5, 21, 1, nil, nil, "state after server or")
    drawElement(2, 6, 21, 1, nil, nil, "software restart")
    drawElement(2, 8, 22, 1, nil, nil, "Left click name, note, ")
    drawElement(2, 9, 22, 1, nil, nil, "or dim to edit")
    drawElement(2, 11, 19, 1, nil, nil, "Right click name to")
    drawElement(2, 12, 19, 1, nil, nil, "change color")
    drawElement(2, 14, 21, 1, nil, nil, "Universal Digital and")
    drawElement(2, 15, 21, 1, nil, nil, "Universal Analog both")
    drawElement(2, 16, 21, 1, nil, nil, "respond to All ON/OFF")
  end
end

local function drawMainUI() -- Device 'Address Book'
  local xPos, yPos = 1, 4
  local magicNumber = ((pageNum - 1) * 23) + pageNum
  for i = magicNumber, #filteredDevices do
    term.setCursorPos(xPos, yPos)
    if not filteredDevices[i].lockState then
      if filteredDevices[i].state == 0 then
        term.setBackgroundColor(black)
        term.setTextColor(assignColor(i))
      elseif filteredDevices[i].state > 0 and filteredDevices[i].state < 16 then
        term.setBackgroundColor(assignColor(i))
        term.setTextColor(white)
      elseif filteredDevices[i].state == 16 then
        term.setBackgroundColor(black)
        term.setTextColor(gray)
      end
    else
      term.setBackgroundColor(gray)
      term.setTextColor(silver)
    end
    if i > pageNum * 24 then
      break
    else
      term.write(string.rep(" ", math.floor((8 - #filteredDevices[i].name) / 2)) .. filteredDevices[i].name:sub(1, 8) .. string.rep(" ", math.ceil((8 - #filteredDevices[i].name) / 2))) -- Dial
    end
    yPos = yPos + 2
    if yPos > 19 then yPos = 4 xPos = xPos + 9 end
    if xPos > termX - 7 then break end
  end
  term.setBackgroundColor(black)
end

drawCLI = function()   -- Client Interface 'decider'
  drawHeader()
  if runState == "List" then
    drawMainUI()
    drawNaviUI()
  elseif runState == "Edit" then
    viewDeviceEntry(thisDevice)
  end
end

local function readInput(cX, cY, cO, bG)  -- cursor X, Y, text color, bg color
  local word = ""
  local curX, curY = cX, math.floor(cY)
  bG = bG or black
  term.setTextColor(cO)
  term.setCursorBlink(true)
  term.setCursorPos(curX, curY)
  while true do
    local kEvent = { os.pullEvent() }
    if kEvent[1] == "key" then
      if kEvent[2] == keys.backspace then
        if curX > cX then
          curX = curX - 1
          word = word:sub(1, #word - 1)
        end
        drawElement(curX, curY, 1, 1, cO, bG, " ")
      elseif kEvent[2] == keys.enter then
        term.setCursorBlink(false)
        return word
      end
    elseif kEvent[1] == "char" then
      drawElement(curX, curY, 1, 1, cO, bG, kEvent[2])
   	  word = word .. kEvent[2]
      curX = curX + 1
    elseif kEvent[1] == "mouse_click" then
      local mX, mY = kEvent[3], kEvent[4]
      if mY ~= curY or mX < curX - #word or (mX > curX + 3 or mX > curX - #word + 13) then
        term.setCursorBlink(false)
        --inputting = false
        return word
      end
    elseif kEvent[1] == "timer" and kEvent[2] == pollTimer then
      timerDelay = true
    end
    term.setCursorPos(curX, curY)
  end
end

local function colorPicker()
  local colorCodes = { "B", "S", "N", "P", "G", "O", "R", "I", }
  while true do
    local newColor = { os.pullEvent("mouse_click") }
    if newColor[3] > 6 and newColor[3] < 21 then
      for colorChoice = 4, 11, 1 do
        if newColor[4] == colorChoice then
          for i = 1, #colorCodes, 1 do
            if (colorChoice - 3) == i then
              allDevices[thisDevice].color = colorCodes[i]
              netSend(allDevices[thisDevice].cc, "color", colorCodes[i])
              break
            end
          end
        end
      end
      break
    else
      break
    end
  end
  runState = "Edit"
  drawElement(2, 2, termX, termY, nil, black, "")
  viewDeviceEntry(thisDevice)
end

local function editEntry()
  if runState == "EditName" then
    drawElement(2, 4, #allDevices[thisDevice].name, 1, gray, black, allDevices[thisDevice].name)
    local newName = tostring(readInput(2, 4, yellow))
    if newName ~= "" and newName ~= "nil" then -- and newName:len() < 9 then
      allDevices[thisDevice].name = newName:sub(1, 8)  -- We set this so the screen updates immediately and so we have the 8 character name to send
      netSend(allDevices[thisDevice].cc, "name", allDevices[thisDevice].name)
    end
    local gColor = assignMasterColor(thisDevice)
    local gDev = allDevices[thisDevice].name .. string.rep(" ", 8-#allDevices[thisDevice].name)
    drawElement(2, 4, #gDev, 1, gColor, nil, gDev)
    runState = "Edit"
    return
  elseif runState == "EditNote" then
    drawElement(2, 8, #allDevices[thisDevice].note, 1, gray, black, allDevices[thisDevice].note)
    term.setBackgroundColor(black)
    local newNote = tostring(readInput(2, 8, white))
    if newNote ~= "" and newNote ~= "nil" then
      allDevices[thisDevice].note = newNote  -- We set this so the screen updates immediately
      netSend(allDevices[thisDevice].cc, "note", newNote)
    end
    drawElement(2, 8, termX, 2, nil, nil, "")
    drawElement(2, 8, #allDevices[thisDevice].note, 1, silver, nil, allDevices[thisDevice].note)
    runState = "Edit"
    return
  elseif runState == "EditDim" then
    drawElement(7, 15, #allDevices[thisDevice].loc.dim, 1, gray, black, allDevices[thisDevice].loc.dim)
    local newDim = tostring(readInput(7, 15, sky))
    if newDim ~= "" and tostring(newDim) ~= "nil" then
      allDevices[thisDevice].loc.dim = newDim -- We set this so the screen updates immediately
      drawElement(7, 15, 38, 1, nil, nil, "")
      netSend(allDevices[thisDevice].cc, "locDim", newDim)
    end
    drawElement(7, 15, 5, 1, nil, nil, "")
    drawElement(7, 15, #allDevices[thisDevice].loc.dim, 1, sky, nil, allDevices[thisDevice].loc.dim)
    runState = "Edit"
    return
  end
end

local function enterPassword()
  drawElement((termX / 2) - 7, 8, 15, 1, white, gray, "   Password:   ")
  drawElement((termX / 2) - 7, 9, 15, 2, nil, nil, "")
  drawElement((termX / 2) - 6, 9, 13, 1, nil, black, "") -- input area bg
  local pass = readInput(termX / 2 - 6, 9, lime)
  drawElement((termX / 2) - 7, 8, 15, 3, nil, nil, "")
  netSend(allDevices[thisDevice].cc, runState == "lock" and "lock" or "unlock", pass)
  runState = "List"
  drawMainUI()
end

local function goToPage()
  drawElement((termX / 2) - 7, 8, 15, 1, white, gray, "  Go To Page:  ")
  drawElement((termX / 2) - 7, 9, 15, 2, nil, nil, "")
  drawElement((termX / 2) - 6, 9, 13, 1, nil, black, "") -- input area bg
  local newPage = tonumber(readInput((termX / 2) - 6, 9, lime))
  drawElement((termX / 2) - 7, 8, 15, 3, nil, nil, "")
  pageNum = newPage or pageNum
  pageNum = math.max(1, pageNum)
  pageNum = math.min(pageNum, numPages)
  if pageNum == numPages and numPages > 1 then
    drawElement(2, 4, termX, termY, nil, nil, "")
  end
  runState = "List"
  drawMainUI()
  drawNaviUI()
end

local function keyClick()
  while true do
    local clickyKeys = { os.pullEvent("key") }
    if clickyKeys[2] == 22 then
      if runState == "List" then
        uFilter = not uFilter
        filterDeviceList()
        drawElement(1, 3, termX, termY, nil, black, "")
        drawCLI()
        return
      end
    elseif clickyKeys[2] == 59 then
      if runState ~= "Help" then
        tmpState = runState
        runState = "Help"
        drawHelpUI()
        return
      else
        drawElement(1, 2, termX, termY, nil, black, "")
        runState = tmpState
        if runState == "List" then
          drawCLI()
        elseif runState == "Edit" then
          viewDeviceEntry(thisDevice)
        end
        return
      end
    end
  end
end

local function mClick()
  while true do
    local clickEvent = { os.pullEvent("mouse_click") }
    local mButton = clickEvent[2]
    local mcX = clickEvent[3]
    local mcY = clickEvent[4]
    if runState == "Help" then
      if mcY == 2 then
        drawElement(1, 2, termX, termY, nil, black, "")
        runState = tmpState
        if runState == "List" then
          drawCLI()
        elseif runState == "Edit" then
          viewDeviceEntry(thisDevice)
        end
        return
      end
    elseif runState == "Edit" then
      if mcY == (2) then -- Exit Edit Screen
        drawElement(2, 2, termX, termY, nil, black, "")
        runState = "List"
        drawCLI()
        return
      elseif mcY == 4 then
        if mcX > 1 and mcX < 10 then
          if mButton == 1 then -- Edit Device Name
            runState = "EditName"
            return
          elseif mButton == 2 then -- Change color assignment
            drawColorList(allDevices[thisDevice].color)
            runState = "color"
            return
          end
        elseif mcX > 20 and mcX < 26 then -- Change ON/OFF state
          if allDevices[thisDevice].state == 0 then
            netSend(allDevices[thisDevice].cc, "set", allDevices[thisDevice].onState)
          elseif allDevices[thisDevice].state > 0 and allDevices[thisDevice].state < 16 then
            netSend(allDevices[thisDevice].cc, "set", 0)
          end
          viewDeviceEntry(thisDevice)
        end
      elseif mcY == 6 then
        if mcX > 21 and mcX < 25 and allDevices[thisDevice].outputType:sub(1, 1) ~= "P" then -- Restore state on restart?
          if allDevices[thisDevice].restore then
            netSend(allDevices[thisDevice].cc, "rNO")
          else
            netSend(allDevices[thisDevice].cc, "rYES")
          end
        end
      elseif mcY == 8 then -- Edit Device Note
        if mcX > 1 then
          runState = "EditNote"
          return
        end
      elseif mcY == 10 then -- Edit Output Type (Analog/Digital/Universal A/D)
        if mcX > 14 and mcX < 24 then
          if allDevices[thisDevice].outputType == "A" then
            allDevices[thisDevice].outputType = "UA"
            netSend(allDevices[thisDevice].cc, "AD", "UA")
          elseif allDevices[thisDevice].outputType == "UA" then
            allDevices[thisDevice].outputType = "D"
            allDevices[thisDevice].onState = 15
            netSend(allDevices[thisDevice].cc, "AD", "D")
          elseif allDevices[thisDevice].outputType == "D" then
            allDevices[thisDevice].outputType = "UD"
            allDevices[thisDevice].onState = 15
            netSend(allDevices[thisDevice].cc, "AD", "UD")
          elseif allDevices[thisDevice].outputType == "UD" then
            allDevices[thisDevice].outputType = "PS"
            netSend(allDevices[thisDevice].cc, "AD", "PS")
          elseif allDevices[thisDevice].outputType == "PS" then
            allDevices[thisDevice].outputType = "PL"
            netSend(allDevices[thisDevice].cc, "AD", "PL")
          elseif allDevices[thisDevice].outputType == "PL" then
            allDevices[thisDevice].outputType = "A"
            netSend(allDevices[thisDevice].cc, "AD", "A")
          end
          viewDeviceEntry(thisDevice)
        end
      elseif mcY == 13 then -- Edit Redstone Output Level
        if mcX > 2 and mcX < 19 then
          if allDevices[thisDevice].outputType == "A" or allDevices[thisDevice].outputType == "UA" then
            allDevices[thisDevice].onState = mcX - 2 -- We set this so the slider updates immediately and so we have the correct value to send
            netSend(allDevices[thisDevice].cc, "lvl", allDevices[thisDevice].onState)
            if allDevices[thisDevice].onState == 0 then allDevices[thisDevice].state = 0 end
            viewDeviceEntry(thisDevice)
          end
        end
      elseif mcY == 15 then -- Device Dimension
        if mcX > 6 and mcX < 26 then
          runState = "EditDim"
          return
        end
      elseif mcY == 17 then
        if mcX > 14 and mcX < 23 and allDevices[thisDevice].loc.x ~= "No GPS Fix" then -- Export to pgps
          drawElement(14, 17, 9, 1, white, gray, "Export")
          drawElement(14, 18, 9, 1, nil, nil, "to pgps")
          local pgpsPlaces = fs.open("/.places", fs.exists("/.places") and "a" or "w")
          pgpsPlaces.writeLine(allDevices[thisDevice].loc.x .. ", " .. allDevices[thisDevice].loc.y .. ", " .. allDevices[thisDevice].loc.z .. ", " .. allDevices[thisDevice].name)
          pgpsPlaces.close()
          sleep(0.1)
          drawElement(14, 17, 9, 1, black, nil, "Export")
          drawElement(14, 18, 9, 1, nil, nil, "to pgps")
        end
      elseif mcY == 18 then
        if mcX > 14 and mcX < 23 and allDevices[thisDevice].loc.x ~= "No GPS Fix" then -- Export to pgps
          drawElement(14, 17, 9, 1, white, gray, "Export")
          drawElement(14, 18, 9, 1, nil, nil, "to pgps")
          local pgpsPlaces = fs.open("/.places", fs.exists("/.places") and "a" or "w")
          pgpsPlaces.writeLine(allDevices[thisDevice].loc.x .. ", " .. allDevices[thisDevice].loc.y .. ", " .. allDevices[thisDevice].loc.z .. ", " .. allDevices[thisDevice].name)
          pgpsPlaces.close()
          sleep(0.1)
          drawElement(14, 17, 9, 1, black, nil, "Export")
          drawElement(14, 18, 9, 1, nil, nil, "to pgps")
        end
      end
    elseif runState == "List" then
      if mcY == 1 then
        if mcX > 0 and mcX < 4 then -- Universal filter
          if mButton == 1 or mButton == 2 then
            uFilter = not uFilter
            filterDeviceList()
            drawElement(1, 3, termX, termY, nil, black, "")
            drawCLI()
            return
          elseif mButton == 3 then
            sortDeviceList()
            drawElement(1, 3, termX, termY, nil, black, "")
            drawCLI()
          end
        elseif mcX > termX - 3 and mcX <= termX then -- Exit cc10 Remote
          kernelState = false
          runState = "quit"
          break
        end
      elseif mcY == 2 then -- ALL ON/OFF
        if mcX < termX / 2 then
          netSend("ALL", "set", "ON")
        elseif mcX > termX / 2 then
          netSend("ALL", "set", "OFF")
        end
        return
       -- Switch a listed device on or off, or view it's info
      elseif mcY > 3 and mcY < 19 then
        local magicNumber = ((pageNum - 1) * 23) + pageNum
        local xPos, yPos = 1, 4
        for i = magicNumber, #filteredDevices do
          if mcX >= xPos and mcX < xPos + 8 and mcY == yPos then
            if not filteredDevices[i].lockState then
              if mButton == 1 then
                netSend(filteredDevices[i].cc, "set", filteredDevices[i].state == 0 and filteredDevices[i].onState or 0)
                drawMainUI()
                return
              elseif mButton == 2 then
                term.clear()
                thisDevice = filteredDevices[i].adList
                viewDeviceEntry(thisDevice)
                return
              elseif mButton == 3 then
                thisDevice = filteredDevices[i].adList
                runState = "lock"
                return
              end
            else
              if mButton == 3 then
                thisDevice = filteredDevices[i].adList
                runState = "unlock"
                return
              end
            end
          end
          yPos = yPos + 2
          if yPos > 19 then yPos = 4 xPos = xPos + 9 end
          if xPos > termX - 7 then break end
        end
       -- Page Navigation via click
      elseif mcY == termY then -- Bottom row of screen
        if mcX < termX / 2 - 3 then -- Page Back
          if pageNum > 1 then
            pageNum = pageNum - 1
            drawMainUI()
            drawNaviUI()
            return
          end
        elseif mcX > termX / 2 - 3 and mcX < termX / 2 + 3 then -- Page Numbers (Go To Page dialogue)
          runState = "goPage"
          return
        elseif mcX > termX / 2 + 3 then -- Page Forward
          pageNum = math.min(pageNum + 1, numPages)
          if pageNum == numPages and numPages > 1 then
            drawElement(2, 4, termX, termY, nil, black, "")
          end
          drawMainUI()
          drawNaviUI()
          return
        end
      end
    end
  end
end

local function mScroll()
  while true do
    local scrollEvent = { os.pullEvent("mouse_scroll") }
    if runState == "List" then
      if scrollEvent[2] == -1 and pageNum > 1 then
        pageNum = math.max(1, pageNum - 1)
      elseif scrollEvent[2] == 1 and pageNum < numPages then
        pageNum = math.min(pageNum + 1, numPages)
        if pageNum == numPages and numPages > 1 then
          drawElement(2, 4, termX, termY, nil, black, "")
        end
      end
      drawMainUI()
      drawNaviUI()
    end
  end
end

local function dataPoller()
  while true do
    local timerEvent = { os.pullEvent("timer") }
    if timerEvent[2] == pollTimer then
      if runState == "List" then
        local clCount = 0
        for i = #allDevices, 1, -1 do
          if allDevices[i].quietCount > 1 then
            table.remove(allDevices, i)
            removal = true
          end
        end
        for i = 1, #allDevices, 1 do
          allDevices[i].quietCount = allDevices[i].quietCount + 1
        end
        sortDeviceList()
        if removal then drawElement(1, 4, termX, termY - 1, nil, black, "") drawCLI() removal = false end
      end
      netSend("ALL", "QRY", "cc10QRY")
      pollTimer = os.startTimer(3)
      return
    end
  end
end

local function cc10Kernel()
  if kernelState then
    if runState == "List" or runState == "Edit" or runState == "Help" then
      parallel.waitForAny(keyClick, mClick, mScroll, netReceive, dataPoller)
    elseif runState == "EditName" or runState == "EditNote" or runState == "EditDim" then
      parallel.waitForAny(editEntry, netReceive)
    elseif runState == "lock" or runState == "unlock" then
      parallel.waitForAny(enterPassword, netReceive)
    elseif runState == "goPage" then
      parallel.waitForAny(goToPage, netReceive)
    elseif runState == "color" then
      parallel.waitForAny(colorPicker, netReceive, dataPoller)
    end
  end
  if runState == "quit" then
    kernelState = false
  end
  if kernelState and timerDelay then
    timerDelay = false
    pollTimer = os.startTimer(2)
  end
  return kernelState
end

local function initMe()
  term.setBackgroundColor(black)
  term.clear()
  drawElement(2, 2, 1, 1, white, nil, "Initializing . . .")
  if fs.exists("/data/cc10cfg") then fs.delete("/data/cc10cfg") end
  local ccLabel = os.getComputerLabel()
  if ccLabel == nil or ccLabel == "" then
    os.setComputerLabel("Remote")
  end
  for _, side in ipairs(rs.getSides()) do
    if peripheral.isPresent(side) then
      if peripheral.getType(side) == "modem" then
        if peripheral.call(side, "isWireless") then
          rednet.open(side)
          netSide = side
          break
        end
      end
    end
  end
  if netSide == "none" then
    term.setBackgroundColor(black)
    term.clear()
    drawElement(2, 2, 17, 1, red, nil, "No wireless")
    drawElement(2, 3, 17, 1, nil, nil, "modem detected!")
    drawElement(2, 5, 17, 1, nil, nil, "cc10 REQUIRES")
    drawElement(2, 6, 17, 1, nil, nil, "a wireless modem.")
    term.setCursorPos(1, 9)
    runState = "quit"
    err = "YES"
    return
  end
  kernelState = true
  runState = "List"
  drawElement(2, 4, 1, 1, white, nil, "Searching for hosts . . .")
  netSend("ALL", "QRY", "cc10QRY")
  pollTimer = os.startTimer(2)
  term.clear()
  drawCLI()
end

initMe()

repeat
  cc10Kernel()
  if runState == "quit" then kernelState = false end
  if not kernelState then
    term.setBackgroundColor(black)
    term.setTextColor(white)
    if err == "NO" then
      term.clear()
      term.setCursorPos(1, 1)
    end
    rednet.close(netSide)
    return
  end
until kernelState == false