/*
** ckitem  semi-clone
**
**
** will implement a close, semi-functional version of the SVR4
** ckitem(1) program.
**
**    1.4   01/28/93   relling  added fixed " [?,??,q]:" to prompt sting
**    1.3   12/02/91   relling  removed extra \n from list (probably need pager)
**    1.2   06/05/91   relling  added exit code 4 (no choices)
**    1.1   03/19/91   relling  created
*/

#ifndef lint
static char SCCS_id[] = "@(#)ckitem.c   1.4   1/28/93   Auburn University Engineering";
#endif

#include <stdio.h>
#include <string.h>
#include <signal.h>

/* defines for return codes */
#define EXIT_SUCCESS 0
#define EXIT_EOF 1
#define EXIT_USAGE_ERR 2
#define EXIT_USER_QUIT 3
#define EXIT_NO_CHOICES 4

/* thingys for handling command line options */
#define OPTION_STR "d:f:l:p:Q"
#define OPT_USAGE "[-Q][-d default][-f file][-l label][-p prompt][choice [...]]"
extern int optind;
extern char *optarg;

/* other defines */
#define DEFAULT_PROMPT_STR "Enter selection"

/* macro to check success of malloc */
#define CHECK_MALLOC(x)   if( x == NULL) { \
       fprintf( stderr, "error: couldn't allocate memory\n"); \
       exit( EXIT_USAGE_ERR); }

/* the menu items are placed in this structure */
struct items {
  char *token;
  char *desc;
  struct items *next;
};

static char default_item[BUFSIZ] = "";   /* default string */
static char prompt[BUFSIZ] = DEFAULT_PROMPT_STR;   /* default prompt */
static char label[BUFSIZ] = "";  /* default label */
static int allow_q_check = 1;  /* 1 means user can type 'q' (see -Q option) */
static struct items *itemlist;  /* linked list for items */

main( argc, argv)
int argc;
char *argv[];
{
  char buf[BUFSIZ];   /* input character buffer */
  int i;   /* general purpose int */
  struct items *current_item;  /* pointer into item list */
  int handle_signal();

  /* initialize items list */
  itemlist = (struct items *)malloc( sizeof( struct items));
  CHECK_MALLOC( itemlist);
  itemlist->next = NULL;

  /* setup signal handler to catch user termination */
  (void)signal( SIGHUP, handle_signal);
  (void)signal( SIGINT, handle_signal);
  (void)signal( SIGQUIT, handle_signal);
  (void)signal( SIGTSTP, handle_signal);

  /* read & handle options */
  while(( i = getopt( argc, argv, OPTION_STR)) != -1) {
    switch( i) {
    case 'd':   /* change default (no validity checks) */
      (void)strcpy( default_item, optarg);
      break;
    case 'f':   /* read items from file */
      read_items_from_file( optarg);
      break;
    case 'l':   /* menu label */
      (void)strcpy( label, optarg);
      break;
    case 'p':   /* query prompt */
      (void)strcpy( prompt, optarg);
      break;
    case 'Q':   /* don't allow user to type 'q' */
      allow_q_check = 0;
      break;
    case '?':   /* usage error */
      (void)fprintf( stderr, "usage: %s\n", OPT_USAGE);
      exit( EXIT_USAGE_ERR);
    }
  }    

  /* if more args in argv, then move pointer to end of item list */
  if( optind < argc) {
    current_item = itemlist;
    while( current_item->next) {
      current_item = current_item->next;
    }
  }
  /* add any additional items left in argv */
  for( ; optind < argc; optind++) {
    current_item->token = (char *)malloc( strlen( argv[optind]) + 1);
    CHECK_MALLOC( current_item->token);
    (void)strcpy( current_item->token, argv[optind]);
    current_item->desc = (char *)malloc( 8);
    CHECK_MALLOC( current_item->desc);
    (void)strcpy( current_item->desc, "");
    current_item->next = (struct items *)malloc( sizeof( struct items));
    CHECK_MALLOC( current_item->next);
    current_item = current_item->next;
    current_item->next = NULL;
  }

  /* check to see if there were any items */
  if( itemlist->next == NULL) {
    exit( EXIT_NO_CHOICES);
  }
    
  /* main loop */
  print_label();
  print_menu();
  while( handle_input())
    ;

  exit( EXIT_SUCCESS);
} /* end of main */


/*
** handle_signal
**
** upon receiving a user-initiated signal, return with EXIT_USER_QUIT
**
*/
int handle_signal( sig, code, scp, addr)
int sig, code;
struct sigcontext *scp;
char *addr;
{
  exit( EXIT_USER_QUIT);
} /* end of function handle_signal */


/*
** read_items_from_file
**
** open a file and load the item struct from it's contents.
** format is:
**    token\tdescription
** strings starting with '#' are comments.
** NULL strings are ignored.
**
*/
read_items_from_file( filename)
char *filename;
{
  FILE *fp;
  char buf[BUFSIZ], *buf2;
  struct items *current_item = itemlist;

  if(!(fp = fopen( filename, "r"))) {
    fprintf( stderr, "ckitem: couldn't read file %s", filename);
    exit( EXIT_USAGE_ERR);
  }

  /* get to staring point in itemlist */
  while( current_item->next) {
    current_item = current_item->next;
  }

  while( fgets( buf, BUFSIZ, fp)) {
    if( strlen(buf) > 0 && buf[0] != '#' && buf[0] != '\n') {
      /* add tokens to itemlist */
      buf2 = strtok( buf, "\t\n");
      if( !buf2)
	buf2 = "";
      current_item->token = (char *)malloc( strlen( buf2) + 1);
      CHECK_MALLOC( current_item->token);
      (void)strcpy( current_item->token, buf2);
      buf2 = strtok( NULL, "\n");
      if( !buf2)
	buf2 = "";
      current_item->desc = (char *)malloc( strlen( buf2) + 1);
      CHECK_MALLOC( current_item->desc);
      (void)strcpy( current_item->desc, buf2);

      /* add to bottom of list */
      current_item->next = (struct items *)malloc( sizeof( struct items));
      CHECK_MALLOC( current_item->next);
      current_item = current_item->next;
      current_item->next = NULL;
    }
  }
  fclose( fp);
}  /* end of function read_items_from_file */


/*
** print_error_msg
**
*/
void print_error_msg()
{
  fprintf( stderr, "ERROR - Does not match an available menu selection.\n");
  print_help_msg();
} /* end of function print_error_msg */

/*
** print_help_msg
**
*/
print_help_msg()
{
  fprintf( stderr, "Enter one of the following:\n");
  fprintf( stderr, "- the number fo the menu item you wish to select,\n");
  fprintf( stderr, "- the token associated with the menu item,\n");
  fprintf( stderr, "- partial string which uniquely identifies the token for the menu item,\n");
  fprintf( stderr, "- ?? to reprint the menu.\n\n");
} /* end of function print_help_msg */

/*
**  print_label
**
*/
print_label()
{
  if( *label != '\0')
    fprintf( stderr, "%s\n\n", label);
  else
    fprintf( stderr, "\n");
}

/*
** print_menu
**
*/
print_menu()
{
  int i = 1;
  struct items *current_item = itemlist;

  while( current_item->next) {
    /* add default note if applicable */
    if( !strcmp( default_item, current_item->token))
      fprintf( stderr, "[default] ");
    else
      fprintf( stderr, "          ");

    fprintf( stderr, "%d - %s", i, current_item->token);

    if( *current_item->desc == '\0')
      fprintf( stderr, "   ");
    else
      fprintf( stderr, " - ");

    fprintf( stderr, "%s\n", current_item->desc);
    i++;
    current_item = current_item->next;
  }
  fprintf( stderr, "\n");
} /* end of function print_menu */

/*
**  handle_input
**
** this is the main loop routine.  We will prompt the user for
** input, then take action based on the input.
**
** upon making a matched selection, return 0, otherwise 1 to recycle.
**
*/
int handle_input()
{
  char buf[BUFSIZ];
  
  /* show prompt string */
  fputs( prompt, stderr);
  if( allow_q_check) fputs( " [?,??,q]: ", stderr);
  else fputs( " [?,??]: ", stderr);

  /* read from terminal */
  if( !gets( buf))  {
    /* EOF or other ailment encountered */
    exit( EXIT_EOF);
  }
  
  /* check for user hitting [return] */
  if( buf[0] == '\0') {
    /* be alittle bit nice about lack of default */
    if( default_item[0] == '\0') {
      fprintf( stderr, "ERROR - no default action available\n");
      return(1);
    }
    return( check_selection( default_item));
  }

  return( check_selection( buf));
} /* end of function handle_input */

/*
**  check_selection
**
** upon making a matched selection, return 0, otherwise 1 to recycle
*/
int check_selection( buf)
char *buf;
{
  int i;  /* general purpose int */
  int input_number;    /* integer part of input string */
  char *terminator[BUFSIZ];   /* string which may be after integer part */
  int matches = 0;     /* counter for partial string matches */
  char *matched_token; /* matched token for possible return */
  struct items *current_item = itemlist;  /* position in list */

  /* check for ? */
  if( !strcmp( buf, "?")) {
    print_help_msg();
    return(1);
  }

  /* check for ?? */
  if( !strcmp( buf, "??")) {
    print_help_msg();
    print_label();
    print_menu();
    return(1);
  }

  /* check for 'q', if allowed */
  if( allow_q_check && !strcmp( buf, "q")) {
    exit( EXIT_USER_QUIT);
  }
  
  /* check for item number */
  /* see if string is a complete integer, if not, continue */
  input_number = (int)strtol( buf, terminator, 10);

  if( **terminator == '\0') {
    /* got whole integer, so traverse list */
    for( i = 1; i < input_number; i++) {
      if( current_item->next == 0) {
	/* end of list too soon */
	print_error_msg();
	return(1);
      }
      current_item = current_item->next;
    }
    /* end of list too late? */
    if( current_item->next == 0 || input_number < 1) {
      print_error_msg();
      return(1);
    }
    printf( "%s\n", current_item->token);
    return(0);
  }
  
  /* check for string part */
  i = strlen( buf);
  while( current_item->next) {
    if( !strncmp( buf, current_item->token, i)) {
      /* at least a partial match */
      matches++;
      matched_token = current_item->token;
    }
    current_item = current_item->next;
  }
  if( matches == 1)  {
    /* unique match of partial string */
    printf( "%s\n", matched_token);
    return(0);
  }
  
  /* print error message and prompt again */
  print_error_msg();
  return(1);
} /* end of function check_selection */
