// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */
#include <stdarg.h>

#include "auth/Auth.h"
#include "common/ceph_argparse.h"
#include "common/config.h"
#include "common/version.h"
#include "include/str_list.h"

/*
 * Ceph argument parsing library
 *
 * We probably should eventually replace this with something standard like popt.
 * Until we do that, though, this file is the place for argv parsing
 * stuff to live.
 */

#undef dout
#undef pdout
#undef derr
#undef generic_dout
#undef dendl

struct strict_str_convert {
  const char *str;
  std::string *err;
  strict_str_convert(const char *str,  std::string *err)
    : str(str), err(err) {}

  inline operator float() const
  {
    return strict_strtof(str, err);
  }
  inline operator int() const
  {
    return strict_strtol(str, 10, err);
  }
  inline operator long long() const
  {
    return  strict_strtoll(str, 10, err);
  }
};

void string_to_vec(std::vector<std::string>& args, std::string argstr)
{
  std::istringstream iss(argstr);
  while(iss) {
    std::string sub;
    iss >> sub;
    if (sub == "") break;
    args.push_back(sub);
  }
}

std::pair<std::vector<const char*>, std::vector<const char*>>
split_dashdash(const std::vector<const char*>& args) {
  auto dashdash = std::find_if(args.begin(), args.end(),
			       [](const char* arg) {
				 return strcmp(arg, "--") == 0;
			       });
  std::vector<const char*> options{args.begin(), dashdash};
  if (dashdash != args.end()) {
    ++dashdash;
  }
  std::vector<const char*> arguments{dashdash, args.end()};
  return {std::move(options), std::move(arguments)};
}

static std::mutex g_str_vec_lock;
static std::vector<std::string> g_str_vec;

void clear_g_str_vec()
{
  g_str_vec_lock.lock();
  g_str_vec.clear();
  g_str_vec_lock.unlock();
}

void env_to_vec(std::vector<const char*>& args, const char *name)
{
  if (!name)
    name = "CEPH_ARGS";

  /*
   * We can only populate str_vec once. Other threads could hold pointers into
   * it, so clearing it out and replacing it is not currently safe.
   */
  g_str_vec_lock.lock();
  if (g_str_vec.empty()) {
    char *p = getenv(name);
    if (!p) {
      g_str_vec_lock.unlock();
      return;
    }
    get_str_vec(p, " ", g_str_vec);
  }

  std::vector<const char*> env;
  for (const auto& s : g_str_vec) {
    env.push_back(s.c_str());
  }
  g_str_vec_lock.unlock();
  auto [env_options, env_arguments] = split_dashdash(env);

  auto [options, arguments] = split_dashdash(args);
  args.clear();
  args.insert(args.end(), env_options.begin(), env_options.end());
  args.insert(args.end(), options.begin(), options.end());
  if (arguments.empty() && env_arguments.empty()) {
    return;
  }
  args.push_back("--");
  args.insert(args.end(), env_arguments.begin(), env_arguments.end());
  args.insert(args.end(), arguments.begin(), arguments.end());
}

std::vector<const char*> argv_to_vec(int argc, const char* const * argv)
{
  assert(argc > 0);
  return {argv + 1, argv + argc};
}

void vec_to_argv(const char *argv0, std::vector<const char*>& args,
                 int *argc, const char ***argv)
{
  *argv = (const char**)malloc(sizeof(char*) * (args.size() + 1));
  if (!*argv)
    throw std::bad_alloc();
  *argc = 1;
  (*argv)[0] = argv0;

  for (unsigned i=0; i<args.size(); i++)
    (*argv)[(*argc)++] = args[i];
}

