local _G = require "_G"
local coroutine = require "coroutine"
local math = require "math"
local nmap = require "nmap"
local string = require "string"
local table = require "table"
local assert = assert;
local error = error;
local getmetatable = getmetatable;
local ipairs = ipairs
local pairs = pairs
local next = next
local rawset = rawset
local require = require;
local select = select
local setmetatable = setmetatable;
local tonumber = tonumber;
local tostring = tostring;
local print = print;
local type = type
local pcall = pcall
local ceil = math.ceil
local max = math.max
local format = string.format;
local rep = string.rep
local match = string.match
local find = string.find
local sub = string.sub
local gsub = string.gsub
local char = string.char
local byte = string.byte
local gmatch = string.gmatch
local concat = table.concat;
local insert = table.insert;
local remove = table.remove;
local pack = table.pack;
local unpack = table.unpack;
local EMPTY = {}; -- Empty constant table
_ENV = require "strict" {};
_ENV.sleep = nmap.socket.sleep;
_ENV.getid = function () return end
_ENV.getinfo = function () return end
_ENV.gethostport = function () return end
local function debug (level, ...)
  if type(level) ~= "number" then
    return debug(1, level, ...)
  end
  local current = nmap.debugging()
  if level <= current then
    local host, port = gethostport()
    local prefix = ( (current >= 2 and getinfo or getid)() or "")
    .. (host and " "..host.ip .. (port and ":"..port.number or "") or "")
    if prefix ~= "" then
      nmap.log_write("stdout", "[" .. prefix .. "] " .. format(...))
    else
      nmap.log_write("stdout", format(...))
    end
  end
end
_ENV.debug = debug
function debug1 (...) return debug(1, ...) end
function debug2 (...) return debug(2, ...) end
function debug3 (...) return debug(3, ...) end
function debug4 (...) return debug(4, ...) end
function debug5 (...) return debug(5, ...) end
print_debug = function(level, fmt, ...)
  local l, d = tonumber(level), nmap.debugging();
  if l and l <= d then
    nmap.log_write("stdout", format(fmt, ...));
  elseif not l and 1 <= d then
    nmap.log_write("stdout", format(level, fmt, ...));
  end
end
local function verbose (level, ...)
  if type(level) ~= "number" then
    return verbose(1, level, ...)
  end
  local current = nmap.verbosity()
  if level <= current then
    local prefix
    if current >= 2 then
      local host, port = gethostport()
      prefix = (getid() or "")
      .. (host and " "..host.ip .. (port and ":"..port.number or "") or "")
    else
      prefix = getid() or ""
    end
    if prefix ~= "" then
      nmap.log_write("stdout", "[" .. prefix .. "] " .. format(...))
    else
      nmap.log_write("stdout", format(...))
    end
  end
end
_ENV.verbose = verbose
function verbose1 (...) return verbose(1, ...) end
function verbose2 (...) return verbose(2, ...) end
function verbose3 (...) return verbose(3, ...) end
function verbose4 (...) return verbose(4, ...) end
function verbose5 (...) return verbose(5, ...) end
print_verbose = function(level, fmt, ...)
  local l, d = tonumber(level), nmap.verbosity();
  if l and l <= d then
    nmap.log_write("stdout", format(fmt, ...));
  elseif not l and 1 <= d then
    nmap.log_write("stdout", format(level, fmt, ...));
  end
end
function make_buffer(socket, sep)
  local point, left, buffer, done, msg = 1, "";
  local function self()
    if done then
      return nil, msg; -- must be nil for stdnse.lines (below)
    elseif not buffer then
      local status, str = socket:receive();
      if not status then
        if #left > 0 then
          done, msg = not status, str;
          return left;
        else
          return status, str;
        end
      else
        buffer = left..str;
        return self();
      end
    else
      local i, j = buffer:find(sep, point);
      if i then
        local ret = buffer:sub(point, i-1);
        point = j + 1;
        return ret;
      else
        point, left, buffer = 1, buffer:sub(point), nil;
        return self();
      end
    end
  end
  return self;
end
--[[ This function may be usable in Lua 5.2
function lines(socket)
  return make_buffer(socket, "\r?\n"), nil, nil;
end --]]
do
  local t = {
    ["0"] = "0000",
    ["1"] = "0001",
    ["2"] = "0010",
    ["3"] = "0011",
    ["4"] = "0100",
    ["5"] = "0101",
    ["6"] = "0110",
    ["7"] = "0111",
    ["8"] = "1000",
    ["9"] = "1001",
    a = "1010",
    b = "1011",
    c = "1100",
    d = "1101",
    e = "1110",
    f = "1111"
  };
  function tobinary(n)
    assert(tonumber(n), "number expected");
    return (("%x"):format(n):gsub("%w", t):gsub("^0*", ""));
  end
