/* Copyright 1995, 1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_option[] =
	    "$Id: option.c 63 2004-12-24 18:19:43Z ctm $";
#endif

#include "rsys/common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "rsys/flags.h"
#include "rsys/option.h"
#include "rsys/parsenum.h"
#include "rsys/notmac.h"

#ifdef NEXTSTEP

#ifndef OPENSTEP

#include <defaults/defaults.h>

#else /* OPENSTEP */

#include <Foundation/NSUserDefaults.h>
#include <Foundation/NSString.h>

#endif /* OPENSTEP */

#endif /* NEXTSTEP */

struct opt_block
{
  char *interface;
  option_t *opts;
  int n_opts;
} *opt_blocks;
static int n_opt_blocks_max;
static int n_opt_blocks;

static int max_pre_notes;
static int n_pre_notes;
static char **pre_notes;

static char *wrap_buf;
static int wrap_buf_size;

static int help_buf_len, help_buf_len_max;
static char *help_buf;

void
opt_init (void)
{
  
}

void
opt_shutdown (void)
{
  free (wrap_buf);
  free (help_buf);
  free (pre_notes);
  free (opt_blocks);
}

void
strcpy_to_wrap_buf (char *text)
{
  int text_len;
  char *src, *dst;

  text_len = strlen (text) + 1;

  if (wrap_buf_size < text_len)
    {
      wrap_buf = realloc (wrap_buf, text_len * sizeof *wrap_buf);
      wrap_buf_size = text_len;
    }
  for (dst = wrap_buf, src = text; *src;)
    {
      if (*src != '\n')
	*dst ++ = *src ++;
      else
	src ++;
    }
  *dst = '\0';
}

static void
wrap (char *buf,
      int desired_len,
      int *out_len, int *next_len)
{
  int buf_len;

  buf_len = strlen (buf);
  
  if (buf_len <= desired_len)
    {
      *out_len = buf_len;
      *next_len = -1;
    }
  else
    {
      char *t;

      t = &buf[desired_len];
      while (!isspace (*t))
	t --;
      *next_len = (t - buf) + 1;

      while (isspace (*t))
	t --;

      *out_len = (t - buf) + 1;
    }
}

void
_safe_strncat (char *dst, char *src, int len)
{
  char *t;
  int dst_len;

  dst_len = strlen (dst);
  t = &dst[dst_len];

  memcpy (t, src, len);

  dst[dst_len + len] = '\0';
}

void
send_to_help_buf (char *text, int len, int append_newline_p)
{
  if (help_buf == NULL)
    {
      help_buf = malloc (2 * len);
      help_buf_len_max = 2 * len;

      *help_buf = '\0';
      help_buf_len = 1;
    }
  else if (help_buf_len + len > help_buf_len_max)
    {
      help_buf_len_max *= 2;
      help_buf = realloc (help_buf, help_buf_len_max * sizeof *help_buf);
    }
  
  _safe_strncat (help_buf, text, len);
  help_buf_len += len;
  if (append_newline_p)
    {
      strcat (help_buf, "\n");
      help_buf_len += len;
    }
}