void ceph_arg_value_type(const char * nextargstr, bool *bool_option, bool *bool_numeric)
{
  bool is_numeric = true;
  bool is_float = false;
  bool is_option;

  if (nextargstr == NULL) {
    return;
  }

  if (strlen(nextargstr) < 2) {
    is_option = false;
  } else {
    is_option = (nextargstr[0] == '-') && (nextargstr[1] == '-');
  }

  for (unsigned int i = 0; i < strlen(nextargstr); i++) {
    if (!(nextargstr[i] >= '0' && nextargstr[i] <= '9')) {
      // May be negative numeral value
      if ((i == 0) && (strlen(nextargstr) >= 2))  {
	if (nextargstr[0] == '-')
	  continue;
      }
      if ( (nextargstr[i] == '.') && (is_float == false) ) {
        is_float = true;
        continue;
      }
        
      is_numeric = false;
      break;
    }
  }

  // -<option>
  if (nextargstr[0] == '-' && is_numeric == false) {
    is_option = true;
  }

  *bool_option = is_option;
  *bool_numeric = is_numeric;

  return;
}


bool parse_ip_port_vec(const char *s, std::vector<entity_addrvec_t>& vec, int type)
{
  // first split by [ ;], which are not valid for an addrvec
  std::list<std::string> items;
  get_str_list(s, " ;", items);

  for (auto& i : items) {
    const char *s = i.c_str();
    while (*s) {
      const char *end;

      // try parsing as an addr
      entity_addr_t a;
      if (a.parse(s, &end, type)) {
	vec.push_back(entity_addrvec_t(a));
	s = end;
	if (*s == ',') {
	  ++s;
	}
	continue;
      }

      // ok, try parsing as an addrvec
      entity_addrvec_t av;
      if (!av.parse(s, &end)) {
	return false;
      }
      vec.push_back(av);
      s = end;
      if (*s == ',') {
	++s;
      }
    }
  }
  return true;
}

// The defaults for CephInitParameters
CephInitParameters::CephInitParameters(uint32_t module_type_)
  : module_type(module_type_)
{
  name.set(module_type, "admin");
}

static void dashes_to_underscores(const char *input, char *output)
{
  char c = 0;
  char *o = output;
  const char *i = input;
  // first two characters are copied as-is
  *o = *i++;
  if (*o++ == '\0')
    return;
  *o = *i++;
  if (*o++ == '\0')
    return;
  for (; ((c = *i)); ++i) {
    if (c == '=') {
      strcpy(o, i);
      return;
    }
    if (c == '-')
      *o++ = '_';
    else
      *o++ = c;
  }
  *o++ = '\0';
}

/** Once we see a standalone double dash, '--', we should remove it and stop
 * looking for any other options and flags. */
bool ceph_argparse_double_dash(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i)
{
  if (strcmp(*i, "--") == 0) {
    i = args.erase(i);
    return true;
  }
  return false;
}

bool ceph_argparse_flag(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, ...)
{
  const char *first = *i;
  char tmp[strlen(first)+1];
  dashes_to_underscores(first, tmp);
  first = tmp;
  va_list ap;

  va_start(ap, i);
  while (1) {
    const char *a = va_arg(ap, char*);
    if (a == NULL) {
      va_end(ap);
      return false;
    }
    char a2[strlen(a)+1];
    dashes_to_underscores(a, a2);
    if (strcmp(a2, first) == 0) {
      i = args.erase(i);
      va_end(ap);
      return true;
    }
  }
}

static bool check_bool_str(const char *val, int *ret)
{
  if ((strcmp(val, "true") == 0) || (strcmp(val, "1") == 0)) {
    *ret = 1;
    return true;
  } else if ((strcmp(val, "false") == 0) || (strcmp(val, "0") == 0)) {
    *ret = 0;
    return true;
  }
  return false;
}

