-- The MIT License (MIT)
--
-- Copyright (c) 2013 - 2015 Peter Melnichenko, 2016 Joan Puigcerver
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the "Software"),
-- to deal in the Software without restriction, including without limitation
-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
-- and/or sell copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-- IN THE SOFTWARE.

local function deep_update(t1, t2)
  for k, v in pairs(t2) do
    if type(v) == "table" then
      v = deep_update({}, v)
    end

    t1[k] = v
  end

  return t1
end

-- A property is a tuple {name, callback}.
-- properties.args is number of properties that can be set as arguments
-- when calling an object.
local function class(prototype, properties, parent)
  -- Class is the metatable of its instances.
  local cl = {}
  cl.__index = cl

  if parent then
    cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
  else
    cl.__prototype = prototype
  end

  if properties then
    local names = {}

    -- Create setter methods and fill set of property names.
    for _, property in ipairs(properties) do
      local name, callback = property[1], property[2]

      cl[name] = function(self, value)
	if not callback(self, value) then
	  self["_" .. name] = value
	end

	return self
      end

      names[name] = true
    end

    function cl.__call(self, ...)
      -- When calling an object, if the first argument is a table,
      -- interpret keys as property names, else delegate arguments
      -- to corresponding setters in order.
      if type((...)) == "table" then
	for name, value in pairs((...)) do
	  if names[name] then
	    self[name](self, value)
	  end
	end
      else
	local nargs = select("#", ...)

	for i, property in ipairs(properties) do
	  if i > nargs or i > properties.args then
	    break
	  end

	  local arg = select(i, ...)

	  if arg ~= nil then
	    self[property[1]](self, arg)
	  end
	end
      end

      return self
    end
  end

  -- If indexing class fails, fallback to its parent.
  local class_metatable = {}
  class_metatable.__index = parent

  function class_metatable.__call(self, ...)
    -- Calling a class returns its instance.
    -- Arguments are delegated to the instance.
    local object = deep_update({}, self.__prototype)
    setmetatable(object, self)
    return object(...)
  end

  return setmetatable(cl, class_metatable)
end

local function typecheck(name, types, value)
  for _, type_ in ipairs(types) do
    if type(value) == type_ then
      return true
    end
  end
  error(("bad property '%s' (%s expected, got %s)"):format(
      name, table.concat(types, " or "), type(value)))
end

local function typechecked(name, ...)
  local types = {...}
  return {name, function(_, value) typecheck(name, types, value) end}
end

local multiname = {"name", function(self, value)
		     typecheck("name", {"string"}, value)

		     for alias in value:gmatch("%S+") do
		       self._name = self._name or alias
		       table.insert(self._aliases, alias)
		     end

		     -- Do not set _name as with other properties.
		     return true
end}

local function parse_boundaries(str)
  if tonumber(str) then
    return tonumber(str), tonumber(str)
  end

  if str == "*" then
    return 0, math.huge
  end

  if str == "+" then
    return 1, math.huge
  end

  if str == "?" then
    return 0, 1
  end

  if str:match "^%d+%-%d+$" then
    local min, max = str:match "^(%d+)%-(%d+)$"
    return tonumber(min), tonumber(max)
  end

  if str:match "^%d+%+$" then
    local min = str:match "^(%d+)%+$"
    return tonumber(min), math.huge
  end
end

local function boundaries(name)
  return {name, function(self, value)
	    typecheck(name, {"number", "string"}, value)

	    local min, max = parse_boundaries(value)

	    if not min then
	      error(("bad property '%s'"):format(name))
	    end

	    self["_min" .. name], self["_max" .. name] = min, max
  end}
end

local actions = {}

local option_action = {
  "action",
  function(_, value)
    typecheck("action", {"function", "string"}, value)
    if type(value) == "string" and not actions[value] then
      error(("unknown action '%s'"):format(value))
    end
  end
}

local option_init = {
  "init",
  function(self)
    self._has_init = true
  end
}

local option_default = {
  "default",
  function(self, value)
    if type(value) ~= "string" then
      self._init = value
      self._has_init = true
      return true
    end
  end
}

