                        /* options.c,  1 Aug 26 */

#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "global.h"
#include "errors.h"
#include "utils.h"
#include "hashtab.h"
#include "fltval.h"
#include "readdb.h"
#include "arccomp.h"
#include "singlefile.h"
#include "chkfiles.h"
#include "levkeydist.h"
#include "comments.h"
#include "matchnum.h"
#include "needs.h"
#include "printnum.h"
#include "usage.h"
#include "words.h"
#include "options.h"


#define TRACE_FSPLIT    FALSE
#define TRACE_GETPATH   FALSE	/* very useful at start of a port! */

char minus_plus[2] = {'-', '+'};

#if defined(FULL_POSIX) || defined(SUN_UNIX)
struct stat ISexec;
#endif


/*	Given a relative path, search the PATH environment
 *      variable to find the absolute path
 *	NOTE: if the host sets argv[0] to the absolute path, then either:
 *		that host is MS-DOS and we check relpath first
 *		or that host is NOT unix so you probably need
 *              to modify this routine anyway!
 */
char	*getpath(char * relpath)
{
char	*pathlist;
char	*copy_of_paths;
char	*fullpath;
size_t	curpath = 0,
	nextpath;
FILE	*fp;
unsigned long	copy_path_len;

#if	TRACE_GETPATH
printf("getpath(%s)\n", relpath);
#endif

#ifdef  MS_DOS
/* MS-DOS searches current directory first */
if ((fp = fopen(relpath, "r")) != NULL)
   {
   fclose(fp);
   if (ISEXECUTABLE(relpath))
      {
#if     TRACE_GETPATH
printf("%s...exists...isexec\n", relpath);
#endif
      return relpath;
      }
   }
#endif

/* If the path which is passed contains a directory separator
 * character then the path name is either absolute, or
 * relative to the current directory
 */
if (strstr(relpath, directory_separator) != NULL)
   {
   if ((fp = fopen(relpath, "r")) != NULL)
      {
      fclose(fp);
      if (ISEXECUTABLE(relpath))
	 {
#if     TRACE_GETPATH
   printf("%s...exists...isexec\n", relpath);
#endif
         return relpath;
         }
      }
   return (char *)NULL;		/* ?? Can this be executed ?? */
   }

pathlist = GETVAR(PATHVAR);
if (pathlist == NULL)
   pathlist = "";		/* NOTEXEC under unix - cannot unset PATH */

#if	TRACE_GETPATH
printf("pathlist=\"%s\"\n", pathlist);
#endif

fullpath = (char *)get_heap(MAX_FILENAME_LEN+1);

/* we can't modify the environment string! */
copy_path_len = strlen(pathlist) + strlen(path_separator) + 1;
copy_of_paths = (char*)get_heap(copy_path_len);

strcpy(copy_of_paths, pathlist);
strcat(copy_of_paths, path_separator);

while (copy_of_paths[curpath]) /* while more paths to search... */
   {
   for (nextpath = curpath; copy_of_paths[nextpath]; ++nextpath)
     /* look for next path separator... */
      if (strncmp(&copy_of_paths[nextpath], path_separator,
                                              strlen(path_separator)) == 0)
         {
         copy_of_paths[nextpath] = '\0';
         /* having chopped string at that point, break out */
         break;
         }
   /* construct full pathname to test */
   strcpy(fullpath, &copy_of_paths[curpath]);
   strcat(fullpath, directory_separator);
   strcat(fullpath, relpath);
#if	TRACE_GETPATH
printf("trying %s...", fullpath);
#endif
   /* attempt to open file: */
   if ((fp = fopen(fullpath, "r")) != NULL)
      {
      /* success: close the file and check it is executable: */
      fclose(fp);
#if	TRACE_GETPATH
printf("exists...");
#endif
      if (ISEXECUTABLE(fullpath))
         {
         /* success: return the path found */
#if	TRACE_GETPATH
printf("exec\n");
#endif
         return fullpath;
         }
      }
#if	TRACE_GETPATH
printf("\n");
#endif
   /* keep searching paths... */
   curpath = nextpath + strlen(path_separator);
   }
#if     TRACE_GETPATH
printf("can't find %s\n", relpath);
#endif
/* no path matches: return NULL to indicate failure */
return (char *)NULL;
}


