local component = require("component")
local fs = require("filesystem")
local process = require("process")
local event = require("event")
local keyboard = require("keyboard")
local shell = require("shell")
local term = require("term")
local text = require("text")
local unicode = require("unicode")
local sides = require("sides")
local colors=require("colors")

-- The following code added by Michiyo, to check if:
-- A.) The computer has HTTP access, and
-- B.) if the required files exist to run this program.
-- C.) And to download them if it can.
--#~#~#~#~#~#~#~ CHANGE THE FOLLOWING VARIABLE TO FALSE TO DISABLE AUTOUPDATE CHECKING~#~#~#~#~#~#~#
autoUpdate = true

local cpath = fs.path(shell.resolve(process.running(),nil))
local oldDirectory = os.getenv("PWD");
shell.setWorkingDirectory(cpath)

--Thanks Gopher!
function compareVersions(v1,v2)
  local pat="(%d+)"
  local iter1,iter2=v1:gmatch(pat),v2:gmatch(pat)
  while true do
    local n1,n2=iter1(), iter2()
    n1,n2=n1 and tonumber(n1),n2 and tonumber(n2)
    if n1==nil then
      if n2==nil then
        return 0
      else
        return -1
      end
    elseif n2==nil then
      return 1
    end
    if n1~=n2 then
      return n1-n2
    end
  end
end

  function file_check(file_name)
    local file = fs.open(file_name)
    if file ~= nil then
      file:close()
      return true
    else
      return false
    end
  end

  wget = loadfile("/bin/wget.lua")

  function downloadFile(remotename, filename)
    wget("-fq", "https://raw.github.com/OpenPrograms/Kenny-Programs/master/CompViewer/" .. remotename, fs.concat(cpath, filename))
  end

  function remoteVersion()
    for line in internet.request("https://raw.github.com/OpenPrograms/Kenny-Programs/master/CompViewer/compviewer-version.txt") do
      if line ~= "not found" then
        return text.trim(line)
      else
        return "0"
      end
    end
  end

  function localVersion()
    if not file_check(cpath.."compviewer-version.txt") then
      return "0"
    else
      local f = io.open(cpath.."compviewer-version.txt", "rb")
      local content = f:read("*all")
      f:close()
      return content
    end
  end

  function doUpdate(watdo)
    if (watdo == "update") then
      if autoUpdate == true then
        print("A new version " .. remoteVersion() .. " is available, would you like to download it? Yes/No")
        doTehUpdate = io.read()
        doTehUpdate = string.lower(doTehUpdate)
        if doTehUpdate == "yes" then
          print("Cleaning up previous install")
          fs.remove(cpath .. "default.gss")
          fs.remove(cpath .. "cv.gss")
          fs.remove(cpath .. "gml.lua")
          fs.remove(cpath .. "gfxbuffer.lua")
          fs.remove(cpath .. "colorutils.lua")
          fs.remove(cpath .. "colorutils.lua")
          fs.remove(cpath .. "compviewer-version.txt")
          currFile = process.running()
          fs.remove(currFile)
          if not file_check(cpath .. currFile) then
            print("Downloading CompViewer.lua")
            downloadFile("CompViewer.lua", currFile)
          end
        end
        else print("Skipping update, you will be reminded next time")
        end
      end
      print("Downloading latest versions of required files")
      if not file_check(cpath .. "default.gss") then
        print("Downloading default.gss")
        downloadFile("default.gss","default.gss")
        downloaded = true
      end
      if not file_check(cpath .. "cv.gss") then
        print("Downloading cv.gss")
        downloadFile("cv.gss","cv.gss")
        downloaded = true
      end
      if not file_check(cpath .. "gml.lua") then
        print("Downloading gml.lua")
        downloadFile("gml.lua","gml.lua")
        downloaded = true
      end
      if not file_check(cpath .. "gfxbuffer.lua") then
        print("Downloading gfxbuffer.lua")
        downloadFile("gfxbuffer.lua","gfxbuffer.lua")
        downloaded = true
      end
      if not file_check(cpath .. "colorutils.lua") then
        print("Downloading colorutils.lua")
        downloadFile("colorutils.lua","colorutils.lua")
        downloaded = true
      end
      if not file_check(cpath .. "compviewer-version.txt") then
        print("Downloading compviewer-version.txt")
        downloadFile("compviewer-version.txt","compviewer-version.txt")
      end
      if downloaded == true then
        print("Please run the program again")
        shell.setWorkingDirectory(oldDirectory)
        os.exit()
      end
    end

    if not component.isAvailable("internet") then
      if not file_check(cpath .. "gml.lua") or not file_check(cpath .. "cv.gss") or not file_check(cpath .. "default.gss") or not file_check(cpath .. "gfxbuffer.lua") or not file_check(cpath .. "colorutils.lua") then
        io.stderr:write("You are missing one or more of the required files 'gml.lua', 'colorutils.lua', 'gfxbuffer.lua', 'default.gss', or 'cv.gss' and do not have internet access to download them automaticly!\n")
        return
      end
    else
