/* Command line argument handling code */
/* ifile - intelligent mail filter for EXMH/MH
   Copyright (C) 1997  Jason Rennie <jrennie@ai.mit.edu>
   
   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 (see file 'COPYING'); if not, write to the Free
   Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA  02111-1307, USA.
   */

#include <ifile.h>
#include <argp.h>

const char *argp_program_version = IFILE_VERSION;
const char *argp_program_bug_address = NULL;

static char doc[] = "ifile -- core executable for the ifile mail filtering system";
static char args_doc[] = "[FILES...]";

static struct argp_option options[] = {
  {"concise",       'c', 0,        0,
   "Equivalent of \"ifile -v 0 | head -1 | cut -f1 -d' '\".  "
   "Must be used with -q or -Q."},
  {"threshold", 'T', "THRESH", 0,
   "With more than 1 folder, for top 2 folders (f0,f1) with ratings (r0,r1), "
   "  if THRESH > 0 then with -q print also 'diff[f0,f1](%) x.xx' and if "
   "  R=(r0-r1)/(r0+r1), R*1000 < THRESH, then "
   "  with -c -q print 'f0,f1' instead of jusrt 'f0'; disabled if THRESH=0."},
  {"query",       'q', 0,        0,
   "Output rating scores for each of FILES"},
  {"query-insert",'Q', 0,        0,
   "For each of FILES, output rating scores and add statistics for the "
   "folder with the highest score"},
  {"query-loocv", 'l', "FOLDER", 0,
   "For each of FILES, temporarily removes file from FOLDER, performs "
   "query and then reinserts file in FOLDER.  Database is not modified."},
  {"delete",      'd', "FOLDER", 0,
   "Delete the statistics for each of FILES from the category FOLDER"},
  {"insert",      'i', "FOLDER", 0,
   "Add the statistics for each of FILES to the category FOLDER"},
  {"update",      'u', "FOLDER", 0,
   "Same as 'insert' except only adds stats if FOLDER already exists"},
  {"keep-infrequent", 'k', 0,    0,
   "Leave in the database words that occur infrequently (normally they "
   "are tossed)"},
  {"verbosity",   'v', "LEVEL",  0,
   "Amount of output while running: 0=silent, 1=quiet, 2=progress, "
   "3=verbose, 4=debug"},
  {"folder-calcs", 'f', "FOLDER", 0,
   "Show the word-probability calculations for FOLDER"},
  {"reset-data",  'r', 0,        0,
   "Erases all currently stored information"},
  {"db-file",     'b', "FILE",    0,
   "Location to read/store ifile database.  Default is ~/.idata"},
  {"log-file",    'g', 0,        0,
   "Create and store debugging information in ~/.ifile_log"},
  {"occur",  'o', 0,        0,
   "Uses document bit-vector representation.  Count each word once per "
   "document."},
  {0, 0, 0, 0, "Lexing options:"},
  {"stemming",    'S', 0,        0,
   "Use 'Porter' stemming algorithm when lexing documents"},
  {"no-stoplist", 's', 0,        0,
   "Do not throw out overly frequent (stoplist) words when lexing"},
  {"white-lexer", 'w', 0,        0,
   "Lex words as sequences of space separated characters"},
  {"alpha-lexer", 'a', 0,        0,
   "Lex words as sequences of alphabetic characters (default)"},
  {"alpha-only-lexer", 'A', 0,        0,
   "Only lex space-separated character sequences which are composed "
   "entirely of alphabetic characters"},
  {"strip-header", 'h', 0,        0,
   "Skip all of the header lines except Subject:, From: and To:"},
  {"max-length", 'm', "CHAR", 0,
   "Ignore portion of message after first CHAR characters.  Use entire "
   "message if CHAR set to 0.  Default is 50,000."},
  {"print-tokens", 'p', 0, 0,
   "Just tokenize and print, don't do any other processing.  Documents are "
   "returned as a list of word, frequency pairs."},
  /*  {"tag-headers", 't', 0,        0,
   "Mark words in headers as being different from words in body of message (only for use with strip-header)"},
   */  {0, 0, 0, 0, "If no files are specified on the command line, ifile will use standard input as its message to process."},
  { 0 }
};

