// Copyright (C) 1999,2000 Bruce Guenter <bruce@untroubled.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include <config.h>
#include "ac/systime.h"
#include "fdbuf/fdbuf.h"
#include <stdlib.h>
#include <string.h>
#include "cli++.h"

#ifndef HAVE_SRANDOM
void srandom(unsigned int seed);
#endif

static bool do_show_usage = false;
const char* argv0;
const char* argv0base;
const char* argv0dir;

static cli_option help_option = { 'h', "help", cli_option::flag,
				  true, &do_show_usage,
				  "Display this help and exit", 0 };

static cli_option** options;
static unsigned optionc;

static void build_options()
{
  for(optionc = 0;
      cli_options[optionc].ch || cli_options[optionc].name;
      optionc++) ;
  optionc++;
  options = new cli_option*[optionc];
  for(unsigned i = 0; i < optionc-1; i++)
    options[i] = &cli_options[i];
  options[optionc-1] = &help_option;
}

static inline unsigned max(unsigned a, unsigned b)
{
  return (a>b) ? a : b;
}

static const char* fill(unsigned i)
{
  static unsigned lastlen = 0;
  static char* buf = 0;
  if(i > lastlen) {
    delete[] buf;
    buf = new char[i+1];
    lastlen = i;
  }
  memset(buf, ' ', i);
  buf[i] = 0;
  return buf;
}
  
static void show_usage()
{
  fout << "usage: " << cli_program << " [flags] " << cli_args_usage << endl;
}

static unsigned calc_max_width()
{
  // maxwidth is the maximum width of the long argument
  unsigned maxwidth = 0;
  for(unsigned i = 0; i < optionc; i++) {
    unsigned width = 0;
    cli_option* o = options[i];
    if(o->name) {
      width += strlen(o->name);
      switch(o->type) {
      case cli_option::string:     width += 6; break;
      case cli_option::integer:    width += 4; break;
      case cli_option::uinteger:   width += 4; break;
      case cli_option::stringlist: width += 5; break;
      case cli_option::flag:       break;
      case cli_option::counter:    break;
      }
    }
    if(width > maxwidth)
      maxwidth = width;
  }
  return maxwidth;
}

static void show_option(cli_option* o, unsigned maxwidth)
{
  if(o == &help_option)
    fout << '\n';
  if(o->ch)
    fout << "  -" << o->ch;
  else
    fout << "    ";
  fout << (o->ch && o->name ? ", " : "  ");
  if(o->name) {
    const char* extra = "";
    switch(o->type) {
    case cli_option::string:     extra = "=VALUE"; break;
    case cli_option::integer:    extra = "=INT"; break;
    case cli_option::uinteger:   extra = "=UNS"; break;
    case cli_option::stringlist: extra = "=ITEM"; break;
    case cli_option::flag:       break;
    case cli_option::counter:    break;
    }
    fout << "--" << o->name << extra
	 << fill(maxwidth - strlen(o->name) - strlen(extra) + 2);
  }
  else
    fout << fill(maxwidth+4);
  fout << o->helpstr << '\n';
  if(o->defaultstr)
    fout << fill(maxwidth+10) << "(Defaults to " << o->defaultstr << ")\n";
}

static void show_help()
{
  if(cli_help_prefix)
    fout << cli_help_prefix;
  unsigned maxwidth = calc_max_width();
  for(unsigned i = 0; i < optionc; i++)
    show_option(options[i], maxwidth);
  if(cli_help_suffix)
    fout << cli_help_suffix;
}

void usage(int exit_value, const char* errorstr)
{
  if(errorstr)
    ferr << cli_program << ": " << errorstr << endl;
  show_usage();
  show_help();
  exit(exit_value);
}

cli_stringlist* stringlist_append(cli_stringlist* node, const char* newstr)
{
  cli_stringlist* newnode = new cli_stringlist(newstr);
  if(node) {
    cli_stringlist* head = node;
    while(node->next)
      node = node->next;
    node->next = newnode;
    return head;
  }
  else
    return newnode;
}