end
function tooctal(n)
  assert(tonumber(n), "number expected");
  return ("%o"):format(n)
end
function tohex( s, options )
  options = options or EMPTY
  local separator = options.separator
  local hex
  if type( s ) == "number" then
    hex = ("%x"):format(s)
  elseif type( s ) == 'string' then
    hex = ("%02x"):rep(#s):format(s:byte(1,#s))
  else
    error( "Type not supported in tohex(): " .. type(s), 2 )
  end
  if separator then
    local group = options.group or 2
    local fmt_table = {}
    for i=#hex,1,-group do
      fmt_table[ceil(i/group)] = hex:sub(max(i-group+1,1),i)
    end
    hex = concat( fmt_table, separator )
  end
  return hex
end
function fromhex (hex)
  local len = #hex
  local out = {}
  local i = 1
  while i <= len do
    local p, q, c1, c2 = find(hex, "^%s*(%x)%s*(%x)%s*", i)
    if not p then
      return nil, format("Invalid characters or odd number of hex digits at %d", i)
    end
    out[#out+1] = char(tonumber(c1..c2, 16))
    i = q + 1
  end
  return concat(out)
end
function format_mac(mac)
  return tohex(mac, {separator=":"})
end
function string_or_blank(string, blank)
  if(string == nil or string == "") then
    if(blank == nil) then
      return "<blank>"
    else
      return blank
    end
  else
    return string
  end
end
function parse_timespec(timespec)
  if timespec == nil then return nil, "Can't parse nil timespec" end
  local n, unit, t, m
  local multipliers = {[""] = 1, s = 1, m = 60, h = 60 * 60, ms = 0.001}
  n, unit = match(timespec, "^([%d.]+)(.*)$")
  if not n then
    return nil, format("Can't parse time specification \"%s\"", timespec)
  end
  t = tonumber(n)
  if not t then
    return nil, format("Can't parse time specification \"%s\" (bad number \"%s\")", timespec, n)
  end
  m = multipliers[unit]
  if not m then
    return nil, format("Can't parse time specification \"%s\" (bad unit \"%s\")", timespec, unit)
  end
  return t * m
end
function clock_ms()
  return nmap.clock() * 1000
end
function clock_us()
  return nmap.clock() * 1000000
end
local function format_get_indent(indent)
  return rep("  ", #indent)
end
local function format_output_sub(status, data, indent)
  if (#data == 0) then
    return ""
  end
  local prefix = ""
  local output = {}
  if(not(indent)) then
    insert(output, '\n')
  end
  if(not(status)) then
    if(nmap.debugging() < 1) then
      return nil
    end
    prefix = "ERROR: "
  end
  if(type(data) == 'string') then
    data = {data}
  end
  indent = indent or {}
  if(data['name']) then
    if(data['warning'] and nmap.debugging() > 0) then
      insert(output, format("%s%s%s (WARNING: %s)\n",
                        format_get_indent(indent), prefix,
                        data['name'], data['warning']))
    else
      insert(output, format("%s%s%s\n",
                        format_get_indent(indent), prefix,
                        data['name']))
    end
  elseif(data['warning'] and nmap.debugging() > 0) then
    insert(output, format("%s%s(WARNING: %s)\n",
                      format_get_indent(indent), prefix,
                      data['warning']))
  end
  for i, value in ipairs(data) do
    if(type(value) == 'table') then
      local new_indent = {}
      for _, v in ipairs(indent) do
        insert(new_indent, v)
      end
      if(i ~= #data) then
        insert(new_indent, false)
      else
        insert(new_indent, true)
      end
      insert(output, format_output_sub(status, value, new_indent))
    elseif(type(value) == 'string') then
      if sub(value, -1) ~= "\n" then value = value .. "\n" end
      for line in gmatch(value, "([^\r\n]-)\n") do
        insert(output, format("%s  %s%s\n",
          format_get_indent(indent),
          prefix, line))
      end
    end
  end
  return concat(output)
end
function format_output(status, data, indent)
  assert(data, "No data was passed to format_output()")
  if (#data == 0) then
    return nil
  end
  local result = format_output_sub(status, data, indent)
  if(result == nil or #result == "" or result == "\n" or result == "\n") then
    return nil
  end
  return result
end
local function arg_value(argname)
  if nmap.registry.args[argname] then
    return nmap.registry.args[argname]
  end
  for _, v in ipairs(nmap.registry.args) do
    if v == argname then
      return 1
    end
  end
  local shortname = argname:match("%.([^.]*)$")
  if shortname then
    if nmap.registry.args[shortname] then
      return nmap.registry.args[shortname]
    end
    for _, v in ipairs(nmap.registry.args) do
      if v == shortname then
        return 1
      end
    end
  end
  return nil
end
function get_script_args (...)
  local args = {}
  for i, set in ipairs({...}) do
    if type(set) == "string" then
      set = {set}
    end
    for _, test in ipairs(set) do
      local v = arg_value(test)
      if v then
        args[i] = v
        break
      end
    end
  end
  return unpack(args, 1, select("#", ...))
end
function get_hostname(host)
  if type(host) == "table" then
    return host.targetname or ( host.name ~= '' and host.name ) or host.ip
  else
    return host
  end
end
function registry_get(subkeys)
  local registry = nmap.registry
  local i = 1
  while(subkeys[i]) do
    if(not(registry[subkeys[i]])) then
      return nil
    end
    registry = registry[subkeys[i]]
    i = i + 1
  end
  return registry
end
function registry_exists(subkeys, key, value)
  local subkey = registry_get(subkeys)
  if(not(subkey)) then
    return false
  end
  for k, v in pairs(subkey) do
    if((key == nil or key == k) and (v == value)) then -- TODO: if 'value' is a table, this fails
      return true
    end
  end
  return false
end
function registry_add_array(subkeys, value, allow_duplicates)
  local registry = nmap.registry
  local i = 1
  if(allow_duplicates ~= true) then
    if(registry_exists(subkeys, nil, value)) then
      return
    end
  end
  while(subkeys[i]) do
    if(not(registry[subkeys[i]])) then
      registry[subkeys[i]] = {}
    end
    registry = registry[subkeys[i]]
    i = i + 1
  end
  for _, v in pairs(registry) do
    if(v == value) then
      return
    end
  end
  insert(registry, value)
end
function registry_add_table(subkeys, key, value, allow_duplicates)
  local registry = nmap.registry
  local i = 1
  if(allow_duplicates ~= true) then
    if(registry_exists(subkeys, key, value)) then
      return
    end
  end
  while(subkeys[i]) do
    if(not(registry[subkeys[i]])) then
      registry[subkeys[i]] = {}
    end
    registry = registry[subkeys[i]]
    i = i + 1
  end
  registry[key] = value
end
do end -- no function here, see nse_main.lua
do end -- no function here, see nse_main.lua
do end -- no function here, see nse_main.lua
function module (name, ...)
  local env = {};
  env._NAME = name;
  env._PACKAGE = name:match("(.+)%.[^.]+$");
  env._M = env;
  local mods = pack(...);
  for i = 1, mods.n do
    mods[i](env);
  end
  return env;
end
function seeall (env)
  local m = getmetatable(env) or {};
  m.__index = _G;
  setmetatable(env, m);
end
function output_table ()
  local t = {}
  local order = {}
  local function iterator ()
    for i, key in ipairs(order) do
      coroutine.yield(key, t[key])
    end
  end
  local mt = {
    __newindex = function (_, k, v)
      if t[k] == nil and v ~= nil then
        insert(order, k)
      elseif v == nil then
        for i, key in ipairs(order) do
          if key == k then
            remove(order, i)
            break
          end
        end
      end
      rawset(t, k, v)
    end,
    __index = function (_, k)
      return t[k]
    end,
    __pairs = function (_)
      return coroutine.wrap(iterator)
    end,
    __call = function (_) -- hack to mean "not_empty?"
      return not not next(order)
    end,
    __len = function (_)
      return #order
    end
  }
  return setmetatable({}, mt)
end
function pretty_printer (obj, printer)
  if printer == nil then printer = print end
  local function aux (obj, spacing)
    local t = type(obj)
    if t == "table" then
      printer "{\n"
      for k, v in pairs(obj) do
        local spacing = spacing.."\t"
        printer(spacing)
        printer "["
        aux(k, spacing)
        printer "] = "
        aux(v, spacing)
        printer ",\n"
      end
      printer(spacing.."}")
    elseif t == "string" then
      printer(format("%q", obj))
    else
      printer(tostring(obj))
    end
  end
  return aux(obj, "")
end
function get_timeout(host, max_timeout, min_timeout)
  max_timeout = max_timeout or 8000
  local t = type(host) == "table" and host.times and host.times.timeout
  if not t then
    return max_timeout
  end
  t = t * (max_timeout + 6000) / 7
  min_timeout = min_timeout or 1000
  if t < min_timeout then
    return min_timeout
  elseif t > max_timeout then
    return max_timeout
  end
  return t
end
return _ENV;
