local builtin_require = require
local table = table or builtin_require("table")
local string = string or builtin_require("string")
local coroutine = coroutine or builtin_require("coroutine")
local debug = builtin_require("debug")
local os = os or (function(module)
  local ok, res = pcall(builtin_require, module)
  return ok and res or nil
end)("os")
local mobdebug = {
  _NAME = "mobdebug",
  _VERSION = 0.607,
  _COPYRIGHT = "Paul Kulchenko",
  _DESCRIPTION = "Mobile Remote Debugger for the Lua programming language",
  port = os and os.getenv and tonumber((os.getenv("MOBDEBUG_PORT"))) or 8172,
  checkcount = 200,
  yieldtimeout = 0.02,
  connecttimeout = 2
}
local todebug = function(v)
  if type(v) ~= "userdata" then
    return tostring(v)
  end
  local mt = debug.getmetatable(v)
  if not mt then
    return tostring(v)
  end
  if not mt.__tostring_debugger then
    return string.format("%s [%s]", type(v), tostring(v))
  end
  return mt.__tostring_debugger(v)
end
local error = error
local getfenv = getfenv
local setfenv = setfenv
local loadstring = loadstring or load
local pairs = pairs
local setmetatable = setmetatable
local tonumber = tonumber
local unpack = table.unpack or unpack
local rawget = rawget
local genv = _G or _ENV
local jit = rawget(genv, "jit")
local MOAICoroutine = rawget(genv, "MOAICoroutine")
local metagindex = getmetatable(genv) and getmetatable(genv).__index
local ngx = type(metagindex) == "table" and metagindex.rawget and metagindex:rawget("ngx") or nil
local corocreate = ngx and coroutine._create or coroutine.create
local cororesume = ngx and coroutine._resume or coroutine.resume
local coroyield = ngx and coroutine._yield or coroutine.yield
local corostatus = ngx and coroutine._status or coroutine.status
if not setfenv then
  local findenv = function(f)
    local level = 1
    repeat
      local name, value = debug.getupvalue(f, level)
      if name == "_ENV" then
        return level, value
      end
      level = level + 1
    until name == nil
    return nil
  end
  function getfenv(f)
    return select(2, findenv(f)) or _G
  end
  function setfenv(f, t)
    local level = findenv(f)
    if level then
      debug.setupvalue(f, level, t)
    end
    return f
  end
end
local win = false
local mac = false
local iscasepreserving = true
if jit and jit.off then
  jit.off()
end
local socket = builtin_require("socket")
local coro_debugger, coro_debugee
local coroutines = {}
setmetatable(coroutines, {__mode = "k"})
local events = {
  BREAK = 1,
  WATCH = 2,
  RESTART = 3,
  STACK = 4
}
local breakpoints = {}
local watches = {}
local lastsource, lastfile
local watchescnt = 0
local abort
local seen_hook = false
local checkcount = 0
local step_into = false
local step_over = false
local step_level = 0
local stack_level = 0
local server, buf
local outputs = {}
local iobase = {
  print = print
}
local basedir = ""
local deferror = "execution aborted at default debugee"
local error = builtin_error or error
local debugee = function()
  local a = 1
  for _ = 1, 10 do
    a = a + 1
  end
  error(deferror)
end
local q = function(s)
  return s:gsub("([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1")