int cli_option::set(const char* arg)
{
  char* endptr;
  switch(type) {
  case flag:
    *(int*)dataptr = flag_value;
    return 0;
  case counter:
    *(int*)dataptr += flag_value;
    return 0;
  case integer:
    *(int*)dataptr = strtol(arg, &endptr, 10);
    if(*endptr) {
      ferr << argv0 << ": invalid integer: " << arg << endl;
      return -1;
    }
    return 1;
  case uinteger:
    *(unsigned*)dataptr = strtoul(arg, &endptr, 10);
    if(*endptr) {
      ferr << argv0 << ": invalid unsigned integer: " << arg << endl;
      return -1;
    }
    return 1;
  case stringlist:
    *(cli_stringlist**)dataptr =
      stringlist_append(*(cli_stringlist**)dataptr, arg);
    return 1;
  default: // string
    *(const char**)dataptr = arg;
    return 1;
  }
}

static int parse_short(int argc, char* argv[])
{
  int end = strlen(argv[0]) - 1;
  for(int i = 1; i <= end; i++) {
    int ch = argv[0][i];
    unsigned j;
    for(j = 0; j < optionc; j++) {
      cli_option* o = options[j];
      if(o->ch == ch) {
	if(o->type != cli_option::flag &&
	   o->type != cli_option::counter) {
	  if(i < end) {
	    if(o->set(argv[0]+i+1) != -1)
	      return 0;
	  }
	  else if(argc <= 1) {
	    ferr << argv0 << ": option -" << o->ch
		 << " requires a value." << endl;
	  }
	  else
	    if(o->set(argv[1]) != -1)
	      return 1;
	}
	else if(o->set(0) != -1)
	  break;
	return -1;
      }
    }
    if(j >= optionc) {
      ferr << argv0 << ": unknown option letter -" << argv[0][i] << endl;
      return -1;
    }
  }
  return 0;
}

int cli_option::parse_long_eq(const char* arg)
{
  if(type == flag || type == counter) {
    ferr << argv0 << ": option --" << name
	 << " does not take a value." << endl;
    return -1;
  }
  else
    return set(arg)-1;
}

int cli_option::parse_long_noeq(const char* arg)
{
  if(type == flag || type == counter)
    return set(0);
  else if(arg)
    return set(arg);
  else {
    ferr << argv0 << ": option --" << name
	 << " requires a value." << endl;
    return -1;
  }
}

static int parse_long(int, char* argv[])
{
  const char* arg = argv[0]+2;
  for(unsigned j = 0; j < optionc; j++) {
    cli_option* o = options[j];
    if(o->name) {
      size_t len = strlen(o->name);
      if(!memcmp(arg, o->name, len)) {
	if(arg[len] == '\0')
	  return o->parse_long_noeq(argv[1]);
	else if(arg[len] == '=')
	  return o->parse_long_eq(arg+len+1);
      }
    }
  }
  ferr << argv0 << ": unknown option string: '--" << arg << "'" << endl;
  return -1;
}

static int parse_args(int argc, char* argv[])
{
  build_options();
  int i;
  for(i = 1; i < argc; i++) {
    const char* arg = argv[i];
    // Stop at the first non-option argument
    if(arg[0] != '-')
      break;
    // Stop after the first "-" or "--"
    if(arg[1] == '\0' ||
       (arg[1] == '-' && arg[2] == '\0')) {
      i++;
      break;
    }
    int j = (arg[1] != '-') ?
      parse_short(argc-i, argv+i) :
      parse_long(argc-i, argv+i);
    if(j < 0)
      usage(1);
    else
      i += j;
  }
  return i;
}

static void set_argv0(const char* p)
{
  argv0 = p;
  static const char* empty = "";
  const char* s = strrchr(p, '/');
  if(s) {
    ++s;
    argv0base = s;
    size_t length = s-p;
    char* tmp = new char[length+1];
    memcpy(tmp, p, length);
    tmp[length] = 0;
    argv0dir = tmp;
  }
  else {
    argv0base = p;
    argv0dir = empty;
  }
}

int main(int argc, char* argv[])
{
  struct timeval tv;
  gettimeofday(&tv, 0);
  srandom(tv.tv_usec ^ tv.tv_sec);
  
  set_argv0(argv[0]);
  int lastarg = parse_args(argc, argv);

  if(do_show_usage)
    usage(0);

  argc -= lastarg;
  argv += lastarg;
  if(argc < cli_args_min)
    usage(1, "Too few command-line arguments");
  if(cli_args_max >= cli_args_min && argc > cli_args_max)
    usage(1, "Too many command-line arguments");
  
  return cli_main(argc, argv);
}


syntax highlighted by Code2HTML, v. 0.9.1