#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* The program reads up to MaxRecs from the file
 * terms.dat into the two-dimensional array terms.
 * The records occur one to a line, with a maximum
 * length of MaxLen. Each record has two fields and
 * is formatted as follows:          
 * Length:    15         50
 *          <term> <short definition>
 * Columns:  1  15  17             66
 * If a term is less than 15 characters, then the
 * remaining characters are blanks. The term's definition
 * always begins in column 17, and no term is longer than 
 * 15 characters.
 * 
 * After reading the records, the program sorts them
 * and then allows the user to search for terms. If
 * a term is found, its definition is printed; otherwise,
 * a failure message is printed.
 */                             
 
#define Infile    "terms.dat"
#define MaxRecs   (10)
#define MaxLen    (66) /* 15 for key + 1 blank + 50 for definition */
#define TermLen   (15)
#define NoFind    (-999) /* returned on search failure */
#define MoreInput (1)               

void
  swap( char terms[ ][ MaxLen + 2 ], int i, int j ),
  sort( char terms[ ][ MaxLen + 2 ], int n ),
  search( char terms[ ][ MaxLen + 2 ], int n );
int
  bin_search( char key[ ], char terms[ ][ MaxLen + 2 ], int n ),
  input( char terms[ ][ MaxLen + 2 ] );

main()
{
  char terms[ MaxRecs ][ MaxLen + 2 ]; /* +2 for newline and null */
  int n; 
  
  /* Read records from file. */
  n = input( terms );    
  /* Sort the array. */
  sort( terms, n ); 
  /* Allow user to search for terms. */
  search( terms, n );

  return EXIT_SUCCESS;  
}

/* Swap two strings in an array. */
void swap( char terms[ ][ MaxLen + 2 ], int i, int j )
{
  char temp[ MaxLen + 2 ];
  strcpy( temp, terms[ i ] );
  strcpy( terms[ i ], terms[ j ] );
  strcpy( terms[ j ], temp );
}

/* Selection sort an array of char. The algorithm can be sketched
 * as follows:
 *  1. Repeat steps 2 and 3 for i = 0,1,...,n-1.
 *  2. Select smallest among terms[ i ],terms[ i+1 ],...,terms[ n-1 ].
 *  3. Swap smallest with terms[ i ].
 */               
void sort( char terms[ ][ MaxLen + 2 ], int n )
{  
  int smallest, i, j;
  
  /* Loop n-1 times, selecting smallest each time. */
  for ( i = 0; i < n - 1; i++ ) {
    smallest = i; /* assume smallest at index i */
    /* Compare smallest against remaining strings. */
    for ( j = i + 1; j < n; j++ )
      if ( strcmp( terms[ j ], terms[ smallest ] ) < 0 )
        smallest = j;
    /* Swap smallest and term[ i ]. */
    if ( i != smallest )
      swap( terms, i, smallest );
  }
}

/* Search array of terms and definitions for a user-specified
 * term. If found, return its index in array; else return NoFind.
 * The search is binary search, which can be sketched as follows:
 *  1. Repeat steps 2 thru 4 until either key is found (success)
 *     or there is nothing left to search (failure).
 *  2. Find (approximate) midpoint in array.
 *  3. Compare midpoint term with key. If they match, return index. 
 *  4. If key is less than midpoint term, search first half of
 *     array; otherwise, search second half of array.
 */ 
int bin_search( char key[ ], 
                char terms[ ][ MaxLen + 2 ], 
                int n )
{
  int 
    first = 0,     /* first term in array */
    last = n - 1,  /* last positiion in array */
    mid,           /* approximate midpoint */
    flag;          /* result of key versus term comparison */ 
    
  while ( first <= last ) {
    mid = ( first + last ) / 2;
    flag = strncmp( key, terms[ mid ], TermLen );
    if ( 0 == flag )     /* success */
      return mid;
   else if ( flag > 0 )  /* search right half */
      first = mid + 1;
   else
      last = mid - 1;    /* search left half */
  }
  return NoFind;         /* failure */
}

/* Prompt user for term to be searched for until user tires. */                               
void search( char terms[ ][ MaxLen + 2 ], /* searched */ 
             int n )                      /* how many terms */
{
  char ans[ 10 ];         /* search again? */
  char key[ MaxLen + 2 ]; /* term searched for */
  int i, len;
 
  /* Search until user tires. */
  do {
    printf( "\nTerm? " ); scanf( "%s", key );
    /* Pad key with blanks as needed. */
    len = strlen( key );
    for ( i = len; i < TermLen; i++ )
      key[ i ] = ' ';
    key[ i ] = '\0'; /* null terminate */
    if ( ( i = bin_search( key, terms, n ) ) == NoFind ) {
      key[ len ] = '\0'; /* knock off padded blanks */
      printf( "%s not found.\n", key );
    }
    else
      printf( "%s\n", terms[ i ] ); /* term + definition */
      
    printf( "Again? (y/n) " ); scanf( "%s", ans );
  } while ( 'Y' == ans[ 0 ] || 'y' == ans[ 0 ] );
} 

/* Read records from file into two-dimensional array,
 * returning how many were read.
 */
int input( char terms[ ][ MaxLen + 2 ] )
{ 
  FILE* infile;
  int i = 0, len;
  
  /* Open input file and read records into array. */  
  if ( ( infile = fopen( Infile, "r" ) ) == NULL ) {
    fprintf( stderr, "Can't open %s so exiting.\n", Infile );
    exit( EXIT_SUCCESS );
  }                                                 

  while ( MoreInput ) {
    fgets( terms[ i ], MaxLen + 2, infile );
    if ( feof( infile ) || i + 1 >= MaxRecs )
      break;
    else {
      len = strlen( terms[ i ] );
      terms[ i ][ len - 1 ] = '\0'; /* drop newline */
      i++;
    }
  }
  fclose( infile );
  return i;  /* number of records read */
}