void
_generate_help_message (void)
{
  int i, block_i;
  char *buf;

  for (i = 0; i < n_pre_notes; i ++)
    {
      int out_len, next_len;
      char *pre_note = pre_notes[i];
      
      strcpy_to_wrap_buf (pre_note);
      
      for (buf = wrap_buf;; buf += next_len)
	{	   
	  wrap (buf, 75, &out_len, &next_len);
	  send_to_help_buf (buf, out_len, TRUE);
	  if (next_len == -1)
	    break;
	}
    }
  /* newline to separate pre-notes and options */
  send_to_help_buf ("", 0, TRUE);
  for (block_i = 0; block_i < n_opt_blocks; block_i ++)
    {
      char *interface = opt_blocks[block_i].interface;
      option_t *opts = opt_blocks[block_i].opts;
      int n_opts = opt_blocks[block_i].n_opts;
      int opt_i;

      send_to_help_buf (interface, strlen (interface), FALSE);
      send_to_help_buf (":", 1, TRUE);
      
      for (opt_i = 0; opt_i < n_opts; opt_i ++)
	{
	  char *spaces = "                    ";
	  int next_len, out_len;
	  
	  option_t *opt = &opts[opt_i];
	  int opt_text_len;
	  int same_line_p;

	  if (!opt->desc)
	    continue;
	  
	  opt_text_len = strlen (opt->text);
	  same_line_p = opt_text_len < 14;
	  
	  send_to_help_buf ("  -", 3, FALSE);
	  send_to_help_buf (opt->text, opt_text_len,
			    !same_line_p);
	  if (same_line_p)
	    send_to_help_buf (spaces,
			      20 - (3 + opt_text_len), FALSE);
	  else
	    send_to_help_buf (spaces, 20, FALSE);

	  strcpy_to_wrap_buf (opt->desc);
	  for (buf = wrap_buf;; buf += next_len)
	    {	   
	      wrap (buf, 55, &out_len, &next_len);
	      send_to_help_buf (buf, out_len, TRUE);
	      if (next_len != -1)
		send_to_help_buf (spaces, 20, FALSE);
	      else
		break;
	    }
	}
    }
}

char *
opt_help_message (void)
{
  if (help_buf == NULL)
    _generate_help_message ();
  return help_buf;
}

void
opt_register_pre_note (char *note)
{
  if (!pre_notes)
    {
      n_pre_notes = 0;
      max_pre_notes = 4;
      pre_notes = malloc (max_pre_notes * sizeof *pre_notes);
    }
  if (n_pre_notes == max_pre_notes)
    {
      max_pre_notes *= 2;
      pre_notes = realloc (pre_notes, max_pre_notes * sizeof *pre_notes);
    }
  pre_notes[n_pre_notes ++] = note;
}


#if !defined (MSDOS)
# define ERRMSG_STREAM stderr
#else
# define ERRMSG_STREAM stdout  /* stderr is annoying to DOS users. */
#endif


void
opt_register (char *new_interface,
	      option_t *new_opts, int n_new_opts)
{
  int block_i;
  struct opt_block *block;

  if (help_buf)
    {
      /* internal error, must register all options before generating
	 help message */
      fprintf (ERRMSG_STREAM, "\
%s: internal options error: opt register after help message generation.\n",
	       program_name);
      exit (-16);
    }
  
  /* check for conflicting options */
  for (block_i = 0; block_i < n_opt_blocks; block_i ++)
    {
      char *interface = opt_blocks[block_i].interface;
      option_t *opts = opt_blocks[block_i].opts;
      int n_opts = opt_blocks[block_i].n_opts;
      int opt_i, new_opt_i;
      
      for (opt_i = 0; opt_i < n_opts; opt_i ++)
	for (new_opt_i = 0; new_opt_i < n_new_opts; new_opt_i ++)
	  {
	    if (!strcmp (opts[opt_i].text,
			 new_opts[new_opt_i].text))
	      {
		/* conflicting options */
		fprintf (ERRMSG_STREAM, "\
%s: opt internal error: `%s' and `%s' both request option `%s'\n",
			 program_name,
			 interface, new_interface, opts[opt_i].text);
		exit (-16);
	      }
	  }
    }
  
  if (!opt_blocks)
    {
      n_opt_blocks = 0;
      n_opt_blocks_max = 4;
      opt_blocks = malloc (n_opt_blocks_max * sizeof *opt_blocks);
    }
  else if (n_opt_blocks == n_opt_blocks_max)
    {
      n_opt_blocks_max *= 2;
      opt_blocks = realloc (opt_blocks, n_opt_blocks_max * sizeof *opt_blocks);
    }
  
  block = &opt_blocks[n_opt_blocks ++];
  
  block->interface = new_interface;
  block->opts = new_opts;
  block->n_opts = n_new_opts;
}

opt_database_t *
opt_alloc_db (void)
{
  opt_database_t *retval;

  retval = malloc (sizeof *retval);

  retval->opt_vals = NULL;
  retval->n_opt_vals = 0;
  /* default maximum */
  retval->max_opt_vals = 4;
  
  return retval;
}

