local tty = require("tty")
local computer = require("computer")
local process = require("process")
local event = require("event")
local core_cursor = require("core/cursor")

local kb = require("keyboard")
local keys = kb.keys

local term = setmetatable({internal={}}, {__index=tty})

local function as_window(window, func, ...)
  local data = process.info().data
  if not data.window or not window then
    return func(...)
  end
  local prev = rawget(data, "window")
  data.window = window
  local ret = table.pack(func(...))
  data.window = prev
  return table.unpack(ret, 1, ret.n)
end

function term.internal.open(...)
  local dx, dy, w, h = ...
  local window = {fullscreen=select("#",...) == 0, blink = true, output_buffer = ""}

  -- support legacy code using direct manipulation of w and h
  -- (e.g. wocchat) instead of using setViewport
  setmetatable(window,
  {
    __index = function(tbl, key)
      key = key == "w" and "width" or key == "h" and "height" or key
      return rawget(tbl, key)
    end,
    __newindex = function(tbl, key, value)
      key = key == "w" and "width" or key == "h" and "height" or key
      return rawset(tbl, key, value)
    end
  })

  -- first time we open a pty the current tty.window must become the process window
  if rawget(tty, "window") then
    for _,p in pairs(process.list) do
      if not p.parent then
        p.data.window = tty.window
        break
      end
    end
    tty.window = nil
    setmetatable(tty,
    {
      __index = function(_, key)
        if key == "window" then
          return process.info().data.window
        end
      end
    })
  end

  as_window(window, tty.setViewport, w, h, dx, dy, 1, 1)
  as_window(window, tty.bind, tty.gpu())
  return window
end

local function create_cursor(history, ops)
  local cursor = history or {}
  cursor.hint = ops.hint or cursor.hint

  local filter = ops.filter or cursor.filter
  if filter then
    if type(filter) == "string" then
      local filter_text = filter
      filter = function(text)
        return text:match(filter_text)
      end
    end

    function cursor:handle(name, char, code)
      if name == "key_down" and (code == keys.enter or code == keys.numpadenter) then
        if not filter(self.data) then
          computer.beep(2000, 0.1)
          return true -- handled
        end
      end
      return self.super.handle(self, name, char, code)
    end
  end

  local pwchar = ops.pwchar or cursor.pwchar
  local nobreak = ops.dobreak == false or cursor.dobreak == false
  if pwchar or nobreak then
    if type(pwchar) == "string" then
      local pwchar_text = pwchar
      pwchar = function(text)
        return text:gsub(".", pwchar_text)
      end
    end  
    function cursor:echo(arg, ...)
      if pwchar and type(arg) == "string" and #arg > 0 and not arg:match("^\27") then -- "" is used for scrolling
        arg = pwchar(arg)
      elseif nobreak and arg == "\n" then
        arg = ""
      end
      return self.super.echo(self, arg, ...)
    end
  end

  return core_cursor.new(cursor, cursor.nowrap and core_cursor.horizontal)
end

-- cannot use term.write = io.write because io.write invokes metatable
function term.write(value, wrap)
  io.stdout:flush()
  local previous_nowrap = tty.window.nowrap
  tty.window.nowrap = wrap == false
  io.write(value)
  io.stdout:flush()
  tty.window.nowrap = previous_nowrap
end

function term.read(history, dobreak, hint, pwchar, filter)
  tty.window.cursor = create_cursor(history, {
    dobreak = dobreak,
    pwchar = pwchar,
    filter = filter,
    hint = hint
  })
  return io.stdin:readLine(false)
end

function term.getGlobalArea()
  local w,h,dx,dy = tty.getViewport()
  return dx+1,dy+1,w,h
end

function term.clearLine()
  term.write("\27[2K\27[999D")
end

function term.setCursorBlink(enabled)
  tty.window.blink = enabled
end

function term.getCursorBlink()
  return tty.window.blink
end

function term.pull(...)
  local args = table.pack(...)
  local timeout = math.huge
  if type(args[1]) == "number" then
    timeout = computer.uptime() + table.remove(args, 1)
    args.n = args.n - 1
  end
  local cursor = core_cursor.new()
  while timeout >= computer.uptime() do
    cursor:echo()
    local s = table.pack(event.pull(.5, table.unpack(args, 1, args.n)))
    cursor:echo(not s[1])
    if s.n > 1 then return table.unpack(s, 1, s.n) end
  end
end

function term.bind(gpu, window)
  return as_window(window, tty.bind, gpu)
end

term.scroll = tty.stream.scroll
term.internal.run_in_window = as_window

return term