static bool va_ceph_argparse_binary_flag(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, int *ret,
	std::ostream *oss, va_list ap)
{
  const char *first = *i;
  char tmp[strlen(first)+1];
  dashes_to_underscores(first, tmp);
  first = tmp;

  // does this argument match any of the possibilities?
  while (1) {
    const char *a = va_arg(ap, char*);
    if (a == NULL)
      return false;
    int strlen_a = strlen(a);
    char a2[strlen_a+1];
    dashes_to_underscores(a, a2);
    if (strncmp(a2, first, strlen(a2)) == 0) {
      if (first[strlen_a] == '=') {
	i = args.erase(i);
	const char *val = first + strlen_a + 1;
        if (check_bool_str(val, ret)) {
	  return true;
	}
	if (oss) {
	  (*oss) << "Parse error parsing binary flag  " << a
	         << ". Expected true or false, but got '" << val << "'\n";
	}
	*ret = -EINVAL;
	return true;
      }
      else if (first[strlen_a] == '\0') {
        auto next = i+1;
        if (next != args.end() &&
            *next &&
            (*next)[0] != '-') {
          if (check_bool_str(*next, ret)) {
            i = args.erase(i);
            i = args.erase(i);
            return true;
          }
        }
        i = args.erase(i);
        *ret =  1;
	return true;
      }
    }
  }
}

bool ceph_argparse_binary_flag(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, int *ret,
	std::ostream *oss, ...)
{
  bool r;
  va_list ap;
  va_start(ap, oss);
  r = va_ceph_argparse_binary_flag(args, i, ret, oss, ap);
  va_end(ap);
  return r;
}

static int va_ceph_argparse_witharg(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, std::string *ret,
	std::ostream &oss, va_list ap)
{
  const char *first = *i;
  char tmp[strlen(first)+1];
  dashes_to_underscores(first, tmp);
  first = tmp;

  // does this argument match any of the possibilities?
  while (1) {
    const char *a = va_arg(ap, char*);
    if (a == NULL)
      return 0;
    int strlen_a = strlen(a);
    char a2[strlen_a+1];
    dashes_to_underscores(a, a2);
    if (strncmp(a2, first, strlen(a2)) == 0) {
      if (first[strlen_a] == '=') {
	*ret = first + strlen_a + 1;
	i = args.erase(i);
	return 1;
      }
      else if (first[strlen_a] == '\0') {
	// find second part (or not)
	if (i+1 == args.end()) {
	  oss << "Option " << *i << " requires an argument." << std::endl;
	  i = args.erase(i);
	  return -EINVAL;
	}
	i = args.erase(i);
	*ret = *i;
	i = args.erase(i);
	return 1;
      }
    }
  }
}

template<class T>
bool ceph_argparse_witharg(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, T *ret,
	std::ostream &oss, ...)
{
  int r;
  va_list ap;
  bool is_option = false;
  bool is_numeric = true;
  std::string str;
  va_start(ap, oss);
  r = va_ceph_argparse_witharg(args, i, &str, oss, ap);
  va_end(ap);
  if (r == 0) {
    return false;
  } else if (r < 0) {
    return true;
  }

  ceph_arg_value_type(str.c_str(), &is_option, &is_numeric);
  if ((is_option == true) || (is_numeric == false)) {
    *ret = EXIT_FAILURE;
    if (is_option == true) {
      oss << "Missing option value";
    } else {
      oss << "The option value '" << str << "' is invalid";
    }
    return true;
  }

  std::string err;
  T myret = strict_str_convert(str.c_str(), &err);
  *ret = myret;
  if (!err.empty()) {
    oss << err;
  }
  return true;
}

template bool ceph_argparse_witharg<int>(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, int *ret,
	std::ostream &oss, ...);

template bool ceph_argparse_witharg<long long>(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, long long *ret,
	std::ostream &oss, ...);

template bool ceph_argparse_witharg<float>(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, float *ret,
	std::ostream &oss, ...);

bool ceph_argparse_witharg(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, std::string *ret,
	std::ostream &oss, ...)
{
  int r;
  va_list ap;
  va_start(ap, oss);
  r = va_ceph_argparse_witharg(args, i, ret, oss, ap);
  va_end(ap);
  return r != 0;
}

