#include <stdio.h>
#include <strings.h>
#include "cli.h"
#include "log.h"
#include "serval.h"
#include "rhizome.h"

int cli_usage(const struct cli_schema *commands) {
  printf("Usage:\n");
  unsigned cmd;
  for (cmd = 0; commands[cmd].function; ++cmd) {
    unsigned opt;
    const char *word;
    for (opt = 0; (word = commands[cmd].words[opt]); ++opt) {
      if (word[0] == '\\')
	++word;
      printf(" %s", word);
    }
    printf("\n   %s\n",commands[cmd].description);
  }
  return 0;
}

int cli_parse(const int argc, const char *const *args, const struct cli_schema *commands, struct cli_parsed *parsed)
{
  int ambiguous = 0;
  int matched_cmd = -1;
  int cmd;
  for (cmd = 0; commands[cmd].function; ++cmd) {
    struct cli_parsed cmdpa;
    memset(&cmdpa, 0, sizeof cmdpa);
    cmdpa.command = &commands[cmd];
    cmdpa.args = args;
    cmdpa.argc = argc;
    cmdpa.labelc = 0;
    cmdpa.varargi = -1;
    const char *word = NULL;
    unsigned arg = 0;
    unsigned opt = 0;
    while ((word = commands[cmd].words[opt])) {
      //DEBUGF("cmd=%d opt=%d word='%s' args[arg=%d]='%s'", cmd, opt, word, arg, arg < argc ? args[arg] : "");
      unsigned wordlen = strlen(word);
      if (cmdpa.varargi != -1)
	return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - more words not allowed after \"...\"", cmd, opt, word);
      /* These are the argument matching rules:
       *
       * "..." consumes all remaining arguments
       *
       * "word" consumes one argument that exactly matches "word", does not label it
       *
       * "\word" consumes one argument that exactly matches "word" and labels it "word"
       *
       * "[word]" consumes one argument if it exactly matches "word", records it with label
       * "word"
       *
       * "<label>" consumes exactly one argument "ANY", records it with label "label"
       *
       * "prefix=<any>" consumes one argument "prefix=ANY" or two arguments "prefix" "ANY",
       * and records the text matching ANY with label "prefix"
       *
       * "prefix <any>" consumes one argyment "prefix ANY" if available or two arguments "prefix"
       * "ANY", and records the text matching ANY with label "prefix"
       *
       * "prefix<any>" consumes one argument "prefixANY", and records the text matching ANY with
       * label "prefix"
       *
       * "[<label>]" consumes one argument "ANY" if available, records it with label "label"
       *
       * "[prefix=<any>]" consumes one argument "prefix=ANY" if available or two arguments
       * "prefix" "ANY" if available, records the text matching ANY with label "prefix"
       *
       * "[prefix <any>]" consumes one argument "prefix ANY" if available or two arguments
       * "prefix" "ANY" if available, records the text matching ANY with label "prefix"
       *
       * "[prefix<any>]" consumes one argument "prefixANY" if available, records the text matching
       * ANY with label "prefix"
       */
      if (wordlen == 3 && word[0] == '.' && word[1] == '.' && word[2] == '.') {
	cmdpa.varargi = arg;
	arg = argc;
	++opt;
      } else {
	int optional = 0;
	int repeating = 0;
	if (wordlen > 5 && word[0] == '[' && word[wordlen-4] == ']' && word[wordlen-3] == '.' && word[wordlen-2] == '.' && word[wordlen-1] == '.') {
	  optional = repeating = 1;
	  word += 1;
	  wordlen -= 5;
	}
	else if (wordlen > 2 && word[0] == '[' && word[wordlen-1] == ']') {
	  optional = 1;
	  word += 1;
	  wordlen -= 2;
	}
	const char *prefix = NULL;
	unsigned prefixlen = 0;
	char prefixarglen = 0;
	const char *label = NULL;
	unsigned labellen = 0;
	const char *text = NULL;
	const char *caret = strchr(word, '<');
	unsigned oarg = arg;
	if (wordlen > 2 && caret && word[wordlen-1] == '>') {
	  if ((prefixarglen = prefixlen = caret - word)) {
	    prefix = word;
	    if (prefixlen > 1 && (prefix[prefixlen-1] == '=' || prefix[prefixlen-1] == ' '))
	      --prefixarglen;
	    label = prefix;
	    labellen = prefixarglen;
	    if (arg < argc) {
	      unsigned arglen = strlen(args[arg]);
	      if (arglen >= prefixlen && strncmp(args[arg], prefix, prefixlen) == 0) {
		text = args[arg++] + prefixlen;
	      } else if (arg + 1 < argc && arglen == prefixarglen && strncmp(args[arg], prefix, prefixarglen) == 0) {
		++arg;
		text = args[arg++];
	      }
	    }
	  } else {
	    label = &word[1];
	    labellen = wordlen - 2;
	    if (arg < argc)
	      text = args[arg++];
	  }
	} else if (wordlen > 1 && word[0] == '\\') {
	  if (strlen(args[arg]) == wordlen - 1 && strncmp(args[arg], &word[1], wordlen - 1) == 0) {
	    label = &word[1];
	    labellen = wordlen - 1;
	    text = args[arg++];
	  }
	} else if (arg < argc && strlen(args[arg]) == wordlen && strncmp(args[arg], word, wordlen) == 0) {
	  if (optional) {
	    text = args[arg];
	    label = word;
	    labellen = wordlen;
	  }
	  ++arg;
	}
	if (arg == oarg && !optional)
	  break;
	if (labellen && text) {
	  if (cmdpa.labelc >= NELS(cmdpa.labelv))
	    return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - label limit exceeded", cmd, opt, word);
	  cmdpa.labelv[cmdpa.labelc].label = label;
	  cmdpa.labelv[cmdpa.labelc].len = labellen;
	  cmdpa.labelv[cmdpa.labelc].text = text;
	  ++cmdpa.labelc;
	  if (!repeating)
	    ++opt;
	} else
	  ++opt;
      }
    }
    //DEBUGF("cmd=%d opt=%d args[arg=%d]='%s'", cmd, opt, arg, arg < argc ? args[arg] : "");
    if (!word && arg == argc) {
      /* A match!  We got through the command definition with no internal errors and all literal
       args matched and we have a proper number of args.  If we have multiple matches, then note
       that the call is ambiguous. */
      if (matched_cmd >= 0)
	++ambiguous;
      if (ambiguous == 1) {
	WHY("Ambiguous command line call:");
	WHY_argv("   ", argc, args);
	WHY("Matches the following known command line calls:");
	WHY_argv("   ", argc, commands[matched_cmd].words);
      }
      if (ambiguous)
	WHY_argv("   ", argc, commands[cmd].words);
      matched_cmd = cmd;
      *parsed = cmdpa;
    }
  }
  /* Don't process ambiguous calls */
  if (ambiguous)
    return -1;
  /* Complain if we found no matching calls */
  if (matched_cmd < 0) {
    if (argc) {
      WHY("Unknown command line call:");
      WHY_argv("   ", argc, args);
    }
    INFO("Use \"help\" command to see a list of valid commands");
    return -1;
  }
  return matched_cmd;
}