local add_help = {
  "add_help",
  function(self, value)
    typecheck("add_help", {"boolean", "string", "table"}, value)

    if self._has_help then
      table.remove(self._options)
      self._has_help = false
    end

    if value then
      local help = self:flag()
	:description "Show the help message and exit."
	:action(function()
	    print(self:get_help(false))
	    os.exit(0)
	       end)

      local fullhelp = self:flag()
	:description "Show the help message with advanced options and exit."
	:action(function()
	    print(self:get_help(true))
	    os.exit(0)
	       end)

      if value ~= true then
	help = help(value)
	fullhelp = fullhelp(value)
      end

      if not help._name then
	help "--help" "-h"
	fullhelp "--help_full" "-H"
      end

      self._has_help = true
    end
  end
}

local Parser = class(
  {
    _arguments = {},
    _options = {},
    _commands = {},
    _mutexes = {},
    _require_command = true,
    _handle_options = true
  },
  {
    args = 3,
    typechecked("name", "string"),
    typechecked("description", "string"),
    typechecked("epilog", "string"),
    typechecked("usage", "string"),
    typechecked("help", "string"),
    typechecked("require_command", "boolean"),
    typechecked("handle_options", "boolean"),
    typechecked("action", "function"),
    typechecked("command_target", "string"),
    add_help
})

local Command = class(
  {
    _aliases = {}
  },
  {
    args = 3,
    multiname,
    typechecked("description", "string"),
    typechecked("epilog", "string"),
    typechecked("target", "string"),
    typechecked("usage", "string"),
    typechecked("help", "string"),
    typechecked("require_command", "boolean"),
    typechecked("handle_options", "boolean"),
    typechecked("action", "function"),
    typechecked("command_target", "string"),
    add_help
  }, Parser)

local Argument = class(
  {
    _minargs = 1,
    _maxargs = 1,
    _mincount = 1,
    _maxcount = 1,
    _defmode = "unused",
    _show_default = true,
    _asserts = {}
  },
  {
    args = 5,
    typechecked("name", "string"),
    typechecked("description", "string"),
    option_default,
    typechecked("convert", "function", "table"),
    boundaries("args"),
    typechecked("target", "string"),
    typechecked("defmode", "string"),
    typechecked("show_default", "boolean"),
    typechecked("argname", "string", "table"),
    typechecked("tostring", "function"),
    option_action,
    option_init
})

local Option = class(
  {
    _aliases = {},
    _mincount = 0,
    _overwrite = true,
    _asserts = {}
  },
  {
    args = 6,
    multiname,
    typechecked("description", "string"),
    option_default,
    typechecked("convert", "function", "table"),
    boundaries("args"),
    boundaries("count"),
    typechecked("target", "string"),
    typechecked("defmode", "string"),
    typechecked("show_default", "boolean"),
    typechecked("overwrite", "boolean"),
    typechecked("argname", "string", "table"),
    typechecked("advanced", "boolean"),
    option_action,
    option_init
  }, Argument)

function Argument:_get_argument_list()
  local buf = {}
  local i = 1

  while i <= math.min(self._minargs, 3) do
    local argname = self:_get_argname(i)

    if self._default and self._defmode:find "a" then
      argname = "[" .. argname .. "]"
    end

    table.insert(buf, argname)
    i = i+1
  end

  while i <= math.min(self._maxargs, 3) do
    table.insert(buf, "[" .. self:_get_argname(i) .. "]")
    i = i+1

    if self._maxargs == math.huge then
      break
    end
  end

  if i < self._maxargs then
    table.insert(buf, "...")
  end

  return buf
end

function Argument:_get_usage()
  local usage = table.concat(self:_get_argument_list(), " ")

  if self._default and self._defmode:find "u" then
    if self._maxargs > 1 or
    (self._minargs == 1 and not self._defmode:find "a") then
      usage = "[" .. usage .. "]"
    end
  end

  return usage
end

function actions.store_true(result, target)
  result[target] = true
end

function actions.store_false(result, target)
  result[target] = false
end

