local function copyObject(o)
  local obj = {}
  for k,v in pairs(o) do
    obj[k] = v
  end
  setmetatable(obj, getmetatable(o))
  return obj
end

local function execDebug(env)
  local input = io.input()
  local result = { pcall(input.read, input, "*l") }
  if not result[1] then
    return false, result[2]
  end

  local func = load("return " .. result[2], "debug", "bt", env)
  if type(func) ~= "function" then
    func = load(result[2], "debug", "bt", env)
    if type(func) ~= "function" then
      return false, func
    end
  end

  return pcall(func)
end

local function debugREPL(onResult, debugArgs)
  local globalEnv = copyObject(_ENV)
  globalEnv._debug = debugArgs
  local shouldExit = false
  globalEnv.exit = function()
    shouldExit = true
  end
  local result, retval
  repeat
    result, retval = execDebug(globalEnv)
    if result and type(onResult) == "function" then
      onResult(retval)
    end
  until shouldExit or not result
  local success = result and shouldExit
  return success, (not success and retval) or nil
end

local function hookDebugger(context, Logger)
  Logger = Logger or require("logging").getGlobalLogger()

  local result, retval
  repeat
    result, retval = debugREPL(function(r)
      Logger:error("->", r)
    end, context)
    if not result then
      Logger:error("x>", retval)
    end
  until result
end

return { debugREPL = debugREPL, execDebug = execDebug, hookDebugger = hookDebugger }