/******************************************************************************
 Snippet: Automatic naming policy code (v1.0).
 Author:  Richard Woolcock (aka KaVir).
 Date:    25th June 1998.
 ******************************************************************************
 This code is copyright (C) 1998 by Richard Woolcock.  It may be used and
 distributed freely, as long as you don't remove this copyright notice.
 ******************************************************************************/

/******************************************************************************
 Required libraries
 ******************************************************************************/

#include <stdio.h>
#include "name.h"

#ifdef MUD_VERSION
   #include <string.h>
#endif

/******************************************************************************
 Required literals
 ******************************************************************************/

#define MIN_NAME     3 /* Minimum length of a character name. */
#define MAX_NAME    14 /* Maximum length of a character name. */

/******************************************************************************
 Required enumerated types
 ******************************************************************************/

typedef enum                   /**************************************/
{                              /* Standard boolean type, containing  */
   FALSE,                      /* the values TRUE and FALSE.         */
   TRUE                        /**************************************/
} BOOLEAN_TYPE;

typedef enum                   /**************************************/
{                              /* Enumerated type used to determine  */
   START,                      /* what type of letter (vowel or non- */
   MIDDLE,                     /* vowel) the previous letter was     */ 
   FINISH                      /* preceded by.                       */
} POSITION_TYPE;               /**************************************/

typedef enum                   /**************************************/
{                              /* Enumerated type used to indicate   */
   VOWEL,                      /* if a letter is a vowel or a non-   */
   NORMAL,                     /* vowel.                             */
   END                         /**************************************/
} LOCATION_TYPE;

typedef enum                   /**************************************/
{                              /* Enumerated type used to indicate   */
   MODE_PREFIX,                /* the currently used mode of string  */
   MODE_INFIX,                 /* searching.                         */
   MODE_POSTFIX                /**************************************/
} MODE_TYPE;

/******************************************************************************
 Required structure types
 ******************************************************************************/

typedef struct                 /**************************************/
{                              /* Structure used to contain both an  */
   char         *letters;      /* alphabetic character, and its type */
   LOCATION_TYPE location;     /* (vowel or non-vowel).              */
} LETTER_TYPE;                 /**************************************/

typedef struct                 /**************************************/
{                              /* Structure used to contain the data */
   char *        name;         /* about required and banned letter   */
   short         syl_min;      /* combinations for different races,  */
   short         syl_max;      /* as well as min and max number of   */
   char *        required;     /* syllables, and the name itself.    */
   char *        banned;       /**************************************/
} RACE_DATA_TYPE;

/******************************************************************************
 Required macros
 ******************************************************************************/

#define LOWER(c)     ( ( ('A' <= (c)) && ((c) <= 'Z') ) ? ((c)+32) : (c) )
        /* Returns the lower case value of the letter 'c'. */

#define IS_LOWER(c)  ( ( ('a' <= (c)) && ((c) <= 'z') ) ? 1 : 0 )
        /* Returns TRUE if the character 'c' is a lower case letter. */

#define IS_UPPER(c)  ( ( ('A' <= (c)) && ((c) <= 'Z') ) ? 1 : 0 )
        /* Returns TRUE if the character 'c' is an upper case letter. */

#define IS_LETTER(c) ( IS_LOWER(c) | IS_UPPER(c) ) 
        /* Returns TRUE if the character 'c' is a letter. */

#define VALUE(c)     ( (c) <= 'Z' ? ((c) - 'A') : ((c) - 'a') )
        /* Returns the look-up table value of the letter 'c'. */

/******************************************************************************
 Required local operation prototypes
 ******************************************************************************/

static BOOLEAN_TYPE name_contains   ( char *part, char *whole );
                    /* Operation to find part strings within whole string. */

static void         check_vowel     ( char *argument );
                    /* Operation to check the validity of a vowel. */

static void         check_non_vowel ( char *argument );
                    /* Operation to check the validity of a non-vowel. */

static BOOLEAN_TYPE letter_combo    ( char a, char b );
                    /* Operation to check if two letters can be combined. */

static BOOLEAN_TYPE wstr_prefix     ( char *part, char *whole );
                    /* Operation to find string prefix (with wildcard). */

static BOOLEAN_TYPE wstr_infix      ( char *part, char *whole );
                    /* Operation to find string infix (with wildcard). */

static BOOLEAN_TYPE wstr_postfix    ( char *part, char *whole );
                    /* Operation to find string postfix (with wildcard). */

