local _G, _VERSION = _G, _VERSION
local lua_version = _VERSION:sub(-3)

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


local M = _G

if lua_version < "5.3" then

   -- cache globals in upvalues
   local error, ipairs, pairs, pcall, require, select, setmetatable, type =
         error, ipairs, pairs, pcall, require, select, setmetatable, type
   local debug, io, math, package, string, table =
         debug, io, math, package, string, table
   local io_lines = io.lines
   local io_read = io.read
   local unpack = lua_version == "5.1" and unpack or table.unpack

   -- create module table
   M = {}
   local M_meta = {
      __index = _G,
      -- __newindex is set at the end
   }
   setmetatable(M, M_meta)

   -- create subtables
   M.io = setmetatable({}, { __index = io })
   M.math = setmetatable({}, { __index = math })
   M.string = setmetatable({}, { __index = string })
   M.table = setmetatable({}, { __index = table })
   M.utf8 = {}


   -- select the most powerful getmetatable function available
   local gmt = type(debug) == "table" and debug.getmetatable or
               getmetatable or function() return false end

   -- type checking functions
   local checkinteger -- forward declararation

   local function argcheck(cond, i, f, extra)
      if not cond then
         error("bad argument #"..i.." to '"..f.."' ("..extra..")", 0)
      end
   end


   -- load utf8 library
   local utf8_ok, utf8lib = pcall(require, "compat53.utf8")
   if utf8_ok then
      if lua_version == "5.1" then
         utf8lib.charpattern = "[%z\1-\127\194-\244][\128-\191]*"
      end
      for k,v in pairs(utf8lib) do
         M.utf8[k] = v
      end
      package.loaded["utf8"] = M.utf8
   end


   -- load table library
   local table_ok, tablib = pcall(require, "compat53.table")
   if table_ok then
      for k,v in pairs(tablib) do
         M.table[k] = v
      end
   end


   -- load string packing functions
   local str_ok, strlib = pcall(require, "compat53.string")
   if str_ok then
      for k,v in pairs(strlib) do
         M.string[k] = v
      end
   end


   -- try Roberto's struct module for string packing/unpacking if
   -- compat53.string is unavailable
   if not str_ok then
      local struct_ok, struct = pcall(require, "struct")
      if struct_ok then
         M.string.pack = struct.pack
         M.string.packsize = struct.size
         M.string.unpack = struct.unpack
      end
   end


   -- update math library
   do
      local maxint, minint = 1

      while maxint+1 > maxint and 2*maxint > maxint do
         maxint = maxint * 2
      end
      if 2*maxint <= maxint then
         maxint = 2*maxint-1
         minint = -maxint-1
      else
         maxint = maxint
         minint = -maxint
      end
      M.math.maxinteger = maxint
      M.math.mininteger = minint

      function M.math.tointeger(n)
         local n_type = type(n)
         if is_luajit then
            if n_type == "cdata" and tostring(n):match("U?LL$") then
               return n
            elseif n_type == "number" then
               if tostring(n):match("%-") then
                  return load("return " .. string.format("0x%xLL", n))()
               else
                  return load("return " .. string.format("0x%xULL", n))()
               end
            elseif n_type == "string" then
               if
                  string.match(n, "^0[xX][0-9a-fA-F]+$")
                  or
                  string.match(n, "^%-?[0-9]+$")
                  or
                  string.match(n, "^0[bB][0-1]+$")
               then
                  if n:match("%-") then
                     return load("return " .. n .. "LL")()
                  else
                     return load("return " .. n .. "ULL")()
                  end
               elseif string.match(n, "^%d+%.%d+[eE][%+%-]%d+$") then
                  local n_cdata_type = "ULL"
                  if n:match("%-") then n_cdata_type = "LL" end
                  return load("return "
                     .. string.format("0x%x%s", tonumber(n), n_cdata_type)
                  )()
               end
            end
         elseif
            n_type == "number" and n <= maxint and n >= minint and n % 1 == 0
         then
            return n
         end
         return nil
      end

      function M.math.type(n)
         local n_type = type(n)
         if n_type == "number" then
            if n <= maxint and n >= minint and n % 1 == 0 then
               return "integer"
            else
               return "float"
            end
         elseif is_luajit and n_type == "cdata" and tostring(n):match("U?LL$") then
            return "integer"
         else
            return nil
         end
      end

      function checkinteger(x, i, f)
         local t = type(x)
         if t ~= "number" then
            error("bad argument #"..i.." to '"..f..
                  "' (number expected, got "..t..")", 0)
         elseif x > maxint or x < minint or x % 1 ~= 0 then
            error("bad argument #"..i.." to '"..f..
                  "' (number has no integer representation)", 0)
         else
            return x
         end
      end

      function M.math.ult(m, n)
         if is_luajit then
            local m_int = M.math.tointeger(m)
            local n_int = M.math.tointeger(n)
            if not m_int or not n_int then
               error("bad arguments supplied, numbers expected", 0)
            else
               m = m_int
               n = n_int
            end
         else
            m = checkinteger(m, "1", "math.ult")
            n = checkinteger(n, "2", "math.ult")
         end
         if m >= 0 and n < 0 then
            return true
         elseif m < 0 and n >= 0 then
            return false
         else
            return m < n
         end
      end
   end


   -- assert should allow non-string error objects
   function M.assert(cond, ...)
      if cond then
         return cond, ...
      elseif select('#', ...) > 0 then
         error((...), 0)
      else
         error("assertion failed!", 0)
      end
   end


   -- ipairs should respect __index metamethod
   do
      local function ipairs_iterator(st, var)
         var = var + 1
         local val = st[var]
         if val ~= nil then
            return var, st[var]
         end
      end
      function M.ipairs(t)
         if gmt(t) ~= nil then -- t has metatable
            return ipairs_iterator, t, 0
         else
            return ipairs(t)
         end
      end
   end


   -- make '*' optional for io.read and io.lines
   do
      local function addasterisk(fmt)
         if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then
            return "*"..fmt
         else
            return fmt
         end
      end

      function M.io.read(...)
         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 io_read(unpack(args, 1, n))
            end
         end
         return io_read(...)
      end

      -- PUC-Rio Lua 5.1 uses a different implementation for io.lines!
      function M.io.lines(...)
         local n = select('#', ...)
         for i = 2, 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 io_lines(unpack(args, 1, n))
            end
         end
         return io_lines(...)
      end
   end


   -- update table library (if C module not available)
   if not table_ok then
      local table_concat = table.concat
      local table_insert = table.insert
      local table_remove = table.remove
      local table_sort = table.sort

      function M.table.concat(list, sep, i, j)
         local mt = gmt(list)
         if type(mt) == "table" and type(mt.__len) == "function" then
            local src = list
            list, i, j  = {}, i or 1, j or mt.__len(src)
            for k = i, j do
               list[k] = src[k]
            end
         end
         return table_concat(list, sep, i, j)
      end

      function M.table.insert(list, ...)
         local mt = gmt(list)
         local has_mt = type(mt) == "table"
         local has_len = has_mt and type(mt.__len) == "function"
         if has_mt and (has_len or mt.__index or mt.__newindex) then
            local e = (has_len and mt.__len(list) or #list)+1
            local nargs, pos, value = select('#', ...), ...
            if nargs == 1 then
               pos, value = e, pos
            elseif nargs == 2 then
               pos = checkinteger(pos, "2", "table.insert")
               argcheck(1 <= pos and pos <= e, "2", "table.insert",
                        "position out of bounds" )
            else
               error("wrong number of arguments to 'insert'", 0)
            end
            for i = e-1, pos, -1 do
               list[i+1] = list[i]
            end
            list[pos] = value
         else
            return table_insert(list, ...)
         end
      end

      function M.table.move(a1, f, e, t, a2)
         a2 = a2 or a1
         f = checkinteger(f, "2", "table.move")
         argcheck(f > 0, "2", "table.move",
                  "initial position must be positive")
         e = checkinteger(e, "3", "table.move")
         t = checkinteger(t, "4", "table.move")
         if e >= f then
            local m, n, d = 0, e-f, 1
            if t > f then m, n, d = n, m, -1 end
            for i = m, n, d do
               a2[t+i] = a1[f+i]
            end
         end
         return a2
      end

      function M.table.remove(list, pos)
         local mt = gmt(list)
         local has_mt = type(mt) == "table"
         local has_len = has_mt and type(mt.__len) == "function"
         if has_mt and (has_len or mt.__index or mt.__newindex) then
            local e = (has_len and mt.__len(list) or #list)
            pos = pos ~= nil and checkinteger(pos, "2", "table.remove") or e
            if pos ~= e then
               argcheck(1 <= pos and pos <= e+1, "2", "table.remove",
                        "position out of bounds" )
            end
            local result = list[pos]
            while pos < e do
               list[pos] = list[pos+1]
               pos = pos + 1
            end
            list[pos] = nil
            return result
         else
            return table_remove(list, pos)
         end
      end

      do
         local function pivot(list, cmp, a, b)
            local m = b - a
            if m > 2 then
               local c = a + (m-m%2)/2
               local x, y, z = list[a], list[b], list[c]
               if not cmp(x, y) then
                  x, y, a, b = y, x, b, a
               end
               if not cmp(y, z) then
                  y, b = z, c
               end
               if not cmp(x, y) then
                  y, b = x, a
               end
               return b, y
            else
               return b, list[b]
            end
         end

         local function lt_cmp(a, b)
            return a < b
         end

         local function qsort(list, cmp, b, e)
            if b < e then
               local i, j, k, val = b, e, pivot(list, cmp, b, e)
               while i < j do
                  while i < j and cmp(list[i], val) do
                     i = i + 1
                  end
                  while i < j and not cmp(list[j], val) do
                     j = j - 1
                  end
                  if i < j then
                     list[i], list[j] = list[j], list[i]
                     if i == k then k = j end -- update pivot position
                     i, j = i+1, j-1
                  end
               end
               if i ~= k and not cmp(list[i], val) then
                  list[i], list[k] = val, list[i]
                  k = i -- update pivot position
               end
               qsort(list, cmp, b, i == k and i-1 or i)
               return qsort(list, cmp, i+1, e)
            end
         end

         function M.table.sort(list, cmp)
            local mt = gmt(list)
            local has_mt = type(mt) == "table"
            local has_len = has_mt and type(mt.__len) == "function"
            if has_len then
               cmp = cmp or lt_cmp
               local len = mt.__len(list)
               return qsort(list, cmp, 1, len)
            else
               return table_sort(list, cmp)
            end
         end
      end

      local function unpack_helper(list, i, j, ...)
         if j < i then
            return ...
         else
            return unpack_helper(list, i, j-1, list[j], ...)
         end
      end
      function M.table.unpack(list, i, j)
         local mt = gmt(list)
         local has_mt = type(mt) == "table"
         local has_len = has_mt and type(mt.__len) == "function"
         if has_mt and (has_len or mt.__index) then
            i, j = i or 1, j or (has_len and mt.__len(list)) or #list
            return unpack_helper(list, i, j)
         else
            return unpack(list, i, j)
         end
      end
   end -- update table library


   -- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2
   if lua_version == "5.1" then
      -- cache globals in upvalues
      local load, loadfile, loadstring, setfenv, xpcall =
            load, loadfile, loadstring, setfenv, xpcall
      local coroutine, os = coroutine, os
      local coroutine_create = coroutine.create
      local coroutine_resume = coroutine.resume
      local coroutine_running = coroutine.running
      local coroutine_status = coroutine.status
      local coroutine_yield = coroutine.yield
      local io_input = io.input
      local io_open = io.open
      local io_output = io.output
      local io_write = io.write
      local math_log = math.log
      local os_execute = os.execute
      local string_find = string.find
      local string_format = string.format
      local string_gmatch = string.gmatch
      local string_gsub = string.gsub
      local string_match = string.match
      local string_rep = string.rep
      local table_concat = table.concat

      -- create subtables
      M.coroutine = setmetatable({}, { __index = coroutine })
      M.os = setmetatable({}, { __index = os })
      M.package = setmetatable({}, { __index = package })

      -- handle debug functions
      if type(debug) == "table" then
         local debug_setfenv = debug.setfenv
         local debug_getfenv = debug.getfenv
         local debug_setmetatable = debug.setmetatable

         M.debug = setmetatable({}, { __index = debug })

         if not is_luajit52 then
            function M.debug.setuservalue(obj, value)
               if type(obj) ~= "userdata" then
                  error("bad argument #1 to 'setuservalue' (userdata expected, got "..
                        type(obj)..")", 2)
               end
               if value == nil then value = _G end
               if type(value) ~= "table" then
                  error("bad argument #2 to 'setuservalue' (table expected, got "..
                        type(value)..")", 2)
               end
               return debug_setfenv(obj, value)
            end

            function M.debug.getuservalue(obj)
               if type(obj) ~= "userdata" then
                  return nil
               else
                  local v = debug_getfenv(obj)
                  if v == _G or v == package then
                     return nil
                  end
                  return v
               end
            end

            function M.debug.setmetatable(value, tab)
               debug_setmetatable(value, tab)
               return value
            end
         end -- not luajit with compat52 enabled
      end -- debug table available


      if not is_luajit52 then
         function M.pairs(t)
            local mt = gmt(t)
            if type(mt) == "table" and type(mt.__pairs) == "function" then
               return mt.__pairs(t)
            else
               return pairs(t)
            end
         end
      end


      if not is_luajit then
         local function check_mode(mode, prefix)
            local has = { text = false, binary = false }
            for i = 1,#mode do
               local c = mode:sub(i, i)
               if c == "t" then has.text = true end
               if c == "b" then has.binary = true end
            end
            local t = prefix:sub(1, 1) == "\27" and "binary" or "text"
            if not has[t] then
               return "attempt to load a "..t.." chunk (mode is '"..mode.."')"
            end
         end

         function M.load(ld, source, mode, env)
            mode = mode or "bt"
            local chunk, msg
            if type( ld ) == "string" then
               if mode ~= "bt" then
                  local merr = check_mode(mode, ld)
                  if merr then return nil, merr end
               end
               chunk, msg = loadstring(ld, source)
            else
               local ld_type = type(ld)
               if ld_type ~= "function" then
                  error("bad argument #1 to 'load' (function expected, got "..
                        ld_type..")", 2)
               end
               if mode ~= "bt" then
                  local checked, merr = false, nil
                  local function checked_ld()
                     if checked then
                        return ld()
                     else
                        checked = true
                        local v = ld()
                        merr = check_mode(mode, v or "")
                        if merr then return nil end
                        return v
                     end
                  end
                  chunk, msg = load(checked_ld, source)
                  if merr then return nil, merr end
               else
                  chunk, msg = load(ld, source)
               end
            end
            if not chunk then
               return chunk, msg
            end
            if env ~= nil then
               setfenv(chunk, env)
            end
            return chunk
         end

         M.loadstring = M.load

         function M.loadfile(file, mode, env)
            mode = mode or "bt"
            if mode ~= "bt" then
               local f = io_open(file, "rb")
               if f then
                  local prefix = f:read(1)
                  f:close()
                  if prefix then
                     local merr = check_mode(mode, prefix)
                     if merr then return nil, merr end
                  end
               end
            end
            local chunk, msg = loadfile(file)
            if not chunk then
               return chunk, msg
            end
            if env ~= nil then
               setfenv(chunk, env)
            end
            return chunk
         end
      end -- not luajit


      if not is_luajit52 then
         function M.rawlen(v)
            local t = type(v)
            if t ~= "string" and t ~= "table" then
               error("bad argument #1 to 'rawlen' (table or string expected)", 2)
            end
            return #v
         end
      end


      if not is_luajit then
         function M.xpcall(f, msgh, ...)
            local args, n = { ... }, select('#', ...)
            return xpcall(function() return f(unpack(args, 1, n)) end, msgh)
         end
      end


      if not is_luajit52 then
         function M.os.execute(cmd)
            local code = os_execute(cmd)
            -- Lua 5.1 does not report exit by signal.
            if code == 0 then
               return true, "exit", code
            else
               if package.config:sub(1, 1) == '/' then
                  code = code/256 -- only correct on Linux!
               end
               return nil, "exit", code
            end
         end
      end


      if not table_ok and not is_luajit52 then
         M.table.pack = function(...)
            return { n = select('#', ...), ... }
         end
      end


      local main_coroutine = coroutine_create(function() end)

      function M.coroutine.create(func)
         local success, result = pcall(coroutine_create, func)
         if not success then
            if type(func) ~= "function" then
               error("bad argument #1 (function expected)", 0)
             end
            result = coroutine_create(function(...) return func(...) end)
         end
         return result
      end

      if not is_luajit52 then
         function M.coroutine.running()
            local co = coroutine_running()
            if co then
               return co, false
            else
               return main_coroutine, true
            end
         end
      end

      function M.coroutine.yield(...)
         local co, flag = coroutine_running()
         if co and not flag then
            return coroutine_yield(...)
         else
            error("attempt to yield from outside a coroutine", 0)
         end
      end

      if not is_luajit then
         function M.coroutine.resume(co, ...)
            if co == main_coroutine then
               return false, "cannot resume non-suspended coroutine"
            else
               return coroutine_resume(co, ...)
            end
         end

         function M.coroutine.status(co)
            local notmain = coroutine_running()
            if co == main_coroutine then
               return notmain and "normal" or "running"
            else
               return coroutine_status(co)
            end
         end
      end -- not luajit


      if not is_luajit then
         M.math.log = function(x, base)
            if base ~= nil then
               return math_log(x)/math_log(base)
            else
               return math_log(x)
            end
         end
      end


      if not is_luajit then
         function M.package.searchpath(name, path, sep, rep)
            sep = (sep or "."):gsub("(%p)", "%%%1")
            rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1")
            local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1")
            local msg = {}
            for subpath in path:gmatch("[^;]+") do
               local fpath = subpath:gsub("%?", pname)
               local f = io_open(fpath, "r")
               if f then
                  f:close()
                  return fpath
               end
               msg[#msg+1] = "\n\tno file '" .. fpath .. "'"
            end
            return nil, table_concat(msg)
         end
      end


      if not COMPAT_DISABLE_FIX_PATTERN then
         local function fix_pattern(pattern)
            return (string_gsub(pattern, "%z", "%%z"))
         end

         function M.string.find(s, pattern, ...)
            return string_find(s, fix_pattern(pattern), ...)
         end

         function M.string.gmatch(s, pattern)
            return string_gmatch(s, fix_pattern(pattern))
         end

         function M.string.gsub(s, pattern, ...)
            return string_gsub(s, fix_pattern(pattern), ...)
         end

         function M.string.match(s, pattern, ...)
            return string_match(s, fix_pattern(pattern), ...)
         end
      end

      if not is_luajit then
         function M.string.rep(s, n, sep)
            if sep ~= nil and sep ~= "" and n >= 2 then
               return s .. string_rep(sep..s, n-1)
            else
               return string_rep(s, n)
            end
         end
      end

      if not is_luajit then
         do
            local addqt = {
               ["\n"] = "\\\n",
               ["\\"] = "\\\\",
               ["\""] = "\\\""
            }

            local function addquoted(c, d)
               return (addqt[c] or string_format(d~="" and "\\%03d" or "\\%d", c:byte()))..d
            end

            function M.string.format(fmt, ...)
               local args, n = { ... }, select('#', ...)
               local i = 0
               local function adjust_fmt(lead, mods, kind)
                  if #lead % 2 == 0 then
                     i = i + 1
                     if kind == "s" then
                        args[i] = _G.tostring(args[i])
                     elseif kind == "q" then
                        args[i] = '"'..string_gsub(args[i], "([%z%c\\\"\n])(%d?)", addquoted)..'"'
                        return lead.."%"..mods.."s"
                     end
                  end
               end
               fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt)
               return string_format(fmt, unpack(args, 1, n))
            end
         end
      end


      function M.io.write(...)
         local res, msg, errno = io_write(...)
         if res then
            return io_output()
         else
            return nil, msg, errno
         end
      end

      if not is_luajit then
         local function helper(st, var_1, ...)
            if var_1 == nil then
               if st.doclose then st.f:close() end
               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

         function M.io.lines(fname, ...)
            local doclose, file, msg
            if fname ~= nil then
               doclose, file, msg = true, io_open(fname, "r")
               if not file then error(msg, 2) end
            else
               doclose, file = false, io_input()
            end
            local st = { f=file, doclose=doclose, 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 -- lua 5.1

   -- further write should be forwarded to _G
   M_meta.__newindex = _G

end -- lua < 5.3


-- return module table
return M

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