/***************************************************************************
 *   Copyright (C) 2008 by Peter Eschright                                 *
 *   petereschright@gmail.com                                              *
 *                                                                         *
 *   This source code is presented for informational purposes only.        *
 *   Please contact the author for permision for use.                      *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#include "user_input.h"

/***************************************************************************
*                                                                          *
*  user_input.c                                                            *
*                                                                          *
*  A library for simple safe command line input from a user.               *
*  Include functions for inputing most data types, with error              *
*  checking and safety.                                                    *
*                                                                          *
*                                                                          *
*  Usage:                                                                  *
*                                                                          *
*  Compile and link with your project, or build seperatly and link.        *
*                                                                          *
*                                                                          *
*  Notes:                                                                  *
*  - All functions read in entire input buffer and make sure buffer is     *
*    ready for next input.                                                 *
*  - Maximum read size for all functions is defined by READ_SIZE           *
*                                                                          *
*   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *  *
*  Error codes--  (defined in user_input.h)                                *
*                                                                          *
*  user failed to input desired data type:  USER_INPUT_ERROR_BAD_INPUT     *
*                                                                          *
*  user input exceeded allowed size:        USER_INPUT_ERROR_TOO_LONG      *
*                                                                          *
*  std input stream was closed:             USER_INPUT_ERROR_CLOSED_STREAM *
*                                                                          *
*  input read failed for other reason:      USER_INPUT_ERROR_FAILED_READ   *
*                                                                          *
***************************************************************************/



/**************************************************
*                                                 *
*  Normalize a string:                            *
*  copy normalized str_1 to str_2                 *
*                                                 *
*  String is trimmed of preceding and trailing    *
*  whitespace. string has internal whitespace     *
*  collapsed to single space character.           *
*                                                 *
**************************************************/
void trim_and_normalize(char *str_2,char *str_1){
   register char *ptr1 = str_1;
   register char *ptr2 = str_2;
   register int state = 0;
   while(1){
      if(state == 0){
         while( isspace(*ptr1) ) ++ptr1;
         state = 1;
      }
      else if(state == 1){
         do
           *(ptr2++) = *(ptr1++);
         while(((*ptr1) != NULL) && !isspace(*ptr1));
         if(*ptr1 == '\0'){
            break;
         }
         state = 2;
      }
      else if(state == 2){
         while(isspace(*ptr1) && *ptr1 != '\n') ++ptr1;
         if(*ptr1 == '\n'){
            break;
         }
         else{
            *(ptr2++) = ' ';
            state = 1;
         }
      }
   }
   *ptr2 = '\0';
   return;
}





/*********************************
* read in an unsigned integer    *
* return 0 iff sucessful         *
* return error code if failed    *
*********************************/
int user_input_uint(unsigned int *get_number){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
  
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      input_type_ok = sscanf(read_str,"%u",get_number);
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 
}




/*********************************
* read in an integer             *
* return 0 iff sucessful         *
* return error code if failed    *
*********************************/
int user_input_int(int *get_number){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
  
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      input_type_ok = sscanf(read_str,"%d",get_number);
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 
}




/*********************************
* read in an float               *
* return 0 iff sucessful         *
* return error code if failed    *
*********************************/
int user_input_float(float *get_number){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
  
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      input_type_ok = sscanf(read_str,"%f",get_number);
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 
}


/*************************************************
* read in an double                              *
*                                                *
* Function reads float and casts it to a double. *
* return 0 iff sucessful                         *
* return error code if failed                    *
*************************************************/
int user_input_double(double *get_number){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
   float temp = 0.0;
   
   
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      input_type_ok = sscanf(read_str,"%f",&temp);
      *get_number = (double)temp;
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 

}




/*********************************
* read in a single char          *
* return 0 iff sucessful         *
* return error code if failed    *
*********************************/
int user_input_char(char *get_c){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
  
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      input_type_ok = sscanf(read_str,"%1s",get_c);
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 
}