/******************************************************************************
 Required local variables
 ******************************************************************************/

static char          invalid_store [80] = {'\0'}; 
                                         /* Array to contain return message */
static int           vcount     = 0;     /* Vowel counter.                  */
static int           ncount     = 0;     /* Non-vowel counter.              */
static int           scount     = 0;     /* Syllibal counter.               */
static POSITION_TYPE prev_vowel = START; /* Position of the previous vowel. */
static char *        BANNED_LIST = "|silly dood "; /* Banned words.         */

/******************************************************************************
 Required local constants
 ******************************************************************************/

static const LETTER_TYPE letter[] =
{
   {"a", VOWEL},               /**************************************/
   {"b", NORMAL},              /* Look-up table used to determine    */
   {"c", NORMAL},              /* whether a letter is a vowel or a   */
   {"d", NORMAL},              /* non-vowel.                         */
   {"e", VOWEL},               /**************************************/
   {"f", NORMAL},
   {"g", NORMAL},
   {"h", NORMAL},
   {"i", VOWEL},
   {"j", NORMAL},
   {"k", NORMAL},
   {"l", NORMAL},
   {"m", NORMAL},
   {"n", NORMAL},
   {"o", VOWEL},
   {"p", NORMAL},
   {"q", NORMAL},
   {"r", NORMAL},
   {"s", NORMAL},
   {"t", NORMAL},
   {"u", VOWEL},
   {"v", NORMAL},
   {"w", NORMAL},
   {"x", NORMAL},              /**************************************/
   {"y", VOWEL},               /* The letter 'y' is a special case.  */
   {"z", NORMAL}               /**************************************/
};

static const RACE_DATA_TYPE RACE_TABLE[RACE_MAX] =
{
   /*************************   **************************************/
   /* Note the following:   *   * In order to qualify for that race, */
   /* > := Prefix mode.     *   * the player must have at least one  */
   /* | := Infix mode.      *   * letter-combination, and have none  */
   /* < := Postfix mode.    *   * of the banned letter-combinations. */
   /*************************   **************************************/
   { /* RACE_HUMAN */
      "human",                    /* Race name. */
      1, 3,                       /* Min/Max syllables in name. */
      ">? ",                      /* Required. */
      ">z x |ug >og "             /* Banned.   */
   },
   { /* RACE_ELF */
      "elf",                      /* Race name. */
      3, 4,                       /* Min/Max syllables in name. */
      ">s?l |l?th l?sh <l le ",   /* Required. */
      ">z x "                     /* Banned.   */
   },
   { /* RACE_DWARF */
      "Dwarf",                    /* Race name. */
      1, 2,                       /* Min/Max syllables in name. */
      ">n?r k?r t?r |ug og <rr ", /* Required. */
      "|ll x gar "                /* Banned.   */
   }
};

/******************************************************************************
 Operation for independant testing
 ******************************************************************************/

#ifndef MUD_VERSION
void main()
{
   char argument[80];
   argument[0] = '\0';

   /* Loop until the termination character '9' is entered */
   while ( *argument != '9' )
   {
      /* Not interested in return values, as printf is called. */
      (void) Name_invalid( argument );
      (void) Name_unacceptable( argument, RACE_HUMAN );
      printf( "\n\r\n\r" );
   }

   return;
}
#endif

/******************************************************************************
 Global operations
 ******************************************************************************/

