local vtansi = {}
function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
 local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
 local mx, my = gpu.maxResolution()
 local cx, cy = 1, 1
 local pc = " "
 local lc = ""
 local mode = 0 -- 0 normal, 1 escape, 2 command
 local lw = true
 local sx, sy = 1,1
 local cs = ""
 local bg, fg = 0, 0xFFFFFF

 -- setup
 gpu.setResolution(mx,my)
 gpu.fill(1,1,mx,my," ")
 local function checkCursor()
  if cx > mx and lw then
   cx, cy = 1, cy+1
  end
  if cy > my then
   gpu.copy(1,2,mx,my-1,0,-1)
   gpu.fill(1,my,mx,1," ")
   cy=my
  end
  if cy < 1 then cy = 1 end
  if cx < 1 then cx = 1 end
 end

 local function termwrite(s)
  local wb = ""
  local lb, ec = nil, nil
  local function flushwb()
   while wb:len() > 0 do
    checkCursor()
    local wl = wb:sub(1,mx-cx+1)
    wb = wb:sub(wl:len()+1)
    gpu.set(cx, cy, wl)
    cx = cx + wl:len()
   end
  end
  local rs = ""
  s=s:gsub("\8","\27[D")
  pc = gpu.get(cx,cy)
  gpu.setForeground(fg)
  gpu.setBackground(bg)
  gpu.set(cx,cy,pc)
  for cc in s:gmatch(".") do
   if mode == 0 then
    if cc == "\n" then
     flushwb()
     cx,cy = 1, cy+1
    elseif cc == "\t" then
     wb=wb..(" "):rep(8*((cx+9)//8))
    elseif cc == "\27" then
     flushwb()
     mode = 1
    else
     wb = wb .. cc
    end
   elseif mode == 1 then
    if cc == "[" then
     mode = 2
    else
     mode = 0
    end
   elseif mode == 2 then
    if cc:match("[%d;]") then
     cs = cs .. cc
    else
     mode = 0
     local tA = {}
     for s in cs:gmatch("%d+") do
      tA[#tA+1] = tonumber(s)
     end
     if cc == "H" then
      cx, cy = math.min(mx,tA[1] or 1), math.min(my,tA[2] or 1)
     elseif cc == "A" then
      cy = cy - (tA[1] or 1)
     elseif cc == "B" then
      cy = cy + (tA[1] or 1)
     elseif cc == "C" then
      cx = cx + (tA[1] or 1)
     elseif cc == "D" then
      cx = cx - (tA[1] or 1)
     elseif cc == "s" then
      sx, sy = cx, cy
     elseif cc == "u" then
      cx, cy = sx, sy
     elseif cc == "n" and tA[1] == 6 then
      rs = string.format("%s\27[%d;%dR",rs,cx,cy)
      dprint(string.format("reporting %d;%d as current cursor position",cx,cy))
     elseif cc == "K" and tA[1] == 1 then
      gpu.fill(1,cy,cx,1," ")
     elseif cc == "K" and tA[1] == 2 then
      gpu.fill(cx,cy,mx,1," ")
     elseif cc == "K" then
      gpu.fill(1,cy,mx,1," ")
     elseif cc == "J" and tA[1] == 1 then
      gpu.fill(1,1,mx,cy," ")
     elseif cc == "J" and tA[1] == 2 then
      gpu.fill(1,1,mx,my," ")
      cx, cy = 1, 1
     elseif cc == "J" then
      gpu.fill(1,cy,mx,my," ")
     elseif cc == "m" then
      for _,num in ipairs(tA) do
       if num == 0 then
        fg,bg,ec,lb = 0xFFFFFF,0,true,true
       elseif num == 7 then
        local nfg,nbg = bg, fg
        fg, bg = nfg, nbg
       elseif num > 29 and num < 38 then
        fg = colours[num-29]
       elseif num > 39 and num < 48 then
        bg = colours[num-39]
       elseif num == 100 or num == 8 then -- disable local echo
        ec = false
       elseif num == 101 then -- disable line mode
        lb = false
       end
      end
      gpu.setForeground(fg)
      gpu.setBackground(bg)
     end
     cs = ""
     checkCursor()
    end
   end
  end
  flushwb()
  checkCursor()
  pc = gpu.get(cx,cy)
  gpu.setForeground(bg)
  gpu.setBackground(fg)
  gpu.set(cx,cy,pc)
  gpu.setForeground(fg)
  gpu.setBackground(bg)
  return rs, lb, ec
 end

 return termwrite
end

function vtansi.vtsession(gpua,scra) -- string string -- table -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
 local gpu = component.proxy(gpua)
 gpu.bind(scra)
 local write = vtansi.vtemu(gpu)
 local kba = {}
 for k,v in ipairs(component.invoke(scra,"getKeyboards")) do
  kba[v]=true
 end
 local buf, lbuf, echo = "", true, true
 os.spawn(function() dprint(pcall(function()
  while true do
   local ty,ka,ch = coroutine.yield()
   if ty == "key_down" and kba[ka] then
    if ch == 13 then ch = 10 end
    if ch == 8 and lbuf then
     if buf:len() > 0 then
      if echo then write("\8 \8") end
      buf = buf:sub(1,-2)
     end
    elseif ch > 0 then
     if echo then write(string.char(ch)) end
     buf=buf..string.char(ch)
    end
   end
  end
 end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
 local function bread(n)
  local r
  if lbuf then
   while not buf:find("\n") do
    coroutine.yield()
   end
   local n = buf:find("\n")
   r, buf = buf:sub(1,n), buf:sub(n+1)
  else
   r = buf
   buf = ""
   coroutine.yield()
  end
  return r
 end
 local function bwrite(d)
  local ba, lb, ec = write(d)
  buf = buf .. ba
  if lb ~= nil then
   lbuf = lb
  end
  if ec ~= nil then
   echo = ec
  end
 end
 return bread, bwrite, function() io.write("\27[2J\27[H") end
end
return vtansi