PRIVATE opt_val_t *
opt_lookup_helper (opt_database_t *db, char *opt)
{
  opt_val_t *retval;
  int i;  

  retval = 0;

  /* try to find this option in the database */
  for (i = 0; i < db->n_opt_vals; i ++)
    {
      if (strcmp (db->opt_vals[i].text, opt) == 0)
	{
	  retval = &db->opt_vals[i];
	  break;
	}
    }
  return retval;
}

opt_val_t *
opt_lookup (opt_database_t *db, char *opt)
{
  opt_val_t *retval;

  retval = opt_lookup_helper (db, opt);

#if defined(NEXTSTEP)
  if (!retval || !retval->t_val)
    {
#ifndef OPENSTEP
      static boolean_t defaults_registered;
      static NXDefaultsVector vec = { { NULL } };
      const char *try;
#else /* OPENSTEP */
      NSUserDefaults *defaults;
      NSString *try;
#endif /* OPENSTEP */

#ifndef OPENSTEP
      if (!defaults_registered)
	{
	  NXRegisterDefaults (ROMlib_appname, vec);
	  defaults_registered = TRUE;
	}
#else /* OPENSTEP */
      defaults = [NSUserDefaults standardUserDefaults];
      try = [defaults stringForKey:[NSString stringWithCString:opt]];
#endif /* OPENSTEP */

#ifndef OPENSTEP
      /* Allow dwrites to work. */
      try = NXGetDefaultValue (ROMlib_appname, opt);
#endif /* not OPENSTEP */
      if (try)
	{
	  if (retval)
#ifndef OPENSTEP
	    retval->val = strcpy (malloc (strlen (try) + 1), try);
#else /* OPENSTEP */
	    {
	      char *str;
	      int len;

	      len = [try cStringLength];
	      str = malloc (len+1);
	      [try getCString:str maxLength:len];
	      retval->val = str;
	    }
#endif /* OPENSTEP */
	  else
	    {
#ifndef OPENSTEP
	      opt_put_val (db, opt, try, pri_dwrite, FALSE);
#else /* OPENSTEP */
	      opt_put_val (db, opt, [try cString], pri_dwrite, FALSE);
#endif /* OPENSTEP */
	      retval = opt_lookup_helper (db, opt);
	    }
	}
    }
#endif

  return retval;
}