bool ceph_argparse_witharg(std::vector<const char*> &args,
	std::vector<const char*>::iterator &i, std::string *ret, ...)
{
  int r;
  va_list ap;
  va_start(ap, ret);
  r = va_ceph_argparse_witharg(args, i, ret, std::cerr, ap);
  va_end(ap);
  if (r < 0)
    _exit(1);
  return r != 0;
}

CephInitParameters ceph_argparse_early_args
	  (std::vector<const char*>& args, uint32_t module_type,
	   std::string *cluster, std::string *conf_file_list)
{
  CephInitParameters iparams(module_type);
  std::string val;

  auto orig_args = args;

  for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) {
    if (strcmp(*i, "--") == 0) {
      /* Normally we would use ceph_argparse_double_dash. However, in this
       * function we *don't* want to remove the double dash, because later
       * argument parses will still need to see it. */
      break;
    }
    else if (ceph_argparse_flag(args, i, "--version", "-v", (char*)NULL)) {
      std::cout << pretty_version_to_str() << std::endl;
      _exit(0);
    }
    else if (ceph_argparse_witharg(args, i, &val, "--conf", "-c", (char*)NULL)) {
      *conf_file_list = val;
    }
    else if (ceph_argparse_flag(args, i, "--no-config-file", (char*)NULL)) {
      iparams.no_config_file = true;
    }
    else if (ceph_argparse_witharg(args, i, &val, "--cluster", (char*)NULL)) {
      *cluster = val;
    }
    else if ((module_type != CEPH_ENTITY_TYPE_CLIENT) &&
	     (ceph_argparse_witharg(args, i, &val, "-i", (char*)NULL))) {
      iparams.name.set_id(val);
    }
    else if (ceph_argparse_witharg(args, i, &val, "--id", "--user", (char*)NULL)) {
      iparams.name.set_id(val);
    }
    else if (ceph_argparse_witharg(args, i, &val, "--name", "-n", (char*)NULL)) {
      if (!iparams.name.from_str(val)) {
	std::cerr << "error parsing '" << val << "': expected string of the form TYPE.ID, "
		  << "valid types are: " << EntityName::get_valid_types_as_str()
		  << std::endl;
	_exit(1);
      }
    }
    else if (ceph_argparse_flag(args, i, "--show_args", (char*)NULL)) {
      std::cout << "args: ";
      for (std::vector<const char *>::iterator ci = orig_args.begin(); ci != orig_args.end(); ++ci) {
	if (ci != orig_args.begin())
	  std::cout << " ";
	std::cout << *ci;
      }
      std::cout << std::endl;
    }
    else {
      // ignore
      ++i;
    }
  }
  return iparams;
}

static void generic_usage(bool is_server)
{
  std::cout <<
    "  --conf/-c FILE    read configuration from the given configuration file" << std::endl <<
    (is_server ?
    "  --id/-i ID        set ID portion of my name" :
    "  --id ID           set ID portion of my name") << std::endl <<
    "  --name/-n TYPE.ID set name" << std::endl <<
    "  --cluster NAME    set cluster name (default: ceph)" << std::endl <<
    "  --setuser USER    set uid to user or uid (and gid to user's gid)" << std::endl <<
    "  --setgroup GROUP  set gid to group or gid" << std::endl <<
    "  --version         show version and quit" << std::endl
    << std::endl;

  if (is_server) {
    std::cout <<
      "  -d                run in foreground, log to stderr" << std::endl <<
      "  -f                run in foreground, log to usual location" << std::endl <<
      std::endl <<
      "  --debug_ms N      set message debug level (e.g. 1)" << std::endl;
  }

  std::cout.flush();
}

bool ceph_argparse_need_usage(const std::vector<const char*>& args)
{
  if (args.empty()) {
    return true;
  }
  for (auto a : args) {
    if (strcmp(a, "-h") == 0 ||
	strcmp(a, "--help") == 0) {
      return true;
    }
  }
  return false;
}

void generic_server_usage()
{
  generic_usage(true);
}

void generic_client_usage()
{
  generic_usage(false);
}