end
local serpent = (function()
  local n, v = "serpent", 0.272
  local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
  local snum = {
    [tostring(1 / 0)] = "1/0 --[[math.huge]]",
    [tostring(-1 / 0)] = "-1/0 --[[-math.huge]]",
    [tostring(0 / 0)] = "0/0"
  }
  local badtype = {thread = true, cdata = true}
  local keyword, globals, G = {}, {}, _G or _ENV
  for _, k in ipairs({
    "and",
    "break",
    "do",
    "else",
    "elseif",
    "end",
    "false",
    "for",
    "function",
    "goto",
    "if",
    "in",
    "local",
    "nil",
    "not",
    "or",
    "repeat",
    "return",
    "then",
    "true",
    "until",
    "while"
  }) do
    keyword[k] = true
  end
  local s = function(t, opts)
    local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
    local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
    local space, maxl = opts.compact and "" or " ", opts.maxlevel or math.huge
    local iname, comm = "_" .. (name or ""), not opts.comment or tonumber(opts.comment) or math.huge
    local seen, sref, syms, symn = {}, {
      "local " .. iname .. "={}"
    }, {}, 0
    local gensym = function(val)
      return "_" .. tostring(tostring(val)):gsub("[^%w]", ""):gsub("(%d%w+)", function(s)
        if not syms[s] then
          symn = symn + 1
          syms[s] = symn
        end
        return syms[s]
      end)
    end
    local safestr = function(s)
      return type(s) == "number" and (huge and snum[tostring(s)] or s) or type(s) ~= "string" and tostring(s) or ("%q"):format(s):gsub("\n", "n"):gsub("\026", "\\026")
    end
    local comment = function(s, l)
      return comm and (l or 0) < comm and " --[[" .. tostring(s) .. "]]" or ""
    end
    local globerr = function(s, l)
      return globals[s] and globals[s] .. comment(s, l) or not fatal and safestr(select(2, pcall(tostring, s))) or error("Can't serialize " .. tostring(s))
    end
    local safename = function(path, name)
      local n = name == nil and "" or name
      local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
      local safe = plain and n or "[" .. safestr(n) .. "]"
      return (path or "") .. (plain and path and "." or "") .. safe, safe
    end
    local alphanumsort = type(opts.sortkeys) == "function" and opts.sortkeys or function(k, o, n)
      local maxn, to = tonumber(n) or 12, {number = "a", string = "b"}
      local padnum = function(d)
        return ("%0" .. maxn .. "d"):format(d)
      end
      table.sort(k, function(a, b)
        return (k[a] ~= nil and 0 or to[type(a)] or "z") .. tostring(a):gsub("%d+", padnum) < (k[b] ~= nil and 0 or to[type(b)] or "z") .. tostring(b):gsub("%d+", padnum)
      end)
    end
    local function val2str(t, name, indent, insref, path, plainindex, level)
      local ttype, level, mt = type(t), level or 0, getmetatable(t)
      local spath, sname = safename(path, name)
      local tag = plainindex and (type(name) == "number" and "" or name .. space .. "=" .. space) or name ~= nil and sname .. space .. "=" .. space or ""
      if seen[t] then
        sref[#sref + 1] = spath .. space .. "=" .. space .. seen[t]
        return tag .. "nil" .. comment("ref", level)
      end
      if ttype == "userdata" then
        seen[t] = insref or path
        t = todebug(t)
        ttype = type(t)
      elseif type(mt) == "table" and (mt.__serialize or mt.__tostring) then
        seen[t] = insref or spath
        if mt.__serialize then
          t = mt.__serialize(t)
        else
          t = tostring(t)
        end
        ttype = type(t)
      end
      if ttype == "table" then
        if level >= maxl then
          return tag .. "{}" .. comment("max", level)
        end
        seen[t] = insref or spath
        if next(t) == nil then
          return tag .. "{}" .. comment(t, level)
        end
        local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
        for key = 1, maxn do
          o[key] = key
        end
        if not maxnum or #o < maxnum then
          local n = #o
          for key in pairs(t) do
            if o[key] ~= key then
              n = n + 1
              o[n] = key
            end
          end
        end
        if maxnum and #o > maxnum then
          o[maxnum + 1] = nil
        end
        if opts.sortkeys and maxn < #o then
          alphanumsort(o, t, opts.sortkeys)
        end
        local sparse = sparse and maxn < #o
        for n, key in ipairs(o) do
          local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
          if not ((not opts.valignore or not opts.valignore[value]) and (not opts.keyallow or opts.keyallow[key])) or opts.valtypeignore and opts.valtypeignore[type(value)] or sparse and value == nil then
          elseif ktype == "table" or ktype == "function" or badtype[ktype] then
            if not seen[key] and not globals[key] then
              sref[#sref + 1] = "placeholder"
              local sname = safename(iname, gensym(key))
              sref[#sref] = val2str(key, sname, indent, sname, iname, true)
            end
            sref[#sref + 1] = "placeholder"
            local path = seen[t] .. "[" .. (seen[key] or globals[key] or gensym(key)) .. "]"
            sref[#sref] = path .. space .. "=" .. space .. (seen[value] or val2str(value, nil, indent, path))
          else
            out[#out + 1] = val2str(value, key, indent, insref, seen[t], plainindex, level + 1)
          end
        end
        local prefix = string.rep(indent or "", level)
        local head = indent and "{\n" .. prefix .. indent or "{"
        local body = table.concat(out, "," .. (indent and "\n" .. prefix .. indent or space))
        local tail = indent and "\n" .. prefix .. "}" or "}"
        return (custom and custom(tag, head, body, tail) or tag .. head .. body .. tail) .. comment(t, level)
      elseif badtype[ttype] then
        seen[t] = insref or spath
        return tag .. globerr(t, level)
      elseif ttype == "function" then
        seen[t] = insref or spath
        local ok, res = pcall(string.dump, t)
        local func = ok and (opts.nocode and "function() --[[..skipped..]] end" or "((loadstring or load)(" .. safestr(res) .. ",'@serialized'))") .. comment(t, level)
        return tag .. (func or globerr(t, level))
      else
        return tag .. safestr(t)
      end
    end
    local sepr = indent and "\n" or ";" .. space
    local body = val2str(t, name, indent)
    local tail = 1 < #sref and table.concat(sref, sepr) .. sepr or ""
    local warn = opts.comment and 1 < #sref and space .. "--[[incomplete output with shared/self-references skipped]]" or ""
    return not name and body .. warn or "do local " .. body .. sepr .. tail .. "return " .. name .. sepr .. "end"
  end
  local deserialize = function(data, opts)
    local f, res = (loadstring or load)("return " .. data)
    if not f then
      f, res = (loadstring or load)(data)
    end
    if not f then
      return f, res
    end
    if opts and opts.safe == false then
      return pcall(f)
    end
    local count, thread = 0, coroutine.running()
    local h, m, c = debug.gethook(thread)
    debug.sethook(function(e, l)
      count = count + 1
      if 3 <= count then
        error("cannot call functions")
      end
    end, "c")
    local res = {
      pcall(f)
    }
    count = 0
    debug.sethook(thread, h, m, c)
    return (table.unpack or unpack)(res)
  end
  local merge = function(a, b)
    if b then
      for k, v in pairs(b) do
        a[k] = v
      end
    end
    return a
  end
  return {
    _NAME = n,
    _COPYRIGHT = c,
    _DESCRIPTION = d,
    _VERSION = v,
    serialize = s,
    load = deserialize,
    dump = function(a, opts)
      return s(a, merge({
        name = "_",
        compact = true,
        sparse = true
      }, opts))
    end,
    line = function(a, opts)
      return s(a, merge({sortkeys = true, comment = true}, opts))
    end,
    block = function(a, opts)
      return s(a, merge({
        indent = "  ",
        sortkeys = true,
        comment = true
      }, opts))
    end
  }
end)()
mobdebug.line = serpent.line
mobdebug.dump = serpent.dump
mobdebug.linemap = nil
mobdebug.loadstring = loadstring
local removebasedir = function(path, basedir)
  if iscasepreserving then
    return path:lower():find("^" .. q(basedir:lower())) and path:sub(#basedir + 1) or path
  else
    return string.gsub(path, "^" .. q(basedir), "")
  end
end
local stack = function(start)
  local vars = function(f)
    local func = debug.getinfo(f, "f").func
    local i = 1
    local locals = {}
    while true do
      local name, value = debug.getlocal(f, i)
      if not name then
        break
      end
      if name ~= "_ENV" and string.sub(name, 1, 1) ~= "(" then
        locals[name] = {
          value,
          todebug(value)
        }
      end
      i = i + 1
    end
    i = 1
    local ups = {}
    while func do
      local name, value = debug.getupvalue(func, i)
      if not name then
        break
      end
      if name ~= "_ENV" then
        ups[name] = {
          value,
          todebug(value)
        }
      end
      i = i + 1
    end
    return locals, ups
  end
  local stack = {}
  local linemap = mobdebug.linemap
  for i = start or 0, 100 do
    local source = debug.getinfo(i, "Snl")
    if not source then
      break
    end
    local src = source.source
    if src:find("@") == 1 then
      src = src:sub(2):gsub("\\", "/")
      if src:find("%./") == 1 then
        src = src:sub(3)
      end
    end
    table.insert(stack, {
      {
        source.name,
        removebasedir(src, basedir),
        linemap and linemap(source.linedefined, source.source) or source.linedefined,
        linemap and linemap(source.currentline, source.source) or source.currentline,
        source.what,
        source.namewhat,
        source.short_src
      },
      vars(i + 1)
    })
    if source.what == "main" then
      break
    end
  end
  return stack
end
local set_breakpoint = function(file, line)
  if file == "-" and lastfile then
    file = lastfile
  elseif iscasepreserving then
    file = string.lower(file)
  end
  if not breakpoints[line] then
    breakpoints[line] = {}
  end
  breakpoints[line][file] = true
end
local remove_breakpoint = function(file, line)
  if file == "-" and lastfile then
    file = lastfile
  elseif iscasepreserving then
    file = string.lower(file)
  end
  if breakpoints[line] then
    breakpoints[line][file] = nil
  end
end
local has_breakpoint = function(file, line)
  return breakpoints[line] and breakpoints[line][iscasepreserving and string.lower(file) or file]
end
local restore_vars = function(vars)
  if type(vars) ~= "table" then
    return
  end
  local i = 1
  while true do
    local name = debug.getlocal(3, i)
    if not name then
      break
    end
    i = i + 1
  end
  i = i - 1
  local written_vars = {}
  while 0 < i do
    local name = debug.getlocal(3, i)
    if not written_vars[name] then
      if string.sub(name, 1, 1) ~= "(" then
        debug.setlocal(3, i, rawget(vars, name))
      end
      written_vars[name] = true
    end
    i = i - 1
  end
  i = 1
  local func = debug.getinfo(3, "f").func
  while true do
    local name = debug.getupvalue(func, i)
    if not name then
      break
    end
    if not written_vars[name] then
      if string.sub(name, 1, 1) ~= "(" then
        debug.setupvalue(func, i, rawget(vars, name))
      end
      written_vars[name] = true
    end
    i = i + 1
  end
end
local capture_vars = function(level)
  local vars = {}
  local func = debug.getinfo(level or 3, "f").func
  local i = 1
  while true do
    local name, value = debug.getupvalue(func, i)
    if not name then
      break
    end
    if string.sub(name, 1, 1) ~= "(" then
      vars[name] = value
    end
    i = i + 1
  end
  i = 1
  while true do
    local name, value = debug.getlocal(level or 3, i)
    if not name then
      break
    end
    if string.sub(name, 1, 1) ~= "(" then
      vars[name] = value
    end
    i = i + 1
  end
  setmetatable(vars, {
    __index = getfenv(func),
    __newindex = getfenv(func)
  })
  return vars
end
local stack_depth = function(start_depth)
  for i = start_depth, 0, -1 do
    if debug.getinfo(i, "l") then
      return i + 1
    end
  end
  return start_depth
end
local is_safe = function(stack_level)
  if stack_level == 3 then
    return true
  end
  for i = 3, stack_level do
    local info = debug.getinfo(i, "S")
    if not info then
      return true
    end
    if info.what == "C" then
      return false
    end
  end
  return true
end
local in_debugger = function()
  local this = debug.getinfo(1, "S").source
  for i = 3, 7 do
    local info = debug.getinfo(i, "S")
    if not info then
      return false
    end
    if info.source == this then
      return true
    end
  end
  return false
end
local is_pending = function(peer)
  if not buf and checkcount >= mobdebug.checkcount then
    peer:settimeout(0)
    buf = peer:receive(1)
    peer:settimeout()
    checkcount = 0
  end
  return buf
end
local readnext = function(peer, num)
  peer:settimeout(0)
  local res, err, partial = peer:receive(num)
  peer:settimeout()
  return res or partial or "", err
end
local handle_breakpoint = function(peer)
  if not buf or buf:sub(1, 1) ~= "S" and buf:sub(1, 1) ~= "D" then
    return
  end
  if #buf == 1 then
    buf = buf .. readnext(peer, 1)
  end
  if buf:sub(2, 2) ~= "E" then
    return
  end
  buf = buf .. readnext(peer, 5 - #buf)
  if buf ~= "SETB " and buf ~= "DELB " then
    return
  end
  local res, _, partial = peer:receive()
  if not res then
    if partial then
      buf = buf .. partial
    end
    return
  end
  local _, _, cmd, file, line = (buf .. res):find("^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
  if cmd == "SETB" then
    set_breakpoint(file, tonumber(line))
  elseif cmd == "DELB" then
    remove_breakpoint(file, tonumber(line))
  else
    return
  end
  buf = nil
end
local debug_hook = function(event, line)
  if jit then
    local coro, main = coroutine.running()
    if not coro or main then
      coro = "main"
    end
    local disabled = coroutines[coro] == false or coroutines[coro] == nil and coro ~= (coro_debugee or "main")
    if coro_debugee and disabled or not coro_debugee and (disabled or in_debugger()) then
      return
    end
  end
  if abort and is_safe(stack_level) then
    error(abort)
  end
  if not seen_hook and in_debugger() then
    return
  end
  if event == "call" then
    stack_level = stack_level + 1
  elseif event == "return" or event == "tail return" then
    stack_level = stack_level - 1
  elseif event == "line" then
    if mobdebug.linemap then
      local ok, mappedline = pcall(mobdebug.linemap, line, debug.getinfo(2, "S").source)
      if ok then
        line = mappedline
      end
      if not line then
        return
      end
    end
    if not step_into and not step_over and not breakpoints[line] and not (0 < watchescnt) and not is_pending(server) then
      checkcount = checkcount + 1
      return
    end
    checkcount = mobdebug.checkcount
    stack_level = stack_depth(stack_level + 1)
    local caller = debug.getinfo(2, "S")
    local file = lastfile
    if lastsource ~= caller.source then
      file, lastsource = caller.source, caller.source
      if file:find("^@") then
        file = file:gsub("^@", ""):gsub("\\", "/")
        if iscasepreserving then
          file = string.lower(file)
        end
        if file:find("%./") == 1 then
          file = file:sub(3)
        else
          file = file:gsub("^" .. q(basedir), "")
        end
        file = file:gsub("\n", " ")
      elseif file:find("[\r\n]") then
        file = mobdebug.line(file)
      else
        if iscasepreserving then
          file = string.lower(file)
        end
        file = file:gsub("\\", "/"):gsub(file:find("^%./") and "^%./" or "^" .. q(basedir), "")
      end
      seen_hook = true
      lastfile = file
    end
    if is_pending(server) then
      handle_breakpoint(server)
    end
    local vars, status, res
    if 0 < watchescnt then
      vars = capture_vars()
      for index, value in pairs(watches) do
        setfenv(value, vars)
        local ok, fired = pcall(value)
        if ok and fired then
          status, res = cororesume(coro_debugger, events.WATCH, vars, file, line, index)
          break
        end
      end
    end
    local getin = status == nil and (step_into or step_over and step_over == (coroutine.running() or "main") and stack_level <= step_level or has_breakpoint(file, line) or is_pending(server))
    if getin then
      vars = vars or capture_vars()
      step_into = false
      step_over = false
      status, res = cororesume(coro_debugger, events.BREAK, vars, file, line)
    end
    if status and res == "stack" then
      while status and res == "stack" do
        if vars then
          restore_vars(vars)
        end
        local ok, snapshot = pcall(stack, ngx and 5 or 4)
        status, res = cororesume(coro_debugger, ok and events.STACK or events.BREAK, snapshot, file, line)
      end
    end
    if status and res and res ~= "stack" then
      if not abort and res == "exit" then
        if os then
          os.exit(1, true)
        end
        return
      end
      if not abort and res == "done" then
        mobdebug.done()
        return
      end
      abort = res
      if is_safe(stack_level) then
        error(abort)
      end
    elseif not status and res then
      error(res, 2)
    end
    if vars then
      restore_vars(vars)
    end
    if step_over == true then
      step_over = coroutine.running() or "main"
    end
  end
end
local stringify_results = function(status, ...)
  if not status then
    return status, ...
  end
  local t = {
    ...
  }
  for i, v in pairs(t) do
    local ok, res = pcall(mobdebug.line, v, {nocode = true, comment = 1})
    t[i] = ok and res or ("%q"):format(res):gsub("\n", "n"):gsub("\026", "\\026")
  end
  return pcall(mobdebug.dump, t, {sparse = false})
end
local isrunning = function()
  return coro_debugger and (corostatus(coro_debugger) == "suspended" or corostatus(coro_debugger) == "running")
end
local done = function()
  if not isrunning() or not server then
    return
  end
  if not jit then
    for co, debugged in pairs(coroutines) do
      if debugged then
        debug.sethook(co)
      end
    end
  end
  debug.sethook()
  server:close()
  coro_debugger = nil
  seen_hook = nil
  abort = nil
end
local debugger_loop = function(sev, svars, sfile, sline)
  local command, app, osname
  local eval_env = svars or {}
  local emptyWatch = function()
    return false
  end
  local loaded = {}
  for k in pairs(package.loaded) do
    loaded[k] = true
  end
  while true do
    local line, err
    local wx = rawget(genv, "wx")
    if (wx or mobdebug.yield) and server.settimeout then
      server:settimeout(mobdebug.yieldtimeout)
    end
    while true do
      line, err = server:receive()
      if not line and err == "timeout" then
        app = app or wx and wx.wxGetApp and wx.wxGetApp()
        if app then
          local win = app:GetTopWindow()
          local inloop = app:IsMainLoopRunning()
          osname = osname or wx.wxPlatformInfo.Get():GetOperatingSystemFamilyName()
          if win and not inloop then
            if osname == "Unix" then
              wx.wxTimer(app):Start(10, true)
            end
            do
              local exitLoop = function()
                win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_IDLE)
                win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_TIMER)
                app:ExitMainLoop()
              end
              win:Connect(wx.wxEVT_IDLE, exitLoop)
              win:Connect(wx.wxEVT_TIMER, exitLoop)
              app:MainLoop()
            end
          end
        elseif mobdebug.yield then
          mobdebug.yield()
        end
      elseif not line and err == "closed" then
        error("Debugger connection closed", 0)
      else
        if buf then
          line = buf .. line
          buf = nil
        end
        break
      end
    end
    if server.settimeout then
      server:settimeout()
    end
    command = string.sub(line, string.find(line, "^[A-Z]+"))
    if command == "SETB" then
      local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
      if file and line then
        set_breakpoint(file, tonumber(line))
        server:send("200 OK\n")
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "DELB" then
      local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
      if file and line then
        remove_breakpoint(file, tonumber(line))
        server:send("200 OK\n")
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "EXEC" then
      local _, _, chunk = string.find(line, "^[A-Z]+%s+(.+)$")
      if chunk then
        local func, res = mobdebug.loadstring(chunk)
        local status
        if func then
          setfenv(func, eval_env)
          status, res = stringify_results(pcall(func))
        end
        if status then
          server:send("200 OK " .. #res .. "\n")
          server:send(res)
        else
          server:send("401 Error in Expression " .. #res .. "\n")
          server:send(res)
        end
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "LOAD" then
      local _, _, size, name = string.find(line, "^[A-Z]+%s+(%d+)%s+(%S.-)%s*$")
      size = tonumber(size)
      if abort == nil then
        if 0 < size then
          server:receive(size)
        end
        if sfile and sline then
          server:send("201 Started " .. sfile .. " " .. sline .. "\n")
        else
          server:send("200 OK 0\n")
        end
      else
        for k in pairs(package.loaded) do
          if not loaded[k] then
            package.loaded[k] = nil
          end
        end
        if size == 0 and name == "-" then
          server:send("200 OK 0\n")
          coroyield("load")
        else
          local chunk = size == 0 and "" or server:receive(size)
          if chunk then
            local func, res = mobdebug.loadstring(chunk, "@" .. name)
            if func then
              server:send("200 OK 0\n")
              debugee = func
              coroyield("load")
            else
              server:send("401 Error in Expression " .. #res .. "\n")
              server:send(res)
            end
          else
            server:send("400 Bad Request\n")
          end
        end
      end
    elseif command == "SETW" then
      local _, _, exp = string.find(line, "^[A-Z]+%s+(.+)%s*$")
      if exp then
        local func, res = mobdebug.loadstring("return(" .. exp .. ")")
        if func then
          watchescnt = watchescnt + 1
          local newidx = #watches + 1
          watches[newidx] = func
          server:send("200 OK " .. newidx .. "\n")
        else
          server:send("401 Error in Expression " .. #res .. "\n")
          server:send(res)
        end
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "DELW" then
      local _, _, index = string.find(line, "^[A-Z]+%s+(%d+)%s*$")
      index = tonumber(index)
      if 0 < index and index <= #watches then
        watchescnt = watchescnt - (watches[index] ~= emptyWatch and 1 or 0)
        watches[index] = emptyWatch
        server:send("200 OK\n")
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "RUN" then
      server:send("200 OK\n")
      local ev, vars, file, line, idx_watch = coroyield()
      eval_env = vars
      if ev == events.BREAK then
        server:send("202 Paused " .. file .. " " .. line .. "\n")
      elseif ev == events.WATCH then
        server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
      elseif ev == events.RESTART then
      else
        server:send("401 Error in Execution " .. #file .. "\n")
        server:send(file)
      end
    elseif command == "STEP" then
      server:send("200 OK\n")
      step_into = true
      local ev, vars, file, line, idx_watch = coroyield()
      eval_env = vars
      if ev == events.BREAK then
        server:send("202 Paused " .. file .. " " .. line .. "\n")
      elseif ev == events.WATCH then
        server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
      elseif ev == events.RESTART then
      else
        server:send("401 Error in Execution " .. #file .. "\n")
        server:send(file)
      end
    elseif command == "OVER" or command == "OUT" then
      server:send("200 OK\n")
      step_over = true
      if command == "OUT" then
        step_level = stack_level - 1
      else
        step_level = stack_level
      end
      local ev, vars, file, line, idx_watch = coroyield()
      eval_env = vars
      if ev == events.BREAK then
        server:send("202 Paused " .. file .. " " .. line .. "\n")
      elseif ev == events.WATCH then
        server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
      elseif ev == events.RESTART then
      else
        server:send("401 Error in Execution " .. #file .. "\n")
        server:send(file)
      end
    elseif command == "BASEDIR" then
      local _, _, dir = string.find(line, "^[A-Z]+%s+(.+)%s*$")
      if dir then
        basedir = iscasepreserving and string.lower(dir) or dir
        lastsource = nil
        server:send("200 OK\n")
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "SUSPEND" then
    elseif command == "DONE" then
      server:send("200 OK\n")
      coroyield("done")
      return
    elseif command == "STACK" then
      local vars, ev = {}
      if seen_hook then
        ev, vars = coroyield("stack")
      end
      if ev and ev ~= events.STACK then
        server:send("401 Error in Execution " .. #vars .. "\n")
        server:send(vars)
      else
        local ok, res = pcall(mobdebug.dump, vars, {nocode = true, sparse = false})
        if ok then
          server:send("200 OK " .. res .. "\n")
        else
          server:send("401 Error in Execution " .. #res .. "\n")
          server:send(res)
        end
      end
    elseif command == "OUTPUT" then
      local _, _, stream, mode = string.find(line, "^[A-Z]+%s+(%w+)%s+([dcr])%s*$")
      if stream and mode and stream == "stdout" then
        local default = mode == "d"
        genv.print = default and iobase.print or coroutine.wrap(function()
          while true do
            local tbl = {
              coroutine.yield()
            }
            if mode == "c" then
              iobase.print(unpack(tbl))
            end
            for n = 1, #tbl do
              tbl[n] = select(2, pcall(mobdebug.line, tbl[n], {nocode = true, comment = false}))
            end
            local file = table.concat(tbl, "\t") .. "\n"
            server:send("204 Output " .. stream .. " " .. #file .. "\n" .. file)
          end
        end)
        if not default then
          genv.print()
        end
        server:send("200 OK\n")
      else
        server:send("400 Bad Request\n")
      end
    elseif command == "EXIT" then
      server:send("200 OK\n")
      coroyield("exit")
    else
      server:send("400 Bad Request\n")
    end
  end
end
local connect = function(controller_host, controller_port)
  local sock, err = socket.tcp()
  if not sock then
    return nil, err
  end
  if sock.settimeout then
    sock:settimeout(mobdebug.connecttimeout)
  end
  local res, err = sock:connect(controller_host, controller_port)
  if sock.settimeout then
    sock:settimeout()
  end
  if not res then
    return nil, err
  end
  return sock
end
local lasthost, lastport
local start = function(controller_host, controller_port)
  if isrunning() then
    return
  end
  lasthost = controller_host or lasthost
  lastport = controller_port or lastport
  controller_host = lasthost or GetHostIP()
  controller_port = lastport or mobdebug.port
  local err
  server, err = mobdebug.connect(controller_host, controller_port)
  if server then
    stack_level = stack_depth(16)
    do
      local dtraceback = debug.traceback
      function debug.traceback(...)
        if select("#", ...) >= 1 then
          local err, lvl = ...
          if err and type(err) ~= "thread" then
            local trace = dtraceback(err, (lvl or 2) + 1)
            if genv.print == iobase.print then
              return trace
            else
              genv.print(trace)
              return
            end
          end
        end
        return (dtraceback(...):gsub([[
(stack traceback:
)[^
]*
]], "%1"))
      end
    end
    coro_debugger = corocreate(debugger_loop)
    debug.sethook(debug_hook, "lcr")
    seen_hook = nil
    step_into = true
    return true
  else
    print(("Could not connect to %s:%s: %s"):format(controller_host, controller_port, err or "unknown error"))
  end
end
local controller = function(controller_host, controller_port, scratchpad)
  if isrunning() then
    return
  end
  lasthost = controller_host or lasthost
  lastport = controller_port or lastport
  controller_host = lasthost or "localhost"
  controller_port = lastport or mobdebug.port
  local exitonerror = not scratchpad
  local err
  server, err = mobdebug.connect(controller_host, controller_port)
  if server then
    local report = function(trace, err)
      local msg = err .. "\n" .. trace
      server:send("401 Error in Execution " .. #msg .. "\n")
      server:send(msg)
      return err
    end
    seen_hook = true
    coro_debugger = corocreate(debugger_loop)
    while true do
      step_into = true
      abort = false
      if scratchpad then
        checkcount = mobdebug.checkcount
      end
      coro_debugee = corocreate(debugee)
      debug.sethook(coro_debugee, debug_hook, "lcr")
      local status, err = cororesume(coro_debugee)
      if abort then
        if tostring(abort) == "exit" then
          break
        end
      else
        if status then
          break
        end
        if err and not tostring(err):find(deferror) then
          report(debug.traceback(coro_debugee), tostring(err))
          if not (not exitonerror and coro_debugger) then
            break
          end
          local status, err = cororesume(coro_debugger, events.RESTART, capture_vars(2))
          if not status or status and err == "exit" then
            break
          end
        end
      end
    end
  else
    print(("Could not connect to %s:%s: %s"):format(controller_host, controller_port, err or "unknown error"))
    return false
  end
  return true
end
local scratchpad = function(controller_host, controller_port)
  return controller(controller_host, controller_port, true)
end
local loop = function(controller_host, controller_port)
  return controller(controller_host, controller_port, false)
end
local on = function()
  if not isrunning() or not server then
    return
  end
  local co, main = coroutine.running()
  if main then
    co = nil
  end
  if co then
    coroutines[co] = true
    debug.sethook(co, debug_hook, "lcr")
  else
    if jit then
      coroutines.main = true
    end
    debug.sethook(debug_hook, "lcr")
  end
end
local off = function()
  if not isrunning() or not server then
    return
  end
  local co, main = coroutine.running()
  if main then
    co = nil
  end
  if co then
    coroutines[co] = false
    if not jit then
      debug.sethook(co)
    end
  else
    if jit then
      coroutines.main = false
    end
    if not jit then
      debug.sethook()
    end
  end
  if jit then
    local remove = true
    for _, debugged in pairs(coroutines) do
      if debugged then
        remove = false
        break
      end
    end
    if remove then
      debug.sethook()
    end
  end
end
local handle = function(params, client, options)
  local _, _, command = string.find(params, "^([a-z]+)")
  local file, line, watch_idx
  if command == "run" or command == "step" or command == "out" or command == "over" or command == "exit" then
    client:send(string.upper(command) .. "\n")
    client:receive()
    while true do
      local done = true
      local breakpoint = client:receive()
      if not breakpoint then
        print("Program finished")
        os.exit(0, true)
        return
      end
      local _, _, status = string.find(breakpoint, "^(%d+)")
      if status == "200" then
      elseif status == "202" then
        _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
        if file and line then
          print("Paused at file " .. file .. " line " .. line)
        end
      elseif status == "203" then
        _, _, file, line, watch_idx = string.find(breakpoint, "^203 Paused%s+(.-)%s+(%d+)%s+(%d+)%s*$")
        if file and line and watch_idx then
          print("Paused at file " .. file .. " line " .. line .. " (watch expression " .. watch_idx .. ": [" .. watches[watch_idx] .. "])")
        end
      elseif status == "204" then
        local _, _, stream, size = string.find(breakpoint, "^204 Output (%w+) (%d+)$")
        if stream and size then
          local msg = client:receive(tonumber(size))
          print(msg)
          if outputs[stream] then
            outputs[stream](msg)
          end
          done = false
        end
      elseif status == "401" then
        local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)$")
        if size then
          local msg = client:receive(tonumber(size))
          print("Error in remote application: " .. msg)
          os.exit(1, true)
          return nil, nil, msg
        end
      else
        print("Unknown error")
        os.exit(1, true)
        return nil, nil, "Debugger error: unexpected response '" .. breakpoint .. "'"
      end
      if done then
        break
      end
    end
  elseif command == "done" then
    client:send(string.upper(command) .. "\n")
    if client:receive() ~= "200 OK" then
      print("Unknown error")
      os.exit(1, true)
      return nil, nil, "Debugger error: unexpected response after DONE"
    end
  elseif command == "setb" or command == "asetb" then
    _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
    if file and line then
      if not file:find("^\".*\"$") then
        file = string.gsub(file, "\\", "/")
        file = removebasedir(file, basedir)
      end
      client:send("SETB " .. file .. " " .. line .. "\n")
      if command == "asetb" or client:receive() == "200 OK" then
        set_breakpoint(file, line)
      else
        print("Error: breakpoint not inserted")
      end
    else
      print("Invalid command")
    end
  elseif command == "setw" then
    local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
    if exp then
      client:send("SETW " .. exp .. "\n")
      local answer = client:receive()
      local _, _, watch_idx = string.find(answer, "^200 OK (%d+)%s*$")
      if watch_idx then
        watches[watch_idx] = exp
        print("Inserted watch exp no. " .. watch_idx)
      else
        local _, _, size = string.find(answer, "^401 Error in Expression (%d+)$")
        if size then
          local err = client:receive(tonumber(size)):gsub(".-:%d+:%s*", "")
          print("Error: watch expression not set: " .. err)
        else
          print("Error: watch expression not set")
        end
      end
    else
      print("Invalid command")
    end
  elseif command == "delb" or command == "adelb" then
    _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
    if file and line then
      if not file:find("^\".*\"$") then
        file = string.gsub(file, "\\", "/")
        file = removebasedir(file, basedir)
      end
      client:send("DELB " .. file .. " " .. line .. "\n")
      if command == "adelb" or client:receive() == "200 OK" then
        remove_breakpoint(file, line)
      else
        print("Error: breakpoint not removed")
      end
    else
      print("Invalid command")
    end
  elseif command == "delallb" then
    for line, breaks in pairs(breakpoints) do
      for file, _ in pairs(breaks) do
        client:send("DELB " .. file .. " " .. line .. "\n")
        if client:receive() == "200 OK" then
          remove_breakpoint(file, line)
        else
          print("Error: breakpoint at file " .. file .. " line " .. line .. " not removed")
        end
      end
    end
  elseif command == "delw" then
    local _, _, index = string.find(params, "^[a-z]+%s+(%d+)%s*$")
    if index then
      client:send("DELW " .. index .. "\n")
      if client:receive() == "200 OK" then
        watches[index] = nil
      else
        print("Error: watch expression not removed")
      end
    else
      print("Invalid command")
    end
  elseif command == "delallw" then
    for index, exp in pairs(watches) do
      client:send("DELW " .. index .. "\n")
      if client:receive() == "200 OK" then
        watches[index] = nil
      else
        print("Error: watch expression at index " .. index .. " [" .. exp .. "] not removed")
      end
    end
  elseif command == "eval" or command == "exec" or command == "load" or command == "loadstring" or command == "reload" then
    local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
    if exp or command == "reload" then
      if command == "eval" or command == "exec" then
        exp = exp:gsub("%-%-%[(=*)%[.-%]%1%]", ""):gsub("%-%-.-\n", " "):gsub("\n", " ")
        if command == "eval" then
          exp = "return " .. exp
        end
        client:send("EXEC " .. exp .. "\n")
      elseif command == "reload" then
        client:send("LOAD 0 -\n")
      elseif command == "loadstring" then
        local _, _, _, file, lines = string.find(exp, "^([\"'])(.-)%1%s+(.+)")
        if not file then
          _, _, file, lines = string.find(exp, "^(%S+)%s+(.+)")
        end
        client:send("LOAD " .. #lines .. " " .. file .. "\n")
        client:send(lines)
      else
        local file = io.open(exp, "r")
        if not file and pcall(builtin_require, "winapi") then
          winapi.set_encoding(winapi.CP_UTF8)
          local shortp = winapi.short_path(exp)
          file = shortp and io.open(shortp, "r")
        end
        if not file then
          return nil, nil, "Cannot open file " .. exp
        end
        local lines = file:read("*all"):gsub("^#!.-\n", "\n")
        file:close()
        local file = string.gsub(exp, "\\", "/")
        file = removebasedir(file, basedir)
        client:send("LOAD " .. #lines .. " " .. file .. "\n")
        if 0 < #lines then
          client:send(lines)
        end
      end
      while true do
        local params, err = client:receive()
        if not params then
          return nil, nil, "Debugger connection " .. (err or "error")
        end
        local done = true
        local _, _, status, len = string.find(params, "^(%d+).-%s+(%d+)%s*$")
        if status == "200" then
          len = tonumber(len)
          if 0 < len then
            local status, res
            local str = client:receive(len)
            local func, err = loadstring(str)
            if func then
              status, res = pcall(func)
              if not status then
                err = res
              elseif type(res) ~= "table" then
                err = "received " .. type(res) .. " instead of expected 'table'"
              end
            end
            if err then
              print("Error in processing results: " .. err)
              return nil, nil, "Error in processing results: " .. err
            end
            print(unpack(res))
            return res[1], res
          end
        elseif status == "201" then
          _, _, file, line = string.find(params, "^201 Started%s+(.-)%s+(%d+)%s*$")
        elseif status == "202" or params == "200 OK" then
        elseif status == "204" then
          local _, _, stream, size = string.find(params, "^204 Output (%w+) (%d+)$")
          if stream and size then
            local msg = client:receive(tonumber(size))
            print(msg)
            if outputs[stream] then
              outputs[stream](msg)
            end
            done = false
          end
        elseif status == "401" then
          len = tonumber(len)
          local res = client:receive(len)
          print("Error in expression: " .. res)
          return nil, nil, res
        else
          print("Unknown error")
          return nil, nil, "Debugger error: unexpected response after EXEC/LOAD '" .. params .. "'"
        end
        if done then
          break
        end
      end
    else
      print("Invalid command")
    end
  elseif command == "listb" then
    for l, v in pairs(breakpoints) do
      for f in pairs(v) do
        print(f .. ": " .. l)
      end
    end
  elseif command == "listw" then
    for i, v in pairs(watches) do
      print("Watch exp. " .. i .. ": " .. v)
    end
  elseif command == "suspend" then
    client:send("SUSPEND\n")
  elseif command == "stack" then
    client:send("STACK\n")
    local resp = client:receive()
    local _, _, status, res = string.find(resp, "^(%d+)%s+%w+%s+(.+)%s*$")
    if status == "200" then
      local func, err = loadstring(res)
      if func == nil then
        print("Error in stack information: " .. err)
        return nil, nil, err
      end
      local ok, stack = pcall(func)
      if not ok then
        print("Error in stack information: " .. stack)
        return nil, nil, stack
      end
      for _, frame in ipairs(stack) do
        print(mobdebug.line(frame[1], {comment = false}))
      end
      return stack
    elseif status == "401" then
      local _, _, len = string.find(resp, "%s+(%d+)%s*$")
      len = tonumber(len)
      local res = 0 < len and client:receive(len) or "Invalid stack information."
      print("Error in expression: " .. res)
      return nil, nil, res
    else
      print("Unknown error")
      return nil, nil, "Debugger error: unexpected response after STACK"
    end
  elseif command == "output" then
    local _, _, stream, mode = string.find(params, "^[a-z]+%s+(%w+)%s+([dcr])%s*$")
    if stream and mode then
      client:send("OUTPUT " .. stream .. " " .. mode .. "\n")
      local resp = client:receive()
      local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
      if status == "200" then
        print("Stream " .. stream .. " redirected")
        outputs[stream] = type(options) == "table" and options.handler or nil
      else
        print("Unknown error")
        return nil, nil, "Debugger error: can't redirect " .. stream
      end
    else
      print("Invalid command")
    end
  elseif command == "basedir" then
    local _, _, dir = string.find(params, "^[a-z]+%s+(.+)$")
    if dir then
      dir = string.gsub(dir, "\\", "/")
      if not string.find(dir, "/$") then
        dir = dir .. "/"
      end
      local remdir = dir:match("\t(.+)")
      if remdir then
        dir = dir:gsub("/?\t.+", "/")
      end
      basedir = dir
      client:send("BASEDIR " .. (remdir or dir) .. "\n")
      local resp, err = client:receive()
      if not resp then
        print("Unknown error: " .. err)
        return nil, nil, "Debugger connection closed"
      end
      local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
      if status == "200" then
        print("New base directory is " .. basedir)
      else
        print("Unknown error")
        return nil, nil, "Debugger error: unexpected response after BASEDIR"
      end
    else
      print(basedir)
    end
  elseif command == "help" then
    print("setb <file> <line>    -- sets a breakpoint")
    print("delb <file> <line>    -- removes a breakpoint")
    print("delallb               -- removes all breakpoints")
    print("setw <exp>            -- adds a new watch expression")
    print("delw <index>          -- removes the watch expression at index")
    print("delallw               -- removes all watch expressions")
    print("run                   -- runs until next breakpoint")
    print("step                  -- runs until next line, stepping into function calls")
    print("over                  -- runs until next line, stepping over function calls")
    print("out                   -- runs until line after returning from current function")
    print("listb                 -- lists breakpoints")
    print("listw                 -- lists watch expressions")
    print("eval <exp>            -- evaluates expression on the current context and returns its value")
    print("exec <stmt>           -- executes statement on the current context")
    print("load <file>           -- loads a local file for debugging")
    print("reload                -- restarts the current debugging session")
    print("stack                 -- reports stack trace")
    print("output stdout <d|c|r> -- capture and redirect io stream (default|copy|redirect)")
    print("basedir [<path>]      -- sets the base path of the remote application, or shows the current one")
    print("done                  -- stops the debugger and continues application execution")
    print("exit                  -- exits debugger and the application")
  else
    local _, _, spaces = string.find(params, "^(%s*)$")
    if not spaces then
      print("Invalid command")
      return nil, nil, "Invalid command"
    end
  end
  return file, line
end
local listen = function(host, port)
  host = host or "*"
  port = port or mobdebug.port
  local socket = builtin_require("socket")
  print("Lua Remote Debugger")
  print("Run the program you wish to debug")
  local server = socket.bind(host, port)
  local client = server:accept()
  client:send("STEP\n")
  client:receive()
  local breakpoint = client:receive()
  local _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
  if file and line then
    print("Paused at file " .. file)
    print("Type 'help' for commands")
  else
    local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)%s*$")
    if size then
      print("Error in remote application: ")
      print(client:receive(size))
    end
  end
  while true do
    io.write("> ")
    local line = io.read("*line")
    handle(line, client)
  end
end
local cocreate
local coro = function()
  if cocreate then
    return
  end
  cocreate = cocreate or coroutine.create
  function coroutine.create(f, ...)
    return cocreate(function(...)
      mobdebug.on()
      return f(...)
    end, ...)
  end
end
local moconew
local moai = function()
  if moconew then
    return
  end
  moconew = not moconew and MOAICoroutine and MOAICoroutine.new
  if not moconew then
    return
  end
  function MOAICoroutine.new(...)
    local thread = moconew(...)
    local mt = thread.run and thread or getmetatable(thread)
    local patched = mt.run
    function mt:run(f, ...)
      return patched(self, function(...)
        mobdebug.on()
        return f(...)
      end, ...)
    end
    return thread
  end
end
mobdebug.setbreakpoint = set_breakpoint
mobdebug.removebreakpoint = remove_breakpoint
mobdebug.listen = listen
mobdebug.loop = loop
mobdebug.scratchpad = scratchpad
mobdebug.handle = handle
mobdebug.connect = connect
mobdebug.start = start
mobdebug.on = on
mobdebug.off = off
mobdebug.moai = moai
mobdebug.coro = coro
mobdebug.done = done
function mobdebug.pause()
  step_into = true
end
mobdebug.yield = nil
package.loaded.mobdebug = mobdebug
return mobdebug