void _debug_cli_parsed(struct __sourceloc __whence, const struct cli_parsed *parsed)
{
  DEBUG_argv("command", parsed->argc, parsed->args);
  strbuf b = strbuf_alloca(1024);
  int i;
  for (i = 0; i < parsed->labelc; ++i) {
    const struct labelv *lab = &parsed->labelv[i];
    strbuf_sprintf(b, " %s=%s", alloca_toprint(-1, lab->label, lab->len), alloca_str_toprint(lab->text));
  }
  if (parsed->varargi >= 0)
    strbuf_sprintf(b, " varargi=%d", parsed->varargi); 
  DEBUGF("parsed%s", strbuf_str(b));
}

int cli_invoke(const struct cli_parsed *parsed, void *context)
{
  IN();
  int ret = parsed->command->function(parsed, context);
  RETURN(ret);
  OUT();
}

int _cli_arg(struct __sourceloc __whence, const struct cli_parsed *parsed, char *label, const char **dst, int (*validator)(const char *arg), char *defaultvalue)
{
  int labellen = strlen(label);
  if (dst)
    *dst = defaultvalue;
  int i;
  for (i = 0; i < parsed->labelc; ++i) {
    if (parsed->labelv[i].len == labellen && strncasecmp(label, parsed->labelv[i].label, labellen) == 0) {
      const char *value = parsed->labelv[i].text;
      if (validator && !(*validator)(value))
	return WHYF("Invalid '%s' argument \"%s\"", label, value);
      if (dst)
	*dst = value;
      return 0;
    }
  }
  /* No matching valid argument was found, so return default value.  It might seem that this should
   never happen, but it can because more than one version of a command line option may exist, one
   with a given argument and another without, and allowing a default value means we can have a
   single function handle both in a fairly simple manner. */
  return 1;
}

int cli_lookup_did(const char *text)
{
  return text[0] == '\0' || strcmp(text, "*") == 0 || str_is_did(text);
}

int cli_absolute_path(const char *arg)
{
  return arg[0] == '/' && arg[1] != '\0';
}

int cli_optional_sid(const char *arg)
{
  return !arg[0] || str_is_subscriber_id(arg);
}

int cli_optional_bundle_key(const char *arg)
{
  return !arg[0] || rhizome_str_is_bundle_key(arg);
}

int cli_manifestid(const char *arg)
{
  return rhizome_str_is_manifest_id(arg);
}

int cli_fileid(const char *arg)
{
  return rhizome_str_is_file_hash(arg);
}

int cli_optional_bundle_crypt_key(const char *arg)
{
  return !arg[0] || rhizome_str_is_bundle_crypt_key(arg);
}

int cli_uint(const char *arg)
{
  register const char *s = arg;
  while (isdigit(*s))
    ++s;
  return s != arg && *s == '\0';
}

int cli_optional_did(const char *text)
{
  return text[0] == '\0' || str_is_did(text);
}