/* Operation to check if a name is valid or not. */
char *Name_invalid( char *argument )
{
   /* Assign a temporary pointer */
   char *ptr = argument;

   /* Initialise the invalid_store */
   invalid_store[0] = '\0';

   /* Initialise the counter variables */
   vcount = ncount = scount = 0;

#ifndef MUD_VERSION
   /* Accept a single word name from the user */
   printf("Enter a name: ");
   scanf( "%s", argument );
   printf("\n\rYou entered %s.\n\r",argument);
#endif

   /* Loop through the string. */
   while ( *ptr )
   {
      /* Ensure that the entire string is made up of only letters. */
      if ( !IS_LETTER(*ptr) )
      {
#ifndef MUD_VERSION
         printf("You may only use letters.\n\r");
#else
         sprintf( invalid_store, "You may only use letters.\n\r");
#endif
         return ( &invalid_store[0] );
      }
      ptr++;
   }

   /* Ensure that the string is of a valid size. */
   if ( strlen( argument ) < MIN_NAME || strlen( argument ) > MAX_NAME )
   {
#ifndef MUD_VERSION
      printf( "Name must be %d-%d letters long.\n\r", MIN_NAME, MAX_NAME );
#else
      sprintf( invalid_store, "Name must be %d-%d letters long.\n\r",
         MIN_NAME, MAX_NAME );
#endif
      return ( &invalid_store[0] );
   }

   if ( letter[VALUE(*argument)].location == VOWEL )
   {
      /* The next letter is a vowel. */
      prev_vowel = FINISH;
      check_vowel( argument );
   }
   else
   {
      /* The next letter is a non-vowel. */
      prev_vowel = START;
      check_non_vowel( argument );
   }

   /* Ensure the name doesn't end half way through a syllable. */
   if ( prev_vowel == FINISH )
   {
#ifndef MUD_VERSION
      printf( "Illegal name ending.\n\r" );
#else
      sprintf( invalid_store, "Illegal name ending.\n\r" );
#endif
      return ( &invalid_store[0] );
   }

   /* Check for valid name parts. */
   if ( name_contains( BANNED_LIST, argument ) )
   {
#ifndef MUD_VERSION
      printf( "Illegal name syntax, try again.\n\r" );
#else
      sprintf( invalid_store, "Illegal name syntax, try again.\n\r" );
#endif
      return ( &invalid_store[0] );
   }

#ifndef MUD_VERSION
   printf( "Vowels: %d, Non-vowels: %d, Syllables: %d.\n\r", 
      vcount, ncount, scount );
#endif

   /* The name is not invalid */
   return ( NULL );
}

/* Operation to check if a name is valid for that race. */
char *Name_unacceptable( char *argument, RACE_TYPE race )
{
   /* Check for valid name parts. */
   if ( name_contains( BANNED_LIST, argument ) )
   {
#ifndef MUD_VERSION
      printf( "Illegal name syntax, try again.\n\r" );
#else
      sprintf( invalid_store, "Illegal name syntax, try again.\n\r" );
#endif
      return ( &invalid_store[0] );
   }

   if ( race < RACE_MAX )
   {
      /* Check for valid name parts. */
      if ( !name_contains( RACE_TABLE[race].required, argument )
         || name_contains( RACE_TABLE[race].banned, argument )
         || scount < RACE_TABLE[race].syl_min
         || scount > RACE_TABLE[race].syl_max )
      {
#ifndef MUD_VERSION
         printf( "That is not a fitting %s name.\n\r",
            RACE_TABLE[race].name );
#else
         sprintf( invalid_store, "That is not a fitting %s name.\n\r",
            RACE_TABLE[race].name );
#endif
         return ( &invalid_store[0] );
      }
   }

   /* The name is not unacceptable */
   return ( NULL );
}

/******************************************************************************
 Local operations
 ******************************************************************************/

/* Operation returns TRUE if any 'parts' are within 'whole'. */
static BOOLEAN_TYPE name_contains( char *part, char *whole )
{
   char      buf [80];
   int       count = 0;
   MODE_TYPE mode  = MODE_PREFIX;

   do /* Loop through the 'part' string. */
   {
      /* Check the current 'part' character. */
      switch ( LOWER(*part) )
      {
         case '>': /* Prefix character. */
            mode = MODE_PREFIX;
            count = 0;
            break;
         case '|': /* Infix character. */
            mode = MODE_INFIX;
            count = 0;
            break;
         case '<': /* Postfix character. */
            mode = MODE_POSTFIX;
            count = 0;
            break;
         case 'a': /* Alphabetic characters. */
         case 'b': case 'c': case 'd': case 'e': case 'f':
         case 'g': case 'h': case 'i': case 'j': case 'k':
         case 'l': case 'm': case 'n': case 'o': case 'p':
         case 'q': case 'r': case 's': case 't': case 'u':
         case 'v': case 'w': case 'x': case 'y': case 'z':
         case '?': /* Wildcard character. */
            buf[count] = *part;
            count++;
            break;
         default:  /* Other characters. */
            buf[count] = '\0';
            switch ( mode )
            {
               case MODE_PREFIX:
                  /* Check for 'part' at the front of 'whole'. */
                  if ( wstr_prefix( buf, whole ) )
                     return ( TRUE );
                  break;
               case MODE_INFIX:
                  /* Check for 'part' anywhere within 'whole'. */
                  if ( wstr_infix( buf, whole ) )
                     return ( TRUE );
                  break;
               case MODE_POSTFIX:
                  /* Check for 'part' at the end of 'whole'. */
                  if ( wstr_postfix( buf, whole ) )
                     return ( TRUE );
                  break;
            }
            count = 0;
            break;
      }
   }
   while ( *++part );

   return ( FALSE );
}