function actions.store(result, target, argument)
  result[target] = argument
end

function actions.count(result, target, _, overwrite)
  if not overwrite then
    result[target] = result[target] + 1
  end
end

function actions.append(result, target, argument, overwrite)
  result[target] = result[target] or {}
  table.insert(result[target], argument)

  if overwrite then
    table.remove(result[target], 1)
  end
end

function actions.concat(result, target, arguments, overwrite)
  if overwrite then
    error("'concat' action can't handle too many invocations")
  end

  result[target] = result[target] or {}

  for _, argument in ipairs(arguments) do
    table.insert(result[target], argument)
  end
end

function Argument:_get_action()
  local action, init

  if self._maxcount == 1 then
    if self._maxargs == 0 then
      action, init = "store_true", nil
    else
      action, init = "store", nil
    end
  else
    if self._maxargs == 0 then
      action, init = "count", 0
    else
      action, init = "append", {}
    end
  end

  if self._action then
    action = self._action
  end

  if self._has_init then
    init = self._init
  end

  if type(action) == "string" then
    action = actions[action]
  end

  return action, init
end

-- Returns placeholder for `narg`-th argument.
function Argument:_get_argname(narg)
  local argname = self._argname or self:_get_default_argname()

  if type(argname) == "table" then
    return argname[narg]
  else
    return argname
  end
end

function Argument:_get_default_argname()
  return "<" .. self._name .. ">"
end

function Option:_get_default_argname()
  return "<" .. self:_get_default_target() .. ">"
end

-- Returns label to be shown in the help message.
function Argument:_get_label()
  return self._name
end

function Option:_get_label()
  local variants = {}
  local argument_list = self:_get_argument_list()
  table.insert(argument_list, 1, nil)

  for _, alias in ipairs(self._aliases) do
    argument_list[1] = alias
    table.insert(variants, table.concat(argument_list, " "))
  end

  return table.concat(variants, ", ")
end

function Command:_get_label()
  return table.concat(self._aliases, ", ")
end

function Argument:_get_description()
  if self._default and self._show_default then
    if self._description then
      return ("%s (default: %s)"):format(self._description, self._default)
    else
      return ("default: %s"):format(self._default)
    end
  elseif self._has_init then
    local _tostring = self._tostring or tostring
    if type(self._init) == 'table' then
      _tostring = function(t)
	return table.concat(table.map(t, self._tostring or tostring), ' ')
      end
    end
    if self._description then
      return ("%s (default: %s)"):format(self._description,
					 _tostring(self._init))
    else
      return ("default: %s"):format(_tostring(self._init))
    end
  else
    return self._description or ""
  end
end

function Command:_get_description()
  return self._description or ""
end

function Option:_get_usage()
  local usage = self:_get_argument_list()
  table.insert(usage, 1, self._name)
  usage = table.concat(usage, " ")

  if self._mincount == 0 or self._default then
    usage = "[" .. usage .. "]"
  end

  return usage
end

function Argument:_get_default_target()
  return self._name
end

function Option:_get_default_target()
  local res

  for _, alias in ipairs(self._aliases) do
    if alias:sub(1, 1) == alias:sub(2, 2) then
      res = alias:sub(3)
      break
    end
  end

  res = res or self._name:sub(2)
  return (res:gsub("-", "_"))
end

function Option:_is_vararg()
  return self._maxargs ~= self._minargs
end

function Parser:_get_fullname()
  local parent = self._parent
  local buf = {self._name}

  while parent do
    table.insert(buf, 1, parent._name)
    parent = parent._parent
  end

  return table.concat(buf, " ")
end

function Parser:_update_charset(charset)
  charset = charset or {}

  for _, command in ipairs(self._commands) do
    command:_update_charset(charset)
  end

  for _, option in ipairs(self._options) do
    for _, alias in ipairs(option._aliases) do
      charset[alias:sub(1, 1)] = true
    end
  end

  return charset
end

function Parser:argument(...)
  local argument = Argument(...)
  table.insert(self._arguments, argument)
  return argument
end