--We load the internet API here so we don't die on computers without internet cards.
internet = require("internet")
if not file_check(cpath .. "compviewer-version.txt") then
  print("Setting up version cache")
  downloadFile("compviewer-version.txt","compviewer-version.txt")
end
--Check if this is a fresh download, or a update 0 is fresh, > 0 is update.
newVersion = remoteVersion()
if (compareVersions(newVersion, localVersion()) == 0) then
  --doUpdate("fresh")
  elseif(compareVersions(newVersion, localVersion()) > 0) then
    --doUpdate("update")
  end
  --print("Updating Component Info file, One moment please")
  --fs.remove(cpath .. "CompInfo.txt")
  os.sleep(1)
  --downloadFile("CompInfo.txt")
end
--We've checked for gml, and downloaded it if it was available, so we can load gml now.
local gml=require("gml")
--We now return you to the previous code by Kenny.
local function spaces(cnt)
  return string.rep(string.char(32), cnt)
end

local function spChar(letter, cnt)
  return string.rep(unicode.char(letter), cnt)
end

local OC_1 = "   "..spChar(0x2584,3).."  "..spChar(0x2584,4).."  "..spChar(0x2584,5).." "..spChar(0x2584,1).."   "..spChar(0x2584,1).."  "..spChar(0x2584,4).."  "..spChar(0x2584,3).."  "..spChar(0x2584,1).."   "..spChar(0x2584,1).." "..spChar(0x2584,4).."  "..spChar(0x2584,1).."   "..spChar(0x2584,1).." "..spChar(0x2584,5).." "..spChar(0x2584,5).." "..spChar(0x2584,4).."   "..spChar(0x2584,4).."  "
local OC_2 = "  "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."     "..spChar(0x2588,1)..spChar(0x2584,1).."  "..spChar(0x2588,1).." "..spChar(0x2588,1).."     "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1)..spChar(0x2580,1)..spChar(0x2584,1)..spChar(0x2580,1)..spChar(0x2588,1).." "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1).."     "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."  "
local OC_3 = "  "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1)..spChar(0x2580,3).."  "..spChar(0x2588,1)..spChar(0x2580,2).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).." "..spChar(0x2588,1).." "..spChar(0x2588,1).."     "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1)..spChar(0x2580,3).."  "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1)..spChar(0x2580,2).."   "..spChar(0x2588,1)..spChar(0x2580,1)..spChar(0x2588,1)..spChar(0x2580,1).."   "..spChar(0x2580,2)..spChar(0x2584,1).."  "
local OC_4 = "  "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."     "..spChar(0x2588,1).."     "..spChar(0x2588,1).."  "..spChar(0x2588,2).." "..spChar(0x2588,1).."     "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."   "..spChar(0x2588,1).." "..spChar(0x2588,1).."     "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1).."   "..spChar(0x2588,1).."     "..spChar(0x2588,1).."  "..spChar(0x2588,1).."      "..spChar(0x2588,1).."  "
local OC_5 = "   "..spChar(0x2580,3).."  "..spChar(0x2580,1).."     "..spChar(0x2580,5).." "..spChar(0x2580,1).."   "..spChar(0x2580,1).."  "..spChar(0x2580,4).."  "..spChar(0x2580,3).."  "..spChar(0x2580,1).."   "..spChar(0x2580,1).." "..spChar(0x2580,1).."      "..spChar(0x2580,3).."    "..spChar(0x2580,1).."   "..spChar(0x2580,5).." "..spChar(0x2580,1).."   "..spChar(0x2580,1).." "..spChar(0x2580,4).."  "