void
#ifndef OPENSTEP
opt_put_val (opt_database_t *db, char *opt, char *val,
#else /* OPENSTEP */
opt_put_val (opt_database_t *db, char *opt, const char *val,
#endif /* OPENSTEP */
	     priority_t pri, int temp_val_p)
{
  opt_val_t *opt_val;

  opt_val = opt_lookup_helper (db, opt);
  if (!opt_val)
    {
      if (db->opt_vals)
	{
	  /* this option is not yet in the database, add it */
	  if (db->n_opt_vals == db->max_opt_vals)
	    {
	      /* allocate some new ones */
	      db->max_opt_vals *= 2;
	      db->opt_vals = realloc (db->opt_vals,
				      (sizeof *(db->opt_vals)
				       * db->max_opt_vals));
	    }
	}
      else
	{
	  db->opt_vals = malloc (sizeof *(db->opt_vals) * db->max_opt_vals);
	}
      opt_val = &db->opt_vals[db->n_opt_vals ++];
      
      opt_val->text = opt;
      opt_val->val = NULL;
      opt_val->t_val = NULL;
    }

  if (temp_val_p)
    {
      opt_val->t_val = val;
      opt_val->t_pri = pri;
    }
  else
    {
      opt_val->val = val;
      opt_val->pri = pri;
    }
}

void
opt_put_int_val (opt_database_t *db, char *opt, int valint,
		 priority_t pri, int temp_val_p)
{
  char *val, buf[256];
  
  sprintf (buf, "%d", valint);
  val = malloc (strlen (buf) + 1);
  strcpy (val, buf);
  
  opt_put_val (db, opt, val, pri, temp_val_p);
}

#define option_value(opt_val) ((opt_val)->t_val ?: (opt_val)->val)

int
#ifndef OPENSTEP
opt_val (opt_database_t *db, char *opt, char **retval)
#else /* OPENSTEP */
opt_val (opt_database_t *db, char *opt, const char **retval)
#endif /* OPENSTEP */
{
  opt_val_t *opt_val;
#ifndef OPENSTEP
  char *val = NULL;
#else /* OPENSTEP */
  const char *val = NULL;
#endif /* OPENSTEP */
  boolean_t found_p = FALSE;

  opt_val = opt_lookup (db, opt);
  if (opt_val)
    {
      val = option_value (opt_val);
      if (val)
	{
	  if (retval)
	    *retval = val;
	  found_p = TRUE;
	}
    }
  return found_p;
}


/* Parses an integer value and returns it in *retval.  If
 * parse_error_p is non-NULL, sets parse_error_p to TRUE and prints an
 * error message in case of a parse error, else leaves it untouched.
 * Returns TRUE if a value was found.
 */
int
opt_int_val (opt_database_t *db, char *opt, int *retval,
	     boolean_t *parse_error_p)
{
  opt_val_t *opt_val;
#ifndef OPENSTEP
  char *val = NULL;
#else /* OPENSTEP */
  const char *val = NULL;
#endif /* OPENSTEP */

  opt_val = opt_lookup (db, opt);
  if (opt_val && (val = option_value (opt_val)) && retval)
    {
      int32 v;
      if (!parse_number (val, &v, 1))
	{
	  if (parse_error_p)
	    {
	      fprintf (stderr, "Malformed numeric argument to -%s.\n", opt);
	      *parse_error_p = TRUE;
	    }
	  return FALSE;
	}
      *retval = v;
    }

  /* Do *NOT* touch *parse_error_p if there is no error. */

  return opt_val && val;
}

int
opt_parse (opt_database_t *db, option_t *opts, int n_opts,
	   int *argc, char *argv[])
{
  int parse_error_p = FALSE;
  int i;
  int argc_left;

  /* we are sure to copy the null termination because we start with
     `i = 1' */
  argc_left = *argc;

  /* skip over the program name */
  for (i = 1; i < *argc; i ++, argc_left --)
    {
      char *arg;
      int arg_i;

      arg_i = i;
      arg = argv[i];
      /* option */
      if (*arg == '-')
	{
	  int opt_i;
	  
	  /* find the option among the options */
	  for (opt_i = 0; opt_i < n_opts; opt_i ++)
	    {
	      option_t *opt = &opts[opt_i];

	      if (!strcmp (&arg[1],
			   opt->text))
		{
		  char *optval = NULL;
		  
		  /* found the option */
		  switch (opt->kind)
		    {
		    case opt_no_arg:
		      optval = "1";
		      break;
		    case opt_optional:
		      if ((i + 1) < *argc && *argv[i + 1] != '-')
			{
			  optval = argv[++ i];
			  argc_left --;
			}
		      else
			optval = opt->opt_val;
		      break;
		    case opt_sticky:
		      optval = &arg[1 + strlen (opt->text)];
		      break;
		    case opt_sep:
		      if ((i + 1) < *argc)
			{
			  optval = argv[++ i];
			  argc_left --;
			}
		      else
			{
			  fprintf (ERRMSG_STREAM, "\
%s: option `-%s' requires argument\n",
				   program_name, opt->text);
			  parse_error_p = TRUE;
			}
		      break;
		    case opt_ignore:
		      goto next_arg;
		    case opt_sep_ignore:
		      if ((i + 1) < *argc)
			{
			  i ++;
			  argc_left --;
			}
		      else
			{
			  fprintf (ERRMSG_STREAM, "\
%s: option `-%s' requires argument\n",
				   program_name, opt->text);
			  parse_error_p = TRUE;
			}
		      goto next_arg;
		    }
		  *argc -= (i + 1) - arg_i;
		  memmove (&argv[arg_i], &argv[i + 1],
			   (argc_left-1) * sizeof *argv);
		  i = arg_i - 1;
		  opt_put_val (db, opt->text, optval, pri_command_line,
			       FALSE);
		}
	    }
	}
    next_arg:;
    }
  return parse_error_p;
}
