/* options.c: command line options and defaults */

#include <string.h>

#include "options.h"
#include "Boolean.h"
#include "pools.h"

static int *_argc;			/* pointer to argument count */
static char **_argv, 			/* pointer to current arg value */
            **first_arg;		/* pointer to first arg value */

/* initializes the global variables above */
#define init_arg(pargc, argv)	{_argc = pargc; _argv = first_arg = argv;}

/* tests whether arguments remain */
#define args_remain()		(_argv - first_arg < *_argc)

/* skips to next argument value */
#define next_arg()		{_argv++;}

/* prints argument list - for debugging purposes */
static void PrintArglist(void)
{
  char **av = first_arg;
  while (av - first_arg < *_argc) {
    fprintf(stderr, "'%s' ", *av); av++;
  }
}

/* consumes the current argument value, that is: removes it from the
 * list. */
static void consume_arg(void) 
{
  char **av = _argv;
  while (av - first_arg < *_argc-1) {
    *av = *(av+1); av++;
  }
  *av = NULL;
  (*_argc)--;
}

/* scans the current argument value for a value of given format */
#define get_argval(format, res)	(sscanf(*_argv, format, res) == 1)

/* ------------------- integer option values --------------------- */
static int getint(int *n, void *data)
{
  if (!get_argval("%d", n)) {
    fprintf(stderr, "'%s' is not a valid integer value\n", *_argv);
    return FALSE;
  }
  return TRUE;
}

static void printint(FILE *fp, int *n, void *data)
{
  fprintf(fp, "%d", *n);
}

static int dummy_int = 0;

CMDLINEOPTTYPE intTypeStruct = { 
  (int (*)(void *, void *))getint,
  (void (*)(FILE *, void *, void *))printint ,
  (void *)&dummy_int,
  NULL
};

/* ------------------- string option values --------------------- */
static int getstring(char **s, void *data)
{
  *s = Alloc(strlen(*_argv)+1);
  sprintf(*s, "%s", *_argv);
  return TRUE;
}

static void printstring(FILE *fp, char **s, void *data)
{
  fprintf(fp, "'%s'", *s ? *s : "");
}

static char *dummy_string = NULL;

CMDLINEOPTTYPE stringTypeStruct = { 
  (int (*)(void *, void *))getstring,
  (void (*)(FILE *, void *, void *))printstring ,
  (void *)&dummy_string,
  NULL
};

/* ------------------- copied string (maxlength n) option values --------------------- */
int Tnstring_get(char *s, int n)
{
  if(s) {
    strncpy(s, *_argv, n);
    s[n-1] = '\0';  /* ensure zero ending c-string */
  }
  
  return TRUE;
}

void Tnstring_print(FILE *fp, char *s, int n)
{
  fprintf(fp, "'%s'", s ? s : "");
}

char *Tnstring_dummy_val = NULL;


/* ------------------- enumerated type option values --------------------- */
static void print_enum_vals(ENUMDESC *tab)
{
  while (tab && tab->name) {
    fprintf(stderr, "\t%s\n", tab->name);
    tab++;
  }
}

int Tenum_get(int *v, ENUMDESC *tab)
{
  ENUMDESC *tabsave = tab;
  while (tab && tab->name) {
    if (strncasecmp(*_argv, tab->name, tab->abbrev) == 0) {
      *v = tab->value;
      return TRUE;
    }
    tab++;
  }
  fprintf(stderr, "Invalid option argument '%s'. Should be one of:\n", *_argv);
  print_enum_vals(tabsave);
  return FALSE;
}

void Tenum_print(FILE *fp, int *v, ENUMDESC *tab)
{
  while (tab && tab->name) {
    if (*v == tab->value) {
      fprintf(fp, "%s", tab->name);
      return;
    }
    tab++;
  }
  fprintf(fp, "INVALID ENUM VALUE!!!");
}

int Tenum_dummy_val = 0;

static ENUMDESC Tenum_dummy_table[] = {
  { 0, NULL, 0 }
};

CMDLINEOPTTYPE enumTypeStruct = {
  (int (*)(void *, void *))Tenum_get,
  (void (*)(FILE *, void *, void *))Tenum_print,
  (void *)&Tenum_dummy_val,
  (void *)Tenum_dummy_table
};

/* ------------------- boolean (yes|no) option values-------------------- */
/* implemented as an enumeration type */

static ENUMDESC boolTable[] = {
  { TRUE, "yes", 1 },
  { FALSE, "no", 1 },
  { TRUE, "true", 1 },
  { FALSE, "false", 1 },
  { 0, NULL, 0 }
};

CMDLINEOPTTYPE boolTypeStruct = {
  (int (*)(void *, void *))Tenum_get,
  (void (*)(FILE *, void *, void *))Tenum_print,
  (void *)&Tenum_dummy_val,
  (void *)boolTable
};