/* Checks if the letter is a valid vowel. */
static void check_vowel( char *argument )
{
   /* Check that the end of the string has not been reached. */
   if ( *argument == '\0' )
   {
      return;
   }

   /* Increment the number of vowels and syllables. */
   ++vcount;
   ++scount;

   /* Check what the previous letter was. */
   switch ( prev_vowel )
   { 
      case START:  /* The first part of a non-vowel. */
         prev_vowel = MIDDLE; /* This will be a vowel. */
         break;
      case MIDDLE: /* Another vowel. */
#ifndef MUD_VERSION
         printf( "Illegal name: Misplaced letter '%c'.\n\r", *argument );
#else
         sprintf( invalid_store, "Illegal name: Misplaced letter '%c'.\n\r",
            *argument );
#endif
         return;
      case FINISH: /* The second part of a non-vowel. */
         prev_vowel = MIDDLE; /* This will be a vowel. */
         break;
   }

   /* Y isn't really a vowel, so check if this 'vowel' is the letter 'y'. */
   if ( *argument == 'y' )
   {
      /* Decrement number of vowels and increment number of non-vowels. */
      --vcount;
      ++ncount;
   }

   /* Only do this if there is another character in the string. */
   if ( *(argument+1) )
   {
      /* Check if this and next letter are compatible vowels. */
      if ( letter_combo( *argument, *(argument+1) ) &&
         letter[VALUE(*(argument+1))].location == VOWEL )
      {
         /* Increment the number of vowels, ignore this letter. */
         ++vcount;
         ++argument;
      }
   }

   /* Drop out if reached the end of the string. */
   if ( *(argument+1) == '\0' )
   {
      /* The letter 'e' on the end of a name does not make a new syllable. */
      if ( *argument == 'e' )
      {
         scount--;
      }
      return;
   }

   if ( letter[VALUE(*(argument+1))].location == VOWEL )
   {
      /* The next letter is a vowel. */
      check_vowel( argument+1 );
   }
   else
   {
      /* The next letter is a non-vowel. */
      check_non_vowel( argument+1 );
   }
   return;
}

/* Checks if the letter is a valid non-vowel. */
static void check_non_vowel( char *argument )
{
   /* Check that the end of the string has not been reached. */
   if ( *argument == '\0' )
   {
      return;
   }

   /* Increment the number of non-vowels. */
   ++ncount;

   /* Check what the previous letter was. */
   switch ( prev_vowel )
   {
      case START:  /* The first part of a non-vowel. */
         prev_vowel = FINISH; /* This will be the second part */
         break;
      case MIDDLE: /* A vowel. */
         prev_vowel = START; /* This will be the first part of a non-vowel. */
         break;
      case FINISH: /* The second part of a non-vowel. */
#ifndef MUD_VERSION
         printf( "Illegal name: Misplaced letter '%c'.\n\r", *argument );
#else
         sprintf( invalid_store, "Illegal name: Misplaced letter '%c'.\n\r",
            *argument );
#endif
         return;
   }

   /* Check for two valid non-vowels in a row */
   if ( *(argument+1) )
   {
      if ( letter_combo( *argument, *(argument+1) ) &&
         letter[VALUE(*(argument+1))].location != VOWEL )
      {
         /* Increment the number of non-vowels, ignore this letter. */
         ++ncount;
         ++argument;
      }
   }

   /* Drop out if reached the end of the string */
   if ( *(argument+1) == '\0' )
   {
      return;
   }

   if ( letter[VALUE(*(argument+1))].location == VOWEL )
   {
      /* The next letter is a vowel. */
      check_vowel( argument+1 );
   }
   else
   {
      /* The next letter is a non-vowel. */
      check_non_vowel( argument+1 );
   }
   return;
}

