local lua_version = _VERSION:sub(-3)


if lua_version < "5.3" then

   local _G, pairs, require, select, type =
         _G, pairs, require, select, type
   local debug, io = debug, io
   local unpack = lua_version == "5.1" and unpack or table.unpack

   local M = require("compat53.module")

   -- select the most powerful getmetatable function available
   local gmt = type(debug) == "table" and debug.getmetatable or
               getmetatable or function() return false end
   -- metatable for file objects from Lua's standard io library
   local file_meta = gmt(io.stdout)


   -- make '*' optional for file:read and file:lines
   if type(file_meta) == "table" and type(file_meta.__index) == "table" then

      local function addasterisk(fmt)
         if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
            return "*"..fmt
         else
            return fmt
         end
      end

      local file_lines = file_meta.__index.lines
      file_meta.__index.lines = function(self, ...)
         local n = select('#', ...)
         for i = 1, n do
            local a = select(i, ...)
            local b = addasterisk(a)
            -- as an optimization we only allocate a table for the
            -- modified format arguments when we have a '*' somewhere
            if a ~= b then
               local args = { ... }
               args[i] = b
               for j = i+1, n do
                  args[j] = addasterisk(args[j])
               end
               return file_lines(self, unpack(args, 1, n))
            end
         end
         return file_lines(self, ...)
      end

      local file_read = file_meta.__index.read
      file_meta.__index.read = function(self, ...)
         local n = select('#', ...)
         for i = 1, n do
            local a = select(i, ...)
            local b = addasterisk(a)
            -- as an optimization we only allocate a table for the
            -- modified format arguments when we have a '*' somewhere
            if a ~= b then
               local args = { ... }
               args[i] = b
               for j = i+1, n do
                  args[j] = addasterisk(args[j])
               end
               return file_read(self, unpack(args, 1, n))
            end
         end
         return file_read(self, ...)
      end

   end -- got a valid metatable for file objects


   -- changes for Lua 5.1 only
   if lua_version == "5.1" then

      -- cache globals
      local error, pcall, rawset, setmetatable, tostring, xpcall =
            error, pcall, rawset, setmetatable, tostring, xpcall
      local coroutine, package, string = coroutine, package, string
      local coroutine_resume = coroutine.resume
      local coroutine_running = coroutine.running
      local coroutine_status = coroutine.status
      local coroutine_yield = coroutine.yield
      local io_type = io.type


      -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag)
      local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ"
      local is_luajit52 = is_luajit and
        #setmetatable({}, { __len = function() return 1 end }) == 1


      -- make package.searchers available as an alias for package.loaders
      local p_index = { searchers = package.loaders }
      setmetatable(package, {
         __index = p_index,
         __newindex = function(p, k, v)
            if k == "searchers" then
               rawset(p, "loaders", v)
               p_index.searchers = v
            else
               rawset(p, k, v)
            end
         end
      })


      if type(file_meta) == "table" and type(file_meta.__index) == "table" then
         if not is_luajit then
            local function helper(_, var_1, ...)
               if var_1 == nil then
                  if (...) ~= nil then
                     error((...), 2)
                  end
               end
               return var_1, ...
            end

            local function lines_iterator(st)
               return helper(st, st.f:read(unpack(st, 1, st.n)))
            end

            local file_write = file_meta.__index.write
            file_meta.__index.write = function(self, ...)
               local res, msg, errno = file_write(self, ...)
               if res then
                  return self
               else
                  return nil, msg, errno
               end
            end

            file_meta.__index.lines = function(self, ...)
               if io_type(self) == "closed file" then
                  error("attempt to use a closed file", 2)
               end
               local st = { f=self, n=select('#', ...), ... }
               for i = 1, st.n do
                  local t = type(st[i])
                  if t == "string" then
                     local fmt = st[i]:match("^*?([aln])")
                     if not fmt then
                        error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
                     end
                     st[i] = "*"..fmt
                  elseif t ~= "number" then
                     error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2)
                  end
               end
               return lines_iterator, st
            end
         end -- not luajit
      end -- file_meta valid


      -- the (x)pcall implementations start a new coroutine internally
      -- to allow yielding even in Lua 5.1. to allow for accurate
      -- stack traces we keep track of the nested coroutine activations
      -- in the weak tables below:
      local weak_meta = { __mode = "kv" }
      -- maps the internal pcall coroutines to the user coroutine that
      -- *should* be running if pcall didn't use coroutines internally
      local pcall_mainOf = setmetatable({}, weak_meta)
      -- table that maps each running coroutine started by pcall to
      -- the coroutine that resumed it (user coroutine *or* pcall
      -- coroutine!)
      local pcall_previous = setmetatable({}, weak_meta)
      -- reverse of `pcall_mainOf`. maps a user coroutine to the
      -- currently active pcall coroutine started within it
      local pcall_callOf = setmetatable({}, weak_meta)
      -- similar to `pcall_mainOf` but is used only while executing
      -- the error handler of xpcall (thus no nesting is necessary!)
      local xpcall_running = setmetatable({}, weak_meta)

      -- handle debug functions
      if type(debug) == "table" then
         local debug_getinfo = debug.getinfo
         local debug_traceback = debug.traceback

         if not is_luajit then
            local function calculate_trace_level(co, level)
               if level ~= nil then
                  for out = 1, 1/0 do
                     local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "")
                     if info == nil then
                        local max = out-1
                        if level <= max then
                           return level
                        end
                        return nil, level-max
                     end
                  end
               end
               return 1
            end

            local stack_pattern = "\nstack traceback:"
            local stack_replace = ""
            function debug.traceback(co, msg, level)
               local lvl
               local nilmsg
               if type(co) ~= "thread" then
                  co, msg, level = coroutine_running(), co, msg
               end
               if msg == nil then
                  msg = ""
                  nilmsg = true
               elseif type(msg) ~= "string" then
                  return msg
               end
               if co == nil then
                  msg = debug_traceback(msg, level or 1)
               else
                  local xpco = xpcall_running[co]
                  if xpco ~= nil then
                     lvl, level = calculate_trace_level(xpco, level)
                     if lvl then
                        msg = debug_traceback(xpco, msg, lvl)
                     else
                        msg = msg..stack_pattern
                     end
                     lvl, level = calculate_trace_level(co, level)
                     if lvl then
                        local trace = debug_traceback(co, "", lvl)
                        msg = msg..trace:gsub(stack_pattern, stack_replace)
                     end
                  else
                     co = pcall_callOf[co] or co
                     lvl, level = calculate_trace_level(co, level)
                     if lvl then
                        msg = debug_traceback(co, msg, lvl)
                     else
                        msg = msg..stack_pattern
                     end
                  end
                  co = pcall_previous[co]
                  while co ~= nil do
                     lvl, level = calculate_trace_level(co, level)
                     if lvl then
                        local trace = debug_traceback(co, "", lvl)
                        msg = msg..trace:gsub(stack_pattern, stack_replace)
                     end
                     co = pcall_previous[co]
                  end
               end
               if nilmsg then
                  msg = msg:gsub("^\n", "")
               end
               msg = msg:gsub("\n\t%(tail call%): %?", "\000")
               msg = msg:gsub("\n\t%.%.%.\n", "\001\n")
               msg = msg:gsub("\n\t%.%.%.$", "\001")
               msg = msg:gsub("(%z+)\001(%z+)", function(some, other)
                  return "\n\t(..."..#some+#other.."+ tail call(s)...)"
               end)
               msg = msg:gsub("\001(%z+)", function(zeros)
                  return "\n\t(..."..#zeros.."+ tail call(s)...)"
               end)
               msg = msg:gsub("(%z+)\001", function(zeros)
                  return "\n\t(..."..#zeros.."+ tail call(s)...)"
               end)
               msg = msg:gsub("%z+", function(zeros)
                  return "\n\t(..."..#zeros.." tail call(s)...)"
               end)
               msg = msg:gsub("\001", function()
                  return "\n\t..."
               end)
               return msg
            end
         end -- is not luajit
      end -- debug table available


      if not is_luajit52 then
         local coroutine_running52 = M.coroutine.running
         function M.coroutine.running()
            local co, ismain = coroutine_running52()
            if ismain then
               return co, true
            else
               return pcall_mainOf[co] or co, false
            end
         end
      end

      if not is_luajit then
         local function pcall_results(current, call, success, ...)
            if coroutine_status(call) == "suspended" then
               return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...)))
            end
            if pcall_previous then
               pcall_previous[call] = nil
               local main = pcall_mainOf[call]
               if main == current then current = nil end
               pcall_callOf[main] = current
            end
            pcall_mainOf[call] = nil
            return success, ...
         end

         local function pcall_exec(current, call, ...)
            local main = pcall_mainOf[current] or current
            pcall_mainOf[call] = main
            if pcall_previous then
               pcall_previous[call] = current
               pcall_callOf[main] = call
            end
            return pcall_results(current, call, coroutine_resume(call, ...))
         end

         local coroutine_create52 = M.coroutine.create

         local function pcall_coroutine(func)
            if type(func) ~= "function" then
               local callable = func
               func = function (...) return callable(...) end
            end
            return coroutine_create52(func)
         end

         function M.pcall(func, ...)
            local current = coroutine_running()
            if not current then return pcall(func, ...) end
            return pcall_exec(current, pcall_coroutine(func), ...)
         end

         local function xpcall_catch(current, call, msgh, success, ...)
            if not success then
               xpcall_running[current] = call
               local ok, result = pcall(msgh, ...)
               xpcall_running[current] = nil
               if not ok then
                  return false, "error in error handling ("..tostring(result)..")"
               end
               return false, result
            end
            return true, ...
         end

         function M.xpcall(f, msgh, ...)
            local current = coroutine_running()
            if not current then
               local args, n = { ... }, select('#', ...)
               return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
            end
            local call = pcall_coroutine(f)
            return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...))
         end
      end -- not luajit

   end -- lua 5.1


   -- handle exporting to global scope
   local function extend_table(from, to)
      if from ~= to then
         for k,v in pairs(from) do
            if type(v) == "table" and
               type(to[k]) == "table" and
               v ~= to[k] then
               extend_table(v, to[k])
            else
               to[k] = v
            end
         end
      end
   end

   extend_table(M, _G)

end -- lua < 5.3

-- vi: set expandtab softtabstop=3 shiftwidth=3 :