/* ------------------- set true/false option values --------------------- */

static int dummy_true  = TRUE;
static int dummy_false = FALSE;

static int set_true(int *x, void *data)
{
  /* No option expected on command line, nothing consumed */

  *x = TRUE;
  return TRUE;
}

static int set_false(int *x, void *data)
{
  /* No option expected on command line, nothing consumed */

  *x = FALSE;
  return TRUE;
}

static void print_other(FILE *fp, void *x, void *data)
{
  fprintf(fp, "other");
}


CMDLINEOPTTYPE setTrueTypeStruct = {
  (int (*)(void *, void *))set_true,
  (void (*)(FILE *, void *, void *))print_other,
  (void *)&dummy_true,
  (void *)NULL
};

CMDLINEOPTTYPE setFalseTypeStruct = {
  (int (*)(void *, void *))set_false,
  (void (*)(FILE *, void *, void *))print_other,
  (void *)&dummy_false,
  (void *)NULL
};


/* ------------------- float option values --------------------- */
static int getfloat(float *x, void *data)
{
  if (!get_argval("%f", x)) {
    fprintf(stderr, "'%s' is not a valid floating point value\n", *_argv);
    return FALSE;
  }
  return TRUE;
}

static void printfloat(FILE *fp, float *x, void *data)
{
  fprintf(fp, "%g", *x);
}

static float dummy_float = 0.;

CMDLINEOPTTYPE floatTypeStruct = { 
  (int (*)(void *, void *))getfloat,
  (void (*)(FILE *, void *, void *))printfloat ,
  (void *)&dummy_float,
  NULL
};

/* ------------------- CIE xy option values --------------------- */
static int getxy(float *c, void *data)
{
  int ok = TRUE;
  ok = get_argval("%f", &c[0]);
  if (ok) {
    consume_arg();
    ok &= args_remain() && get_argval("%f", &c[1]);
  }
  if (!ok)
    fprintf(stderr, "invalid CIE xy color argument value");

  return ok;
}

static void printxy(FILE *fp, float *c, void *data)
{
  fprintf(fp, "%g %g", c[0], c[1]);
}

static float dummy_xy[2] = {0., 0.};

CMDLINEOPTTYPE xyTypeStruct = {
  (int (*)(void *, void *))getxy,
  (void (*)(FILE *, void *, void *))printxy,
  (void *)&dummy_xy,
  NULL
};

/* ------------------- argument parsing --------------------------- */
#ifndef MAX
#define MAX(a,b)	(((a)>(b)) ? (a) : (b))
#endif

static CMDLINEOPTDESC *LookupOption(char *s, CMDLINEOPTDESC *options)
{
  CMDLINEOPTDESC *opt = options;
  while (opt->name) {
    if (strncmp(s, opt->name, MAX(opt->abbrevlength>0 ? opt->abbrevlength : strlen(opt->name), strlen(s))) == 0) 
      return opt;
    opt++;
  }
  return (CMDLINEOPTDESC *)NULL;
}

static void PrintOption(FILE *fp, CMDLINEOPTDESC *opt)
{
  if (opt->description) {
    fprintf(fp, opt->description);
    fprintf(fp, " ");
    if (opt->type && opt->value &&
	(opt->type != &setTrueTypeStruct) &&
	(opt->type != &setFalseTypeStruct)) {
      fprintf(fp, "(default = ");
      opt->type->print(fp, opt->value, opt->type->data);
      fprintf(fp, ")");
    }
    fprintf(fp, "\n");
  }
}

static void process_arg(CMDLINEOPTDESC *options)
{
  CMDLINEOPTDESC *opt = LookupOption(*_argv, options);
  if (opt) {
    int ok = TRUE;
    if (opt->type) {
      if ((opt->type == &setTrueTypeStruct) ||
	  (opt->type == &setFalseTypeStruct)) {
	if (!opt->type->get(opt->value ? opt->value : opt->type->dummy, opt->type->data))
	  ok = FALSE;
      }
      else {
	consume_arg();
	if(args_remain()) {
	  if (!opt->type->get(opt->value ? opt->value : opt->type->dummy, opt->type->data))
	    ok = FALSE;
	} else {
	  fprintf(stderr, "Option argument missing.\n");
	  ok = FALSE;
	}
      }
      if (!ok)
	PrintOption(stderr, opt);
    }
    if (ok && opt->action) opt->action(opt->value ? opt->value : (opt->type ? opt->type->dummy : NULL));
    consume_arg();
  } else
    next_arg();
}

void ParseOptions(CMDLINEOPTDESC *options, int *argc, char **argv)
{
  init_arg(argc, argv);
  while (args_remain())
    process_arg(options);
}

void PrintOptions(FILE *fp, CMDLINEOPTDESC *opt)
{
  while (opt->name) {
    PrintOption(fp, opt);
    opt++;
  }
}