/* Checks the two input letters to see if they fit together */
static BOOLEAN_TYPE letter_combo ( char a, char b )
{
   BOOLEAN_TYPE valid = FALSE;

   switch ( LOWER(a) )
   {
      default: break;
      case 'a': /* Vowels use inversed method */
         switch ( LOWER(b) )
         {
            case 'a':
               break;
            default:
               valid = TRUE;
               break;
         }
         break;
      case 'b':
         switch ( LOWER(b) )
         {
            case 'l':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'c':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'k':
            case 'l':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'd':
         switch ( LOWER(b) )
         {
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'e': /* Vowels use inversed method */
         valid = TRUE;
         break;
      case 'f':
         switch ( LOWER(b) )
         {
            case 'l':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'g':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'l':
            case 'n':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'h':
         switch ( LOWER(b) )
         {
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'i': /* Vowels use inversed method */
         switch ( LOWER(b) )
         {
            case 'i':
               break;
            default:
               valid = TRUE;
               break;
         }
         break;
      case 'j':
         switch ( LOWER(b) )
         {
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'k':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'l':
            case 'n':
            case 'r':
            case 'w':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'l':
         switch ( LOWER(b) )
         {
            case 'd':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'm':
         switch ( LOWER(b) )
         {
            case 'n':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'n':
         switch ( LOWER(b) )
         {
            case 'd':
            case 'g':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'o': /* Vowels use inversed method */
         valid = TRUE;
         break;
      case 'p':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'l':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'q':
         switch ( LOWER(b) )
         {
            case 'u':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'r':
         switch ( LOWER(b) )
         {
            case 'd':
            case 'l':
            case 'k':
               /* Only valid at the end of a syllable */
               if ( prev_vowel != FINISH )
                  valid = TRUE;
               break;
            case 'h':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 's':
         switch ( LOWER(b) )
         {
            case 'c':
            case 'h':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'p':
            case 'r':
            case 't':
            case 'w':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 't':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'r':
            case 'w':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'u': /* Vowels use inversed method */
         switch ( LOWER(b) )
         {
            case 'u':
               break;
            default:
               valid = TRUE;
               break;
         }
         break;
      case 'v':
         switch ( LOWER(b) )
         {
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'w':
         switch ( LOWER(b) )
         {
            case 'h':
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'x':
         switch ( LOWER(b) )
         {
            case 't':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
      case 'y': /* Vowels use inversed method */
         switch ( LOWER(b) )
         {
            case 'y':
               break;
            default:
               valid = TRUE;
               break;
         }
         break;
      case 'z':
         switch ( LOWER(b) )
         {
            case 'r':
            case 'y':
               valid = TRUE;
               break;
            default:
               break;
         }
         break;
   }

   return ( valid );
}


/* str_prefix with '?' wildcards, non-case sensitive */
static BOOLEAN_TYPE wstr_prefix( char *part, char *whole )
{
   do /* Loop through the characters in the 'whole' and 'part' strings */
   {
      /* Check if part and whole match up, with '?' as a wildcard */
      if ( LOWER(*part) != LOWER(*whole) && *part != '?' )
      {
         return ( FALSE );
      }
   }
   while ( *++part && *++whole ); /* Until one string is empty */

   if ( *part ) /* If the 'part string' is empty, the input is bad */
   {
      return ( FALSE ); 
   }

   return ( TRUE );
}

/* str_infix with '?' wildcards, non-case sensitive */
static BOOLEAN_TYPE wstr_infix( char *part, char *whole )
{
   do /* Loop through the characters in the 'whole' string */
   {
      /* Perform a prefix check at this position in the 'whole' string */
      if ( wstr_prefix( part, whole ) )
      {
         return ( TRUE );
      }
   }
   while ( *whole++ ); /* Until the whole string runs of of characters */

   return ( FALSE );
}

/* str_postfix with '?' wildcards, non-case sensitive */
static BOOLEAN_TYPE wstr_postfix( char *part, char *whole )
{
   int whole_len = strlen( whole );
   int part_len  = strlen( part );

   /* Make sure the string isn't too short to check for */
   if ( whole_len < part_len )
   {
      return ( FALSE );
   }

   /* Position the whole pointer to check the last <part> characters */
   whole += ( whole_len - part_len );

   do /* Loop through the characters in the 'whole' and 'part' strings */
   {
      /* Check if part and whole match up, with '?' as a wildcard */
      if ( LOWER(*part) != LOWER(*whole) && *part != '?' )
      {
         return ( FALSE );
      }
   }
   while ( *++part && *++whole ); /* Until one string is empty */

   if ( *part ) /* If the 'part string' is empty, the input is bad */
   {
      return ( FALSE );
   }

   return ( TRUE );
}

