local base64 = {}
local extract = _G.bit32 and _G.bit32.extract
if not extract then
  if _G.bit then
    local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
    
    function extract(v, from, width)
      return band(shr(v, from), shl(1, width) - 1)
    end
  elseif _G._VERSION == "Lua 5.1" then
    function extract(v, from, width)
      local w = 0
      
      local flag = 2 ^ from
      for i = 0, width - 1 do
        local flag2 = flag + flag
        if flag <= v % flag2 then
          w = w + 2 ^ i
        end
        flag = flag2
      end
      return w
    end
  else
    extract = load([[
return function( v, from, width )
			return ( v >> from ) & ((1 << width) - 1)
		end]])()
  end
end

function base64.makeencoder(s62, s63, spad)
  local encoder = {}
  for b64code, char in pairs({
    [0] = "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "a",
    "b",
    "c",
    "d",
    "e",
    "f",
    "g",
    "h",
    "i",
    "j",
    "k",
    "l",
    "m",
    "n",
    "o",
    "p",
    "q",
    "r",
    "s",
    "t",
    "u",
    "v",
    "w",
    "x",
    "y",
    "z",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    s62 or "+",
    s63 or "/",
    spad or "="
  }) do
    encoder[b64code] = char:byte()
  end
  return encoder
end

function base64.makedecoder(s62, s63, spad)
  local decoder = {}
  for b64code, charcode in pairs(base64.makeencoder(s62, s63, spad)) do
    decoder[charcode] = b64code
  end
  return decoder
end

local DEFAULT_ENCODER = base64.makeencoder()
local DEFAULT_DECODER = base64.makedecoder()
local char, concat = string.char, table.concat

function base64.encode(str, encoder, usecaching)
  encoder = encoder or DEFAULT_ENCODER
  local t, k, n = {}, 1, #str
  local lastn = n % 3
  local cache = {}
  for i = 1, n - lastn, 3 do
    local a, b, c = str:byte(i, i + 2)
    local v = a * 65536 + b * 256 + c
    local s
    if usecaching then
      s = cache[v]
      if not s then
        s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
        cache[v] = s
      end
    else
      s = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[extract(v, 0, 6)])
    end
    t[k] = s
    k = k + 1
  end
  if lastn == 2 then
    local a, b = str:byte(n - 1, n)
    local v = a * 65536 + b * 256
    t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[extract(v, 6, 6)], encoder[64])
  elseif lastn == 1 then
    local v = str:byte(n) * 65536
    t[k] = char(encoder[extract(v, 18, 6)], encoder[extract(v, 12, 6)], encoder[64], encoder[64])
  end
  return concat(t)
end

function base64.decode(b64, decoder, usecaching)
  decoder = decoder or DEFAULT_DECODER
  local pattern = "[^%w%+%/%=]"
  if decoder then
    local s62, s63
    for charcode, b64code in pairs(decoder) do
      if b64code == 62 then
        s62 = charcode
      elseif b64code == 63 then
        s63 = charcode
      end
    end
    pattern = ("[^%%w%%%s%%%s%%=]"):format(char(s62), char(s63))
  end
  b64 = b64:gsub(pattern, "")
  local cache = usecaching and {}
  local t, k = {}, 1
  local n = #b64
  local padding = b64:sub(-2) == "==" and 2 or b64:sub(-1) == "=" and 1 or 0
  for i = 1, 0 < padding and n - 4 or n, 4 do
    local a, b, c, d = b64:byte(i, i + 3)
    local s
    if usecaching then
      local v0 = a * 16777216 + b * 65536 + c * 256 + d
      s = cache[v0]
      if not s then
        local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
        s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
        cache[v0] = s
      end
    else
      local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64 + decoder[d]
      s = char(extract(v, 16, 8), extract(v, 8, 8), extract(v, 0, 8))
    end
    t[k] = s
    k = k + 1
  end
  if padding == 1 then
    local a, b, c = b64:byte(n - 3, n - 1)
    local v = decoder[a] * 262144 + decoder[b] * 4096 + decoder[c] * 64
    t[k] = char(extract(v, 16, 8), extract(v, 8, 8))
  elseif padding == 2 then
    local a, b = b64:byte(n - 3, n - 2)
    local v = decoder[a] * 262144 + decoder[b] * 4096
    t[k] = char(extract(v, 16, 8))
  end
  return concat(t)
end

return base64