function Parser:option(...)
  local option = Option(...)

  if self._has_help then
    table.insert(self._options, #self._options, option)
  else
    table.insert(self._options, option)
  end

  return option
end

function Parser:flag(...)
  return self:option():args(0)(...)
end

function Parser:command(...)
  local command = Command():add_help(true)(...)
  command._parent = self
  table.insert(self._commands, command)
  return command
end

function Parser:mutex(...)
  local options = {...}

  for i, option in ipairs(options) do
    assert(getmetatable(option) == Option,
	   ("bad argument #%d to 'mutex' (Option expected)"):format(i))
  end

  table.insert(self._mutexes, options)
  return self
end

local max_usage_width = 70
local usage_welcome = "Usage: "

function Parser:get_usage(advanced)
  advanced = advanced or false
  if self._usage then
    return self._usage
  end

  local lines = {usage_welcome .. self:_get_fullname()}

  local function add(s)
    if #lines[#lines]+1+#s <= max_usage_width then
      lines[#lines] = lines[#lines] .. " " .. s
    else
      lines[#lines+1] = (" "):rep(#usage_welcome) .. s
    end
  end

  -- This can definitely be refactored into something cleaner
  local mutex_options = {}
  local vararg_mutexes = {}

  -- First, put mutexes which do not contain vararg options and remember
  -- those which do
  for _, mutex in ipairs(self._mutexes) do
    local buf = {}
    local is_vararg = false

    for _, option in ipairs(mutex) do
      if option:_is_vararg() then
	is_vararg = true
      end

      table.insert(buf, option:_get_usage())
      mutex_options[option] = true
    end

    local repr = "(" .. table.concat(buf, " | ") .. ")"

    if is_vararg then
      table.insert(vararg_mutexes, repr)
    else
      add(repr)
    end
  end

  -- Second, put regular options
  for _, option in ipairs(self._options) do
    if not mutex_options[option] and not option:_is_vararg() and
    advanced or not option._advanced then
      add(option:_get_usage())
    end
  end

  -- Put mutexes containing vararg options
  for _, mutex_repr in ipairs(vararg_mutexes) do
    add(mutex_repr)
  end

  -- Options with variable number of arguments
  for _, option in ipairs(self._options) do
    if not mutex_options[option] and option:_is_vararg() then
      add(option:_get_usage())
    end
  end

  -- Put positional arguments
  for _, argument in ipairs(self._arguments) do
    add(argument:_get_usage())
  end


  if #self._commands > 0 then
    if self._require_command then
      add("<command>")
    else
      add("[<command>]")
    end

    add("...")
  end

  return table.concat(lines, "\n")
end

local margin_len = 2
local margin_len2 = 20
local margin = (" "):rep(margin_len)
local margin2 = (" "):rep(margin_len2)

local function make_two_columns(s1, s2)
  if s2 == "" then
    return margin .. s1
  end

  s2 = s2:gsub("\n", "\n" .. margin2)

  if #s1 < (margin_len2-margin_len) then
    return margin .. s1 .. (" "):rep(margin_len2-margin_len-#s1) .. s2
  else
    return margin .. s1 .. "\n" .. margin2 .. s2
  end
end

function Parser:get_help(advanced)
  advanced = advanced or false
  if self._help then
    return self._help
  end

  local blocks = {self:get_usage(advanced)}

  if self._description then
    table.insert(blocks, self._description)
  end

  local labels = {"Arguments:", "Options:", "Commands:"}

  for i, elements in ipairs{self._arguments, self._options, self._commands} do
    if #elements > 0 then
      local buf = {labels[i]}

      for _, element in ipairs(elements) do
	if advanced or not element._advanced then
	  table.insert(buf, make_two_columns(element:_get_label(),
					     element:_get_description()))
	end
      end

      table.insert(blocks, table.concat(buf, "\n"))
    end
  end

  if self._epilog then
    table.insert(blocks, self._epilog)
  end

  return table.concat(blocks, "\n\n")
end

local function get_tip(context, wrong_name)
  local context_pool = {}
  local possible_name
  local possible_names = {}

  for name in pairs(context) do
    if type(name) == "string" then
      for i = 1, #name do
	possible_name = name:sub(1, i - 1) .. name:sub(i + 1)

	if not context_pool[possible_name] then
	  context_pool[possible_name] = {}
	end

	table.insert(context_pool[possible_name], name)
      end
    end
  end

  for i = 1, #wrong_name + 1 do
    possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)

    if context[possible_name] then
      possible_names[possible_name] = true
    elseif context_pool[possible_name] then
      for _, name in ipairs(context_pool[possible_name]) do
	possible_names[name] = true
      end
    end
  end

  local first = next(possible_names)

  if first then
    if next(possible_names, first) then
      local possible_names_arr = {}

      for name in pairs(possible_names) do
	table.insert(possible_names_arr, "'" .. name .. "'")
      end

      table.sort(possible_names_arr)
      return "\nDid you mean one of these: " ..
	table.concat(possible_names_arr, " ") .. "?"
    else
      return "\nDid you mean '" .. first .. "'?"
    end
  else
    return ""
  end
end

local ElementState = class({
    invocations = 0
})

function ElementState:__call(state, element)
  self.state = state
  self.result = state.result
  self.element = element
  self.target = element._target or element:_get_default_target()
  self.action, self.result[self.target] = element:_get_action()
  return self
end

function ElementState:error(fmt, ...)
  self.state:error(fmt, ...)
end

function ElementState:convert(argument)
  local converter = self.element._convert
  if converter then
    local ok, err

    if type(converter) == "function" then
      ok, err = converter(argument)
    else
      ok, err =
	converter[argument], ('value %q is not accepted'):format(argument)
    end

    if ok == nil then
      self:error((err and 'malformed %s: %s') or 'malformed %s',
	self.element._name, err)
    end

    argument = ok
  end

  return argument
end

function ElementState:assert(argument)
  local asserts = self.element._asserts
  if asserts then
    for _, f in ipairs(asserts) do
      local ok, err = f(argument)
      if not ok then
	self:error((err and 'malformed %s: %s') or 'malformed %s',
	  self.element._name, err)
      end
    end
  end
  return argument
end

function ElementState:default(mode)
  return self.element._defmode:find(mode) and self.element._default
end

local function bound(noun, min, max, is_max)
  local res = ""

  if min ~= max then
    res = "at " .. (is_max and "most" or "least") .. " "
  end

  local number = is_max and max or min
  return res .. tostring(number) .. " " .. noun ..  (number == 1 and "" or "s")
end

function ElementState:invoke(alias)
  self.open = true
  self.name = ("%s '%s'"):format(alias and "option" or "argument",
				 alias or self.element._name)
  self.overwrite = false

  if self.invocations >= self.element._maxcount then
    if self.element._overwrite then
      self.overwrite = true
    else
      self:error("%s must be used %s", self.name,
		 bound("time", self.element._mincount, self.element._maxcount,
		       true))
    end
  else
    self.invocations = self.invocations + 1
  end

  self.args = {}

  if self.element._maxargs <= 0 then
    self:close()
  end

  return self.open
end

function ElementState:pass(argument)
  argument = self:convert(argument)
  argument = self:assert(argument)
  table.insert(self.args, argument)

  if #self.args >= self.element._maxargs then
    self:close()
  end

  return self.open
end

function ElementState:complete_invocation()
  while #self.args < self.element._minargs do
    self:pass(self.element._default)
  end
end

function ElementState:close()
  if self.open then
    self.open = false

    if #self.args < self.element._minargs then
      if self:default("a") then
	self:complete_invocation()
      else
	if #self.args == 0 then
	  if getmetatable(self.element) == Argument then
	    self:error("missing %s", self.name)
	  elseif self.element._maxargs == 1 then
	    self:error("%s requires an argument", self.name)
	  end
	end

	self:error("%s requires %s", self.name,
		   bound("argument", self.element._minargs,
			 self.element._maxargs))
      end
    end

    local args = self.args

    if self.element._maxargs <= 1 then
      args = args[1]
    end

    if self.element._maxargs == 1 and self.element._minargs == 0 and
    self.element._mincount ~= self.element._maxcount then
      args = self.args
    end

    if self.element._bind then self.element._bind(args) end
    self.action(self.result, self.target, args, self.overwrite)
  end
end

local ParseState = class({
    result = {},
    options = {},
    arguments = {},
    argument_i = 1,
    element_to_mutexes = {},
    mutex_to_used_option = {},
    command_actions = {}
})

function ParseState:__call(parser, error_handler)
  self.parser = parser
  self.error_handler = error_handler
  self.charset = parser:_update_charset()
  self:switch(parser)
  return self
end

function ParseState:error(fmt, ...)
  self.error_handler(self.parser, fmt:format(...))
end

function ParseState:switch(parser)
  self.parser = parser

  if parser._action then
    table.insert(self.command_actions, {action = parser._action,
					name = parser._name})
  end

  for _, option in ipairs(parser._options) do
    option = ElementState(self, option)
    table.insert(self.options, option)

    for _, alias in ipairs(option.element._aliases) do
      self.options[alias] = option
    end
  end

  for _, mutex in ipairs(parser._mutexes) do
    for _, option in ipairs(mutex) do
      if not self.element_to_mutexes[option] then
	self.element_to_mutexes[option] = {}
      end

      table.insert(self.element_to_mutexes[option], mutex)
    end
  end

  for _, argument in ipairs(parser._arguments) do
    argument = ElementState(self, argument)
    table.insert(self.arguments, argument)
    argument:invoke()
  end

  self.handle_options = parser._handle_options
  self.argument = self.arguments[self.argument_i]
  self.commands = parser._commands

  for _, command in ipairs(self.commands) do
    for _, alias in ipairs(command._aliases) do
      self.commands[alias] = command
    end
  end
end

function ParseState:get_option(name)
  local option = self.options[name]

  if not option then
    self:error("unknown option '%s'%s", name, get_tip(self.options, name))
  else
    return option
  end
end

function ParseState:get_command(name)
  local command = self.commands[name]

  if not command then
    if #self.commands > 0 then
      self:error("unknown command '%s'%s", name, get_tip(self.commands, name))
    else
      self:error("too many arguments")
    end
  else
    return command
  end
end

function ParseState:invoke(option, name)
  self:close()

  if self.element_to_mutexes[option.element] then
    for _, mutex in ipairs(self.element_to_mutexes[option.element]) do
      local used_option = self.mutex_to_used_option[mutex]

      if used_option and used_option ~= option then
	self:error("option '%s' can not be used together with %s", name,
		   used_option.name)
      else
	self.mutex_to_used_option[mutex] = option
      end
    end
  end

  if option:invoke(name) then
    self.option = option
  end
end

function ParseState:pass(arg)
  if self.option then
    if not self.option:pass(arg) then
      self.option = nil
    end
  elseif self.argument then
    if not self.argument:pass(arg) then
      self.argument_i = self.argument_i + 1
      self.argument = self.arguments[self.argument_i]
    end
  else
    local command = self:get_command(arg)
    self.result[command._target or command._name] = true

    if self.parser._command_target then
      self.result[self.parser._command_target] = command._name
    end

    self:switch(command)
  end
end

function ParseState:close()
  if self.option then
    self.option:close()
    self.option = nil
  end
end

function ParseState:finalize()
  self:close()

  for i = self.argument_i, #self.arguments do
    local argument = self.arguments[i]
    if #argument.args == 0 and argument:default("u") then
      argument:complete_invocation()
    else
      argument:close()
    end
  end

  if self.parser._require_command and #self.commands > 0 then
    self:error("a command is required")
  end

  for _, option in ipairs(self.options) do
    local name = option.name or ("option '%s'"):format(option.element._name)

    if option.invocations == 0 then
      if option:default("u") then
	option:invoke(name)
	option:complete_invocation()
	option:close()
      end
    end

    local mincount = option.element._mincount

    if option.invocations < mincount then
      if option:default("a") then
	while option.invocations < mincount do
	  option:invoke(name)
	  option:close()
	end
      elseif option.invocations == 0 then
	self:error("missing %s", name)
      else
	self:error("%s must be used %s", name, bound("time", mincount,
						     option.element._maxcount))
      end
    end
  end

  for i = #self.command_actions, 1, -1 do
    self.command_actions[i].action(self.result, self.command_actions[i].name)
  end
end

function ParseState:parse(args)
  for _, arg in ipairs(args) do
    local plain = true

    if self.handle_options then
      local first = arg:sub(1, 1)

      if self.charset[first] then
	if #arg > 1 then
	  plain = false

	  if arg:sub(2, 2) == first then
	    if #arg == 2 then
	      self:close()
	      self.handle_options = false
	    else
	      local equals = arg:find "="
	      if equals then
		local name = arg:sub(1, equals - 1)
		local option = self:get_option(name)

		if option.element._maxargs <= 0 then
		  self:error("option '%s' does not take arguments", name)
		end

		self:invoke(option, name)
		self:pass(arg:sub(equals + 1))
	      else
		local option = self:get_option(arg)
		self:invoke(option, arg)
	      end
	    end
	  else
	    for i = 2, #arg do
	      local name = first .. arg:sub(i, i)
	      local option = self:get_option(name)
	      self:invoke(option, name)

	      if i ~= #arg and option.element._maxargs > 0 then
		self:pass(arg:sub(i + 1))
		break
	      end
	    end
	  end
	end
      end
    end

    if plain then
      self:pass(arg)
    end
  end

  self:finalize()
  return self.result
end

function Parser:error(msg)
  --io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg))
  -- Use Laia's logging system. Don't use FATAL because we dont want to print
  -- the stacktrace.
  laia.log.error(('%s\n\n%s'):format(msg, self:get_usage()))
  os.exit(1)
end

-- Compatibility with strict.lua and other checkers:
local default_cmdline = rawget(_G, "arg") or {}

function Parser:_parse(args, error_handler)
  return ParseState(self, error_handler):parse(args or default_cmdline)
end

function Parser:parse(args)
  table.sort(self._options,
	     function(x, y) return x:_get_label() < y:_get_label() end)
  table.sort(self._commands,
	     function(x, y) return x:_get_label() < y:_get_label() end)
  for _,mutex in ipairs(self._mutexes) do
    table.sort(mutex,
	       function(x, y) return x:_get_label() < y:_get_label() end)
  end
  return self:_parse(args, self.error)
end

local function xpcall_error_handler(err)
  return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
end

function Parser:pparse(args)
  local parse_error

  local ok, result = xpcall(function()
      return self:_parse(args, function(_, err)
			   parse_error = err
			   error(err, 0)
      end)
			    end, xpcall_error_handler)

  if ok then
    return true, result
  elseif not parse_error then
    error(result, 0)
  else
    return false, parse_error
  end
end

function Argument:bind(tab, key)
  assert(type(tab) == 'table')
  assert(key ~= nil and (type(key) == 'string' or type(key) == 'number'))
  self._bind = function(val) tab[key] = val end
  return self
end

function Argument:assert(func)
  assert(type(func) == 'function')
  table.insert(self._asserts, func)
  return self
end

function Argument:ge(x)
  assert(type(x) == 'number')
  local f = function(v)
    return v >= x, ('value must be >= %g (value = %g)'):format(x, v)
  end
  table.insert(self._asserts, f)
  return self
end

function Argument:gt(x)
  assert(type(x) == 'number')
  local f = function(v)
    return v > x, ('value must be > %g (value = %g)'):format(x, v)
  end
  table.insert(self._asserts, f)
  return self
end

function Argument:le(x)
  assert(type(x) == 'number')
  local f = function(v)
    return v <= x, ('value must be <= %g (value = %g)'):format(x, v)
  end
  table.insert(self._asserts, f)
  return self
end

function Argument:lt(x)
  assert(type(x) == 'number')
  local f = function(v)
    return v < x, ('value must be < %g (value = %g)'):format(x, v)
  end
  table.insert(self._asserts, f)
  return self
end

return function(...)
  return Parser(default_cmdline[0]):add_help(true)(...)
end