static char * get_exe_path(char * argv_0)
/*
 * Find the path that the program was run from
 */
{
int path_len;
char * full_exe_path;
#if defined(MS_DOS) || defined(CYGWIN)
char *exe_name;
#endif

if (argv_0 == (char *)NULL)
   return NULL;            /* NOT Really EXEC */

full_exe_path = getpath(argv_0);
 
#if defined(MS_DOS) || defined(CYGWIN)
if (full_exe_path == NULL)
   {
   exe_name=get_heap(strlen(argv_0)+1+4);
   strcpy(exe_name, argv_0);
#if defined(MS_DOS)
   strcat(exe_name, ".EXE");
#else
   strcat(exe_name, ".exe");
#endif
   full_exe_path=getpath(exe_name);
   }
#endif

if (full_exe_path == NULL)
   return NULL;

path_len=strlen(full_exe_path);
while (path_len > 0)
   {
   if (strncmp(full_exe_path+path_len-DSEP_LEN,
                                     directory_separator, DSEP_LEN) == 0)
      {
      full_exe_path[path_len]=NULL_CHAR;
      break;
      }
   else
      path_len--;
   }

// The POSIX standard requires that an array of appropriate size
// be passed.  It is making use of a common extension to pass NULL
// and expect the system to allocate the storage.
if (path_len == 0)
   full_exe_path=getcwd(NULL, 0);

return full_exe_path;
}


void get_default_db(char *prog_path)
{
char * exe_path = get_exe_path(prog_path);
char * db_name;
size_t exe_path_len;

if (exe_path != NULL)
   {
// Some environments do not append a directory separator at the
// end of the path.  So create space for a separator and add one
// if necessary.
   exe_path_len=strlen(exe_path);
   db_path=get_heap(exe_path_len+DSEP_LEN+strlen(DB_DIR)+1);
   strcpy(db_path, exe_path);
   if (strncmp(exe_path+exe_path_len-DSEP_LEN,
                                     directory_separator, DSEP_LEN) != 0)
      strcat(db_path, directory_separator);
   strcat(db_path, DB_DIR);
   db_name=get_heap(strlen(db_path)+1+strlen(DEFAULT_DB));
   strcpy(db_name, db_path);
   strcat(db_name, DEFAULT_DB);
   process_number_list(db_path, db_name);
   }
}


void get_default_lang_info(char *db_path)
{
char *li_name;

if (db_path != NULL)
   {
   li_name=get_heap(strlen(db_path)+1+strlen(DEFAULT_LANG_INFO));
   strcpy(li_name, db_path);
   strcat(li_name, DEFAULT_LANG_INFO);
   init_comment_handling(li_name);
   }
}


char *get_arg_str(char *arg_str, char *default_str)
{
char *opt_str = default_str;

if (arg_str != NULL)
   {
   opt_str=get_heap(strlen(arg_str)+1);
   strcpy(opt_str, arg_str);
   }

return opt_str;
}


long get_arg_val(char *arg_str, int max_arg_val)
{
unsigned long arg_val;
char *end_ptr;

errno=0;
arg_val = strtoul(arg_str, &end_ptr, 0);
if ((errno != 0) || ((arg_val == 0) && (end_ptr == arg_str)))
   {
   print_warning("Argument %s cannot be converted to an integer value\n",
							arg_str);
   arg_val=max_arg_val;
   }

if (arg_val > (unsigned long)max_arg_val)
   {
   print_warning("Argument value (%lu) exceeds maximum allowed (%d)\n",
							arg_val, max_arg_val);
   arg_val=max_arg_val;
   }

return arg_val;
}


BOOL arg_is_plus(char *arg_str)
{
return arg_str[strlen(arg_str)-1] != '-';
}


void trace_options(int argc, char *argv[])
{
int cur_arg;

for (cur_arg=1; cur_arg < argc; cur_arg++)
   {
   if (strncmp(argv[cur_arg], "-trace", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "input", 3) == 0)
	 do_trace_input=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "ndb", 3) == 0)
	 do_trace_ndb=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "progress", 3) == 0)
	 do_trace_progress=arg_is_plus(argv[cur_arg]);
      else
	 print_warning("Unknown option %s\n", argv[cur_arg]);
      }
   }
}