local gpu = component.gpu
local Tier1 = 1
local Tier2 = 4
local Tier3 = 8

local menuList = {}

local compList = {}
local tmpList = {}
local sentStr = {}
local compLen = 1

--local fname = "CompInfo.txt"
--local filename = shell.resolve(cpath..fname)

local w, h = gpu.getResolution()

local guiRow = 1
local guiWidth = 1
local guiHeight = 1
local guiContentsLabelCol = 1
local guiContentsLabelWidth = 1
local menuDirWidth = 1
local menuDirHeight = 1
local functionsCol = 1
local functionsWidth = 1
local functionsHeight = 1
local infoGuiWidth = 1
local infoGuiHeight = 1
local infoFunctionsLabelWidth = 1
local infoListboxWidth = 1
local infoListboxHeight = 1

local function table_count(tt, item)
  local count
  count = 0
  for ii,xx in pairs(tt) do
    if item == xx then count = count + 1 end
  end
  return count
end

local function table_unique(tt)
  local newtable = {}
  for ii,xx in ipairs(tt) do
    if table_count(newtable, xx) == 0 then
      newtable[#newtable+1] = xx
    end
  end
  return newtable
end

local function isAdvanced()
  return gpu.getDepth()
end

local function setColors(fore, back)
  gpu.setForeground(fore)
  gpu.setBackground(back)
end

local function setCursor(col, row)
  term.setCursor(col, row)
end

local function getSize()
  return gpu.getResolution()
end

local function drawBox(col, row, wid, hgt, fore, back, opt)
  local ul = {0x250C, 0x2554}
  local ur = {0x2510, 0x2557}
  local ll = {0x2514, 0x255A}
  local lr = {0x2518, 0x255D}
  local sl = {0x2502, 0x2551}
  local al = {0x2500, 0x2550}
  setColors(fore, back)
  gpu.set(col, row, unicode.char(ul[opt])..spChar(al[opt], wid - 2)..unicode.char(ur[opt]))
  for a = 1, hgt - 2 do
    gpu.set(col, row + a, unicode.char(sl[opt])..spaces(wid - 2)..unicode.char(sl[opt]))
  end
  gpu.set(col, row + hgt - 2, unicode.char(ll[opt])..spChar(al[opt], wid - 2)..unicode.char(lr[opt]))
  setCursor(col, row)
end

local function printXY(col, row, menuSel)
  gpu.set(col, row, menuSel)
end

local function printDocXY(col, row, menuSel)
  setCursor(col, row)
  print(menuSel)
end


local function centerText(row, msg)
  local w, h = getSize()
  local len = string.len(msg)
  gpu.set((w - len)/2, row, msg)
end

local function centerIntroText(row, msg)
  local msg1 = "   *   * *   * *     **  * *     *   * * * * *   * *   *   *   *     *   * *     "
  local w, h = getSize()
  local len = string.len(msg1)
  gpu.set((w - len)/2, row, msg)
end


local defaultTheme = {        -- Water Theme
  textColor = 0xFFFFFF,
  background = 0x000099,
  introText = 0xFF0000,
  introBackground = 0x000000,
  menuHintText = 0xFFFF00,
  menuHint = 0x000000,
  prompt = 0xBBBB00,
  promptHighlight = 0x000000,
  fancyDots = 0xAAFFCC,
  fancyBackground = 0x113322
  }

local normalTheme = {       -- Water Theme
  textColor = 0xFFFFFF,
  background = 0x000000,
  prompt = 0x000000,
  promptHighlight = 0xFFFFFF,
  }

term.clear()
local w, h = gpu.getResolution()

local function intro()
  local w, h = gpu.getResolution()
  local msg1 = "   *   * *   * *     **  * *     *   * * * * *   * *   *   *   *     *   * *     "
  local len = string.len(msg1) + 1

  drawBox(1, 3, w, h - 3, theme.textColor, theme.background, 2)
  drawBox((w - len)/2 - 2, 5, len + 4, 10, theme.introText, theme.introBackground, 1)

  centerIntroText(6, OC_1)
  centerIntroText(7, OC_2)
  centerIntroText(8, OC_3)
  centerIntroText(9, OC_4)
  centerIntroText(10, OC_5)
  centerText(12, "Component Viewer")
end

local gpuDepth = gpu.getDepth()
if (gpuDepth >= Tier2) then
  theme = defaultTheme
else
  theme = normalTheme
end


if (gpuDepth == Tier3) then
  guiRow = 15
  guiWidth = 80
  guiHeight = 30
  guiContentsLabelCol = 30
  guiContentsLabelWidth = 31
  menuDirWidth = 24
  menuDirHeight = 25
  functionsCol = 30
  functionsWidth = 50
  functionsHeight = 25
  infoGuiWidth = 160
  infoGuiHeight = 50
  infoFunctionsLabelWidth = 50
  infoListboxWidth = 160
  infoListboxHeight = 42
  intro()
elseif (gpuDepth == Tier2) then
  guiRow = "center"
  guiWidth = 50
  guiHeight = 16
  guiContentsLabelCol = 18
  guiContentsLabelWidth = 31
  menuDirWidth = 16
  menuDirHeight = 11
  functionsCol = 20
  functionsWidth = 30
  functionsHeight = 11
  infoGuiWidth = 80
  infoGuiHeight = 25
  infoFunctionsLabelWidth = 30
  infoListboxWidth = 80
  infoListboxHeight = 16
else
  guiRow = "center"
  guiWidth = 50
  guiHeight = 16
  guiContentsLabelCol = 18
  guiContentsLabelWidth = 31
  menuDirWidth = 16
  menuDirHeight = 11
  functionsCol = 20
  functionsWidth = 30
  functionsHeight = 11
  infoGuiWidth = 50
  infoGuiHeight = 16
  infoFunctionsLabelWidth = 30
  infoListboxWidth = 50
  infoListboxHeight = 10
end

local function strripos(s, delim)
  return s:match('^.*()'..delim)
end

local gui=gml.create("center", guiRow, guiWidth, guiHeight)
gui.style=gml.loadStyle("cv.gss")

gui:addLabel(2,1,14,"Component")
local contentsLabel=gui:addLabel(guiContentsLabelCol,1, guiContentsLabelWidth, "contents of")


local function getMenuList()
  menuList = {}
  local tmpName = ""
  for address, name in component.list() do
    table.insert(menuList,name)
    for k, v in pairs(component.proxy(address)) do
      tmpName = name.."."..k
      table.insert(compList,tmpName)
    end
  end
  table.sort(menuList)
end

function loadInfoData(select)
  local lineLen, lineHeight = 0, 0
  local lineCnt = 0
  local optName = "["..string.upper(select).."]"
  local optNameEnd = "["..string.upper(select).."_END]"
  local optNameStart, optNameLast = 0,0
  local w, h = gpu.getResolution()
  local tmpLine = {}
  local tmpStr = ""
  sentStr = {}
  lineLen = w - 8
  local stuffDone = false

  --[[do
    local f = io.open(filename)
      if f then
        for line in f:lines() do
          lineCnt = lineCnt + 1
          if text.trim(line) == optName then
            optNameStart = lineCnt + 1
          end
          if text.trim(line) == optNameEnd then
            optNameLast = lineCnt - 1
          end
          table.insert(tmpLine, line)
        end
      f:close()
    end
  end]]
  do
    local ccomp = component.getPrimary(select)
    if ccomp and optNameStart < 2 then
      for i in pairs(component.methods(ccomp.address)) do
        local cStr = tostring(ccomp[i])
        if cStr and string.len(cStr)>0 then
          if optNameLast < 1 then
            optNameLast = optNameLast + 1
          end
          local ch = ";"
          if not string.find(cStr, ";") then
            ch = "%-%-"
          end
          local ccStr = string.gsub(cStr,"(.*)"..ch..".*",i.." - %1")
          cStr = string.gsub(cStr,".*"..ch.."(.*)","%1")
          table.insert(tmpLine,optNameLast,ccStr)
          optNameLast = optNameLast + 1
          table.insert(tmpLine,optNameLast,cStr)
          optNameLast = optNameLast + 1
          lineCnt = lineCnt + 3
          stuffDone = true
        end
      end
    end
  end
  if optNameStart < 2 and not stuffDone then
    table.insert(sentStr, "No information available for "..select)
  else
    table.insert(sentStr, tostring(select))
  end
  if lineCnt > 2 then
    local tStr = ""
    for k, v in pairs(tmpLine) do
      if k >= optNameStart and k <= optNameLast then
          v = text.trim(v)
          local dspace = 6
          if string.find(v,"function%(.*%)") then
            dspace = 3
          end
        if string.len(v) > lineLen then
          while string.len(v) > lineLen do
            v = spaces(dspace)..v
            tmpStr = string.sub(v, 1, lineLen)
            if string.len(tmpStr) < lineLen then
              tStr = string.sub(tmpStr, 1, string.len(tmpStr) - 1)
            else
              delimPos = strripos(tmpStr, " ")
              tStr = string.sub(tmpStr, 1, delimPos - 1)
            end
            table.insert(sentStr, tStr)
            v = string.sub(v, delimPos + 1)
          end
          if string.len(v) < lineLen then
            table.insert(sentStr, spaces(dspace)..text.trim(v))
          end
        elseif string.sub(v, 1, string.len(select)) == select then
          table.insert(sentStr, v)
        else
          table.insert(sentStr, spaces(dspace)..v)
        end
      end
    end
  end
  for a = 1, #sentStr do
    if string.sub(sentStr[a], 1, string.len(select)) == select then
      sentStr[a] = string.sub(sentStr[a], string.len(select) + 2, string.len(sentStr[a]))
    end
  end
end

getMenuList()

local menuDirList=gui:addListBox(2, 2, menuDirWidth, menuDirHeight, menuList)
local functionsList=gui:addListBox(functionsCol, 2, functionsWidth, functionsHeight, tmpList)

local function updateMenuList()
  getMenuList()
  menuDirList:updateList(menuList)
end

local function updateFunctionsList(comp)
  local tLen = 1
  local sPos = 1
  local sTmp = ""
  local tmpList = {}

  contentsLabel.text="Functions for "..comp
  contentsLabel:draw()

  for len = 1, #compList do
    sPos = string.find(compList[len], ".", 1, true)
    if (string.sub(compList[len], 1, sPos - 1) == comp) then
      sTmp = string.sub(compList[len], sPos + 1, string.len(compList[len]))
      table.insert(tmpList, sTmp)
    end
  end
  table.sort(tmpList)
  tmpList = table_unique(tmpList)
  functionsList:updateList(tmpList)
end

function newListBox()
  local infoGUI=gml.create("center", "center", infoGuiWidth, infoGuiHeight)
  select = menuDirList:getSelected()
  loadInfoData(select)
  term.clear()
  local labelText = "Functions explanation for "
  local infoLabel=infoGUI:addLabel(math.max(1,math.floor((infoGuiWidth-string.len(labelText))/2)), 1, infoFunctionsLabelWidth, labelText)
  local infoLabel2=infoGUI:addLabel(math.max(1,math.floor((infoGuiWidth-string.len(select))/2)), 2, infoFunctionsLabelWidth, select)
  local infoList=infoGUI:addListBox(1, -5, infoListboxWidth, infoListboxHeight, sentStr)
  infoGUI:addButton("center",-1,12,2,"Close",infoGUI.close)
  infoGUI:run()
  term.clear()
  if (gpuDepth == Tier3) then
    intro()
  end
  gui:draw()
end

gui:addButton(-20,-1,8,1,"Info", newListBox)
gui:addButton(-11,-1,8,1,"Reload", updateMenuList)
gui:addButton(-2,-1,8,1,"Close",gui.close)

local function onMenuSelect(lb,prevIndex,selIndex)
  updateFunctionsList(menuDirList:getSelected())
end

menuDirList.onChange=onMenuSelect

menuDirList.onDoubleClick=function()
  updateFunctionsList(menuDirList:getSelected())
end

updateFunctionsList(menuDirList:getSelected())

gui:run()
term.setCursor(1,1)
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)

term.clear()
shell.setWorkingDirectory(oldDirectory)