/**************************************************
*  read in a string                               *
*                                                 *
*  String is trimmed of preceding and trailing    *
*  whitespace.                                    *
*                                                 *
*  upto size - 1 characters are read.             *
*  USER_INPUT_ERROR_BAD_INPUT is returned if      *
*  input was too long for get_str and remaining   *
*  input is removed from buffer.                  *
*                                                 *
*  return 0 iff sucessful                         *
*  return error code if failed                    *
**************************************************/
int user_input_str(char *get_str, int size){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   char trimmed_str[READ_SIZE]; trimmed_str[0] = 0;  /* used to hold trimmed version of input */
   int read_return; /* captures return from read */
   int buffer_not_clean = 0; /* used to see that all input was read */
   int eof_set = 0; /* set to one if eof encountered */
   int failed_eof_set = 0; /* set to one if eof encountered during first read
                              used to determine reason for bad read  */
   int error_set = 0; /* set to one if error encountered */
   int failed_error_set = 0; /* set to one if error encountered during first read
                                used to determine reason for bad read  */
   int input_type_ok = 0; /* set to one iff input was valid */
   int input_too_long = 0; /* set to one iff input was longer than size size */
   
   /* get input from stdin */
   if(!fgets(read_str,READ_SIZE,stdin)){
      /* if input failed do error checking and return */
      if(feof(stdin)){
         eof_set = 1;
         failed_eof_set = 1;
      }
      if(ferror(stdin)){
         error_set = 1;
         failed_error_set = 1;
      }
   }
   /* see that all input was read, look for newline */
   if(!strstr(read_str,"\n")){
      buffer_not_clean = 1;
   }
   /* attempt to extract value */
   if(!error_set){
      if(buffer_not_clean){
         input_type_ok = 0;
         input_too_long = 1;
      }else{ 
         /* trim the input */
         trim_and_normalize(trimmed_str,read_str);
         /* make sure size was ok */
         if((strlen(trimmed_str) + 1 ) > size){
            input_type_ok = 0;
            input_too_long = 1;
         }
         else{ /* looks ok */
           strcpy(get_str,trimmed_str);
           input_type_ok = 1;
         }
      }
   }
   
   /* clean up messes, order is important */
   
   /* if error set clear error flag */
   /* come back if failed to clear error */
   retry_error_clear:
   
   if(error_set){
      clearerr(stdin);
      error_set = 0;
   }
   
   /* empty buffer if needed */
   if(buffer_not_clean){
      int detect_eof;
      while((detect_eof = getchar()) != '\n' && (detect_eof != EOF));
      if(detect_eof == EOF){
        /* see if another error occured */
        if(ferror(stdin)){
          error_set = 1;
          goto retry_error_clear;
        }
        /* else assume eof reached ok */
        eof_set = 1;
      }
      buffer_not_clean = 0;
   }
   
    /* if eof set clear flag  */
   if(eof_set){
      clearerr(stdin);
   }
   
   /* return appropriate value */
   if(input_type_ok == 1)
     return(0);
   else if(input_too_long){
     get_str[0] = 0;
     return(USER_INPUT_ERROR_TOO_LONG);
   }
   else if(failed_error_set)
     return(USER_INPUT_ERROR_FAILED_READ);
   else if(failed_eof_set)
     return(USER_INPUT_ERROR_CLOSED_STREAM);
   else
     return(USER_INPUT_ERROR_BAD_INPUT); 
}




/**************************************************
*  read in a word, defined as a continuos         *
*  string of non-whitespace characters.           *
*                                                 *
*  word is trimmed of preceding and trailing      *
*  whitespace.                                    *
*                                                 *
*  upto size - 1 characters are read.             *
*  USER_INPUT_ERROR_BAD_INPUT is returned if      *
*  input was too long for get_str and remaining   *
*  input is removed from buffer.                  *
*                                                 *
*  return 0 iff sucessful                         *
*  return error code if failed                    *
**************************************************/
int user_input_word(char *get_word, int size){
   char read_str[READ_SIZE]; read_str[0] = 0;  /* used to read from stdin */
   register int read_value = 0;
   register int i = 0;
   register char c;
   read_value = user_input_str(read_str,READ_SIZE);
   if(read_value){
      return(read_value);
   }
   c = read_str[i];
   while(c && !isspace(c) && (i < (size - 1))){
     get_word[i] = c;
     ++c;
     ++i;
     c = read_str[i];
   }
   get_word[i] = '\0';
   if(i < size)
      return(0);
   else
      return(USER_INPUT_ERROR_TOO_LONG);
}





/**************************************************
*  read in a yes or no response,                  *
*  defined as follows                             *
*  string of non-whitespace characters.           *
*                                                 *
*  preceding whitespace and all characters        *
*  first word (see above) are ignored.            *
*                                                 *
*  answer is set to 1 iff yes or 0 iff no         *
*  or -1 iff error)                               *
*                                                 *
*  The input is then interpreted as follows:      *
*  input   interpretation   value of answer       *
*  YES     yes              1                     *
*  Yes     yes              1                     *
*  yes     yes              1                     *
*  Y       yes              1                     *
*  y       yes              1                     *
*  NO      no               0                     *
*  No      no               0                     *
*  no      no               0                     *
*  N       no               0                     *
*  n       no               0                     *
*  other   error            -1                    *
*                                                 *
*                                                 *
*  USER_INPUT_ERROR_BAD_INPUT is returned if      *
*  input was not of form described                *
*  Unused input is removed from buffer.           *
*                                                 *
*  return 0 iff sucessful                         *
*  return error code if failed                    *
**************************************************/
int user_input_yes_no(int *answer){
   char answr_str[8]; answr_str[0] = '\0';
   user_input_word(answr_str,8);
   if(
      (strcmp("YES",answr_str) == 0)
      ||
      (strcmp("Yes",answr_str) == 0)
      ||
      (strcmp("yes",answr_str) == 0)
      ||
      (strcmp("Y",answr_str)   == 0)
      ||
      (strcmp("y",answr_str)   == 0)
   ){
      *answer = 1;
      return(0);
   }
   else if(
      (strcmp("NO",answr_str) == 0)
      ||
      (strcmp("No",answr_str) == 0)
      ||
      (strcmp("no",answr_str) == 0)
      ||
      (strcmp("N",answr_str)  == 0)
      ||
      (strcmp("n",answr_str)  == 0)
   ){
      *answer = 0;
      return(0);
   }
   *answer = -1;
   return(1);
}