void list_options(int argc, char *argv[])
{
int cur_arg;

for (cur_arg=1; cur_arg < argc; cur_arg++)
   {
   if ((strcmp(argv[cur_arg], "-?") == 0) ||
       (strcmp(argv[cur_arg], "-help") == 0) ||
       (strcmp(argv[cur_arg], "-v") == 0) ||
       (strcmp(argv[cur_arg], "-V") == 0))
      {
      printf("numbers version 0.5.0 (%s, %s)\n", FP_ARITHMETIC, ARCHIVE_STR);
      printf("Options, first four characters significant (default values in parenthesis):\n");
      printf("    <file name>\n");
      printf("    <directory name>\n");
      printf("    -allow space%c\n",
					minus_plus[do_space_sep]);
      printf("    -format filename \"%s\" | linenum \"%s\" | pathname \"%s\"\n",
					format_filename,
					format_linenum,
					format_pathname);
      printf("    -fuzz-percent <number>(%s)\n", opt_fuzz_str);
      printf("    -help print version and options\n");
      printf("    -ignore comments%c\n",
					minus_plus[do_ignore_comments]);
      printf("    -levenshtein-distance <number>(%d)\n", min_levkey_dist);
      printf("    -match mantissa%c | value%c | <number>(%d)\n",
					minus_plus[match_mantissa],
					minus_plus[match_value],
					match_signif_digits);
      printf("    -mkndb all%c | <number>(%d)\n",
					minus_plus[mk_ndb_all],
					ndb_signif_digits);
      printf("    -need failmax(%d) | percent(%d) | suspect(%d)\n",
					max_fails_need_match,
					percent_need_match,
					sus_min_need_match);
      printf("    -ndb <filename>\n");
      printf("    -print all%c | location%c | match%c | nomatch%c | value%c | word%c | <number>(%d)\n",
					minus_plus[do_print_all],
					minus_plus[do_print_location],
					minus_plus[do_print_match],
					minus_plus[do_print_no_match],
					minus_plus[do_print_value],
					minus_plus[do_print_word],
					print_signif_digits);
      printf("    -summary match%c | nomatch%c\n",
					minus_plus[do_summary_match],
					minus_plus[do_summary_no_match]);
      printf("    -trace input%c | ndb%c | progress%c\n",
					minus_plus[do_trace_input],
					minus_plus[do_trace_ndb],
					minus_plus[do_trace_progress]);
      printf("    -usage file%c | mantissa%c\n",
					minus_plus[do_usage_file],
					minus_plus[do_usage_value]);
      printf("    -v, -V print version and options\n");
      printf("    -wildcard <shell-wildcard>\n");
      printf("    -words %d\n",
					max_words);
      exit(0);
      }
   }
}