static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  /* Get the INPUT argument from argp_parse, which we know is a pointer to
     our arguments structure.  */
  arguments *args = state->input;

  if (arg == NULL)
    ifile_verbosify(ifile_debug, "parse_opt(key=%c, arg=NULL, state=%p)\n",
		    key, state);
  else
    ifile_verbosify(ifile_debug, "parse_opt(key=%c, arg=%s, state=%p)\n",
		    key, arg, state);

  /* The path to the binary is not interesting to us */
  /*  if (state->arg_num == 0)
    return 0;
    */

  switch (key)
    {
    case 'c':
      /* equivalent of "ifile -v 0 | head -1 | cut -f1 -d' '" */
      ifile_verbosify(ifile_verbose, "--concise\n");
      args->verbosity = 0;
      args->concise = TRUE;
      break;
    case 'q':
      ifile_verbosify(ifile_verbose, "--query\n");
      args->query = TRUE;
      args->read_message = TRUE;
      args->read_db = TRUE;
      break;
    case 'T':
      ifile_verbosify(ifile_verbose, "--threshold=%s\n", arg);
      args->thresh = arg ? atoi(arg) : 0;
      if (args->thresh < 0 || args->thresh > 200)
	{
	  ifile_verbosify(ifile_verbose, "Invalid threshold: %d\n",
			  args->thresh);
	  args->thresh = 0;
	}
       break;
    case 'Q':
      ifile_verbosify(ifile_verbose, "--query-insert\n");
      args->query = TRUE;
      args->query_insert = TRUE;
      args->read_message = TRUE;
      args->read_db = TRUE;
      args->write_db = TRUE;
      break;
    case 'l':
      ifile_verbosify(ifile_verbose, "--query-loocv\n");
      if (arg != NULL)
	args->loocv_folder = arg;
      else
	argp_usage (state);
      args->read_message = TRUE;
      args->read_db = TRUE;
      break;
    case 'd':
      ifile_verbosify(ifile_verbose, "--delete\n");
      if (arg != NULL)
	args->minus_folder = arg;
      else
	argp_usage (state);
      args->read_message = TRUE;
      args->read_db = TRUE;
      args->write_db = TRUE;
      break;
    case 'u':
      ifile_verbosify(ifile_verbose, "--update\n");
      args->create_folder = FALSE;
      if (arg != NULL)
	args->plus_folder = arg;
      else
	argp_usage (state);
      args->read_message = TRUE;
      args->read_db = TRUE;
      args->write_db = TRUE;
      break;
    case 'i':
      ifile_verbosify(ifile_verbose, "--insert\n");
      if (arg != NULL)
	args->plus_folder = arg;
      else
	argp_usage (state);
      args->read_message = TRUE;
      args->read_db = TRUE;
      args->write_db = TRUE;
      break;
    case 'k':
      ifile_verbosify(ifile_verbose, "--keep-infrequent\n");
      args->keep_infrequent = TRUE;
      break;
    case 'v':
      ifile_verbosify(ifile_verbose, "--verbosity=%s\n", arg);
      args->verbosity = arg ? atoi(arg) : ifile_quiet;
      if (args->verbosity < 0 || args->verbosity > 4)
	{
	  ifile_verbosify(ifile_verbose, "Invalid verbosity level: %d\n",
			  args->verbosity);
	  args->verbosity = ifile_debug;
	}
      break;
    case 'f':
      ifile_verbosify(ifile_verbose, "--folder-calc\n");
      args->folder_calcs = arg;
      break;
    case 'r':
      ifile_verbosify(ifile_verbose, "--reset-data\n");
      args->reset_data = TRUE;
      break;
    case 'S':
      ifile_verbosify(ifile_verbose, "--stemming\n");
      args->stemming = TRUE;
      break;
    case 's':
      ifile_verbosify(ifile_verbose, "--no-stoplist\n");
      args->stoplist = FALSE;
      break;
    case 'w':
      ifile_verbosify(ifile_verbose, "--white-lexer\n");
      args->lexer = WHITE_LEXER;
      break;
    case 'a':
      ifile_verbosify(ifile_verbose, "--alpha-lexer\n");
      args->lexer = ALPHA_LEXER;
      break;
    case 'A':
      ifile_verbosify(ifile_verbose, "--alpha-only-lexer\n");
      args->lexer = ALPHA_ONLY_LEXER;
      break;
    case 'h':
      ifile_verbosify(ifile_verbose, "--strip-header\n");
      args->skip_header = TRUE;
      break;
    case 'm':
      ifile_verbosify(ifile_verbose, "--max-length\n");
      if (arg)
	args->max_length = atoi(arg);
      else
	ifile_verbosify(ifile_quiet, "NULL string argument to max-length\n");
      break;
    case 'p':
      ifile_verbosify(ifile_verbose, "--print-tokens\n");
      args->print_tokens = TRUE;
      args->read_message = TRUE;
      break;
    case 't':
      ifile_verbosify(ifile_verbose, "--tag-headers\n");
      args->tag_headers = TRUE;
      break;
    case 'b':
      ifile_verbosify(ifile_verbose, "--db-file\n");
      args->db_file = arg;
      break;
    case 'g':
      ifile_verbosify(ifile_verbose, "--log-file\n");
      args->tmp_file = TRUE;
      break;
    case 'o':
      ifile_verbosify(ifile_verbose, "--occur\n");
      args->occur = TRUE;
      break;

    case ARGP_KEY_END:
      ifile_verbosify(ifile_verbose, "ARGP_KEY_END\n");
      /* Error if -c used, but not -q or -Q */
      if (args->concise && !(args->query || args->loocv_folder))
	ifile_error("Concise option '-c' requires query option, '-q' or '-Q'\n");
      break;

    case ARGP_KEY_SUCCESS:
      ifile_verbosify(ifile_verbose, "ARGP_KEY_SUCCESS\n");
      break;

    case ARGP_KEY_NO_ARGS:
      /* read file from standard input */
      ifile_verbosify(ifile_verbose, "ARGP_KEY_NO_ARGS\n");
      break;

    case ARGP_KEY_ARG:
      ifile_verbosify(ifile_verbose, "ARGP_KEY_ARG\n");
      /* Now we consume all the rest of the arguments.  STATE->arg_num is the
	 index in STATE->argv of the current argument to be parsed, which is
	 the first  we're interested in, so we can just use
	 `&state->argv[state->next]' as the value for arguments->strings.

	 IN ADDITION, by setting STATE->next to the end of the arguments, we
	 can force argp to stop parsing here and return.  */
      EXT_ARRAY_SET(args->file, char *, args->num_files,
		    state->argv[(state->next)-1]);
      args->num_files++;
      break;

    default:
      ifile_verbosify(ifile_verbose, "default\n");
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}
      

void
ifile_init_args (arguments * args)
{
  EXT_ARRAY_INIT(args->file, char *, 10);
  args->num_files = 0;
  args->query = FALSE;
  args->concise = FALSE;
  args->stemming = FALSE;
  args->stoplist = TRUE;
  args->lexer = ALPHA_LEXER;
  args->skip_header = FALSE;
  args->max_length = 50000;
  args->print_tokens = FALSE;
  args->tag_headers = FALSE;
  args->folder_calcs = NULL;
  args->keep_infrequent = FALSE;
  args->verbosity = ifile_quiet;
  args->minus_folder = NULL;
  args->plus_folder = NULL;
  args->loocv_folder = NULL;
  args->create_folder = TRUE;
  args->reset_data = FALSE;
  args->db_file = NULL; /* uses default DB file */
  args->tmp_file = FALSE;    /* create a /tmp/ifile.log.<userid> file? */
  args->occur = FALSE;  /* represents document as bit vector if TRUE */

  args->read_db = FALSE;
  args->write_db = FALSE;
  args->read_message = FALSE;
}

struct argp argp = { options, parse_opt, args_doc, doc };
