
local _M = {}

----------------------------------------------------------------------------
-- Decode an URL-encoded string (see RFC 2396)
----------------------------------------------------------------------------
function _M.url_decode(str)
  if not str then return nil end
  str = string.gsub (str, "+", " ")
  str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end)
  str = string.gsub (str, "\r\n", "\n")
  return str
end

----------------------------------------------------------------------------
-- URL-encode a string (see RFC 2396)
----------------------------------------------------------------------------
function _M.url_encode(str)
  if not str then return nil end
  str = string.gsub (str, "\n", "\r\n")
  str = string.gsub (str, "([^%w ])",
        function (c) return string.format ("%%%02X", string.byte(c)) end)
  str = string.gsub (str, " ", "+")
  return str
end

----------------------------------------------------------------------------
-- Sanitizes all HTML tags
----------------------------------------------------------------------------
function _M.sanitize(text)
   return text:gsub(">", "&gt;"):gsub("<", "&lt;")
end

----------------------------------------------------------------------------
-- Checks whether s is not nil or the empty string
----------------------------------------------------------------------------
function _M.not_empty(s)
  if s and s ~= "" then return s else return nil end
end

----------------------------------------------------------------------------
-- Wraps the WSAPI environment to make the input rewindable, so you
-- can parse postdata more than once, call wsapi_env.input:rewind()
----------------------------------------------------------------------------
function _M.make_rewindable(wsapi_env)
   local new_env = { input = { position = 1, contents = "" } }
   function new_env.input:read(size)
      local left = #self.contents - self.position + 1
      local s
      if left < size then
         self.contents = self.contents .. wsapi_env.input:read(size - left)
         s = self.contents:sub(self.position)
         self.position = #self.contents + 1
      else
         s = self.contents:sub(self.position, self.position + size)
         self.position = self.position + size
      end
      if s == "" then return nil else return s end
   end
   function new_env.input:rewind()
      self.position = 1
   end
   return setmetatable(new_env, { __index = wsapi_env, __newindex = wsapi_env })
end

----------------------------------------------------------------------------
-- getopt, POSIX style command line argument parser
-- param arg contains the command line arguments in a standard table.
-- param options is a string with the letters that expect string values.
-- returns a table where associated keys are true, nil, or a string value.
-- The following example styles are supported
--   -a one  ==> opts["a"]=="one"
--   -bone   ==> opts["b"]=="one"
--   -c      ==> opts["c"]==true
--   --c=one ==> opts["c"]=="one"
--   -cdaone ==> opts["c"]==true opts["d"]==true opts["a"]=="one"
-- note POSIX demands the parser ends at the first non option
--      this behavior isn't implemented.
----------------------------------------------------------------------------
function _M.getopt( arg, options )
  local tab, args = {}, {}
  local k = 1
  while k <= #arg do
    local v = arg[k]
    if string.sub( v, 1, 2) == "--" then
      local x = string.find( v, "=", 1, true )
      if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
      else      tab[ string.sub( v, 3 ) ] = true
      end
      k = k + 1
    elseif string.sub( v, 1, 1 ) == "-" then
      local y = 2
      local l = #v
      local jopt
      local next = 1
      while ( y <= l ) do
        jopt = string.sub( v, y, y )
        if string.find( options, jopt, 1, true ) then
          if y < l then
            tab[ jopt ] = string.sub( v, y+1 )
            y = l
          else
            tab[ jopt ] = arg[ k + 1 ]
            next = 2
          end
        else
          tab[ jopt ] = true
        end
        y = y + 1
      end
      k = k + next
    else
      args[#args + 1] = v
      k = k + 1
    end
  end
  return tab, args
end

----------------------------------------------------------------------------
-- Makes a mock WSAPI environment with GET method and the provided
-- query string
----------------------------------------------------------------------------
function _M.make_env_get(qs)
  return {
    REQUEST_METHOD = "GET",
    QUERY_STRING = qs or "",
    CONTENT_LENGTH = 0,
    PATH_INFO = "/",
    SCRIPT_NAME = "",
    CONTENT_TYPE = "x-www-form-urlencoded",
    input = {
      read = function () return nil end
    },
    error = {
      messages = {},
      write = function (self, msg)
        self.messages[#self.messages+1] = msg
      end
    }
  }
end

----------------------------------------------------------------------------
-- Makes a mock WSAPI environment with POST method and the provided
-- postdata, type (x-www-form-urlenconded default) and query string
----------------------------------------------------------------------------
function _M.make_env_post(pd, type, qs)
  pd = pd or ""
  return {
    REQUEST_METHOD = "POST",
    QUERY_STRING = qs or "",
    CONTENT_LENGTH = #pd,
    PATH_INFO = "/",
    CONTENT_TYPE = type or "x-www-form-urlencoded",
    SCRIPT_NAME = "",
    input = {
      post_data = pd,
      current = 1,
      read = function (self, len)
        if self.current > #self.post_data then return nil end
        local s = self.post_data:sub(self.current, len)
        self.current = self.current + len
        return s
      end
    },
    error = {
      messages = {},
      write = function (self, msg)
        self.messages[#self.messages+1] = msg
      end
    }
  }
end

function _M.loadfile(filename, env)
  if _VERSION == "Lua 5.2" then
    return loadfile(filename, "bt", env)
  else
    local f, err = loadfile(filename)
    if not f then
      return nil, err
    end
    setfenv(f, env)
    return f
  end
end

return _M