void handle_options(int argc, char *argv[])
{
int cur_arg;
int cur_arg_file_len = 0;
int cur_arg_pattern_len = 0;

for (cur_arg=1; cur_arg < argc; cur_arg++)
   {
   if ((strcmp(argv[cur_arg], "?") == 0) ||
       (strcmp(argv[cur_arg], "-help") == 0) ||
       (strcmp(argv[cur_arg], "-v") == 0) ||
       (strcmp(argv[cur_arg], "-V") == 0))
      {
      // handled elsewhere
      }
   if (strncmp(argv[cur_arg], "-trace", 4) == 0) // Handled in earlier loop
      {
      cur_arg++;
      }
   else if (strncmp(argv[cur_arg], "-allow", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "space", 3) == 0)
	 do_space_sep=arg_is_plus(argv[cur_arg]);
      else
	 print_warning("Unknown option %s\n", argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-format", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "filename", 4) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            format_filename=get_arg_str(argv[cur_arg], "");
	 }
      else if (strncmp(argv[cur_arg], "linenum", 4) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            format_linenum=get_arg_str(argv[cur_arg], "");
	 }
      else if (strncmp(argv[cur_arg], "pathname", 4) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            format_pathname=get_arg_str(argv[cur_arg], "");
	 }
      else
         printf("Unknown -format option %s\n", argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-fuzz", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      init_fuzz_percent(argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-ignore", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "comments", 3) == 0)
	 do_ignore_comments=arg_is_plus(argv[cur_arg]);
      else
	 print_warning("Unknown option %s\n", argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-levenshtein", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      min_levkey_dist=get_arg_val(argv[cur_arg], 10);
      match_mantissa=TRUE; // Assume implied by appearance of this option
      }
   else if (strncmp(argv[cur_arg], "-match", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "mantissa", 3) == 0)
	 match_mantissa=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "value", 3) == 0)
	 match_value=arg_is_plus(argv[cur_arg]);
      else
	 match_signif_digits=get_arg_val(argv[cur_arg], MAX_NUM_DIGITS);
      }
   else if (strncmp(argv[cur_arg], "-mkndb", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "all", 3) == 0)
	 mk_ndb_all=arg_is_plus(argv[cur_arg]);
      else
	 ndb_signif_digits=get_arg_val(argv[cur_arg], MAX_NUM_DIGITS);
      do_mk_ndb=TRUE;
      }
   else if (strcmp(argv[cur_arg], "-need") == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "percent", 3) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            percent_need_match=get_arg_val(argv[cur_arg], 100);
	 }
      else if (strncmp(argv[cur_arg], "failmax", 3) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            max_fails_need_match=get_arg_val(argv[cur_arg], 100);
	 }
      else if (strncmp(argv[cur_arg], "suspect", 3) == 0)
	 {
         cur_arg++;
	 if (cur_arg < argc)
            sus_min_need_match=get_arg_val(argv[cur_arg], 100);
	 }
      else
	 printf("Unrecognised -need option: %s\n", argv[cur_arg]);
      }
   else if (strcmp(argv[cur_arg], "-ndb") == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      process_number_list("", argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-print", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "all", 3) == 0)
	 do_print_all=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "location", 3) == 0)
	 do_print_location=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "match", 3) == 0)
	 do_print_match=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "nomatch", 3) == 0)
	 do_print_no_match=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "value", 3) == 0)
	 do_print_value=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "word", 3) == 0)
	 do_print_word=arg_is_plus(argv[cur_arg]);
      else
	 print_signif_digits=get_arg_val(argv[cur_arg], MAX_NUM_DIGITS);
      }
   else if (strncmp(argv[cur_arg], "-summary", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "match", 3) == 0)
	 do_summary_match=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "nomatch", 3) == 0)
	 do_summary_no_match=arg_is_plus(argv[cur_arg]);
      else
	 print_warning("Unknown option %s\n", argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-usage", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (strncmp(argv[cur_arg], "file", 3) == 0)
	 do_usage_file=arg_is_plus(argv[cur_arg]);
      else if (strncmp(argv[cur_arg], "value", 3) == 0)
         do_usage_value=arg_is_plus(argv[cur_arg]);
      }
   else if (strncmp(argv[cur_arg], "-wildcard", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      if (save_arg_info(arg_patterns, &cur_arg_pattern_len, MAX_ARG_PATTERN_LEN,
			argv[cur_arg], "Size of file wildcard arguments exceeded at %s\n"))
	 num_arg_patterns++;
      }
   else if (strncmp(argv[cur_arg], "-words", 4) == 0)
      {
      cur_arg++;
      if (cur_arg == argc)
	 break;
      max_words=get_arg_val(argv[cur_arg], 500);
      }
   else
      {
      // treat as probably a file or directory name
      if (save_arg_info(arg_files, &cur_arg_file_len, MAX_ARG_FILE_LEN,
				argv[cur_arg], "Size of file arguments exceeded at %s\n"))
	 num_arg_files++;
      }
   } 
}


void init(int argc, char *argv[])
{
trace_options(argc, argv);
init_lev_distance();
init_fuzz_percent(opt_fuzz_str);
init_sing_file();
init_match();
init_needs();
handle_options(argc, argv);
get_default_db(argv[0]);
get_default_lang_info(db_path);
list_options(argc, argv);
if (!init_hash_table())
   {
   print_error("Cannot initialize hash table\n");
   exit(EXIT_FAILURE);
   }
init_words(max_words);
init_arccomp();
}

