/* A program shell  to maintain a linked list of names */


#include <stdio.h>  /* needed for standard I/O */
#include <stdlib.h> /* needed for memory allocation */
#include <string.h> /* needed for string functions: strcmp, strlen */
#include <ctype.h>  /* needed for toupper */

/* Maximum length of names */
#define strMax 20

struct node
{ char data [strMax];
  struct node * next;
};

/* function prototypes, listed in alphabetical order */

/* pre-condition:  firstPtr points to the pointer
                   designating the head of a list
  post-condition:  a designated newName is inserted into the list 
                   at a designated place
  Iterative implementation */
void addNameIteratively(struct node ** firstPtr, char * newName);

/* pre-condition:  firstPtr points to the pointer
                   designating the head of a list
  post-condition:  newName is inserted into the list,
                   after all initial nodes with strings that come
		   first in dictionary order
  Recursive implementation */
void addNameRecursively(struct node ** firstPtr, char * newName);

/* pre-condition:  firstPtr points to the pointer designating the head of a list
  post-condition:  a node with the given deletionString is deleted from the list
  Iterative implementation */
void deleteNameIteratively(struct node ** firstPtr, char * deletionString);

/* pre-condition:  firstPtr points to the pointer designating the head of a list
  post-condition:  a node with the given deletionString is deleted from the list
  Recursive implementation */
void deleteNameRecursively(struct node ** firstPtr, char * deletionString);

/* pre-condition:  first designates the first node of a list 
  post-condition:  The items on the list are printed from first to last
                   the list itself is unchanged
  note:  processing proceeds iteratively
*/
void print(struct node * first);

/* pre-condition:  first designates the first node of a list 
  post-condition:  The string coming first in dictionary order is returned
  note:  processing proceeds iteratively
*/
void findMinimum(struct node * first);

/* pre-condition:  first designates the first node of a list 
  post-condition:  Capitalize the first (n % strMax)+1 characters
                   in the data field, where n is the distance of the
                   node to the end of the list
  return:          the distance of the node to the end of the list
  note:  processing proceeds recursively
*/
int selectiveCaps(struct node * first);

int main (void) {
  /* program to coordinate the menu options and calls the requested function */

  struct node * first = NULL; /* pointer to the first list item */
  char option[strMax];        /* user response to menu selection */
  char dataString [strMax];   /* string used when inserting/deleting a string */

  printf ("Program to Maintain a List of Names\n");

  while (1) {
    /* print menu options */
    printf ("Options available\n");
    printf ("I - Insert a name into the list (iterative)\n");
    printf ("J - Insert a name into the list (recursive)\n");
    printf ("D - Delete a name from the list (iteratively)\n");
    printf ("E - Delete a name from the list (recursively)\n");
    printf ("M - Find first name on list in dictionary order (iteratively\n");
    printf ("C - Selectively capitalize characters (recursively)\n");
    printf ("P - Print the names on the list (iteratively)\n");
    printf ("Q - Quit\n");

    /* determine user selection */
    printf ("Enter desired option: ");
    scanf ("%s", option);
    //clear line after option
    while (getchar() != '\n');
    
    switch (option[0])
      { case 'I':
        case 'i':
          printf ("Enter name to be inserted: ");
          fgets(dataString, strMax, stdin);
          
          // remove newline character at end, if present
          if (dataString[strlen(dataString)-1] == '\n')
            dataString[strlen(dataString)-1] = 0;
          
          // insert node containing dataString
          addNameIteratively(&first, dataString);
          break;
        case 'J':
        case 'j': 
          printf ("Enter name to be inserted: ");
          fgets(dataString, strMax, stdin);
          
          // remove newline character at end, if present
          if (dataString[strlen(dataString)-1] == '\n')
            dataString[strlen(dataString)-1] = 0;
          
          // insert node containing dataString
          addNameRecursively(&first, dataString);
          break;
        case 'D':
        case 'd':
          printf ("Enter name to be deleted: ");
          fgets(dataString, strMax, stdin);
          
          // remove newline character at end, if present
          if (dataString[strlen(dataString)-1] == '\n')
            dataString[strlen(dataString)-1] = 0;
          
          // delete node containing dataString
          deleteNameIteratively(&first, dataString);
          break;
        case 'E':
        case 'e': 
          printf ("Enter name to be deleted: ");
          fgets(dataString, strMax, stdin);
          
          // remove newline character at end, if present
          if (dataString[strlen(dataString)-1] == '\n')
            dataString[strlen(dataString)-1] = 0;
          
          // delete node containing dataString
          deleteNameRecursively(&first, dataString);
          break;
        case 'M':
        case 'm': 
          findMinimum(first);
          break;
        case 'C':
        case 'c': 
          selectiveCaps(first);
          break;
        case 'P':
        case 'p': 
          print(first);
          break;
        case 'Q':
        case 'q':
          printf ("Program terminated\n");
          return 0;
          break;
        default: printf ("Invalid Option - Try Again!\n");
          continue;
      }
  }
}
        
/* pre-condition:  firstPtr points to the pointer
                   designating the head of a list
  post-condition:  a designated newName is inserted into the list 
                   at a designated place
  Iterative implementation */
void addNameIteratively(struct node ** firstPtr, char * newName)
{

  struct node * newNode = (struct node *)malloc(sizeof(struct node));
  struct node * listPtr;

  strncpy (newNode->data, newName, strMax);

   if (*firstPtr == NULL) {
     /* insert name's node at start of list */
     newNode->next = *firstPtr;
     *firstPtr = newNode;
   }
  
   else {
     char oldName [strMax];
     printf ("Enter old name which new name should preceed, \n");
     printf ("or enter ? if new name should be placed last\n");
     scanf ("%s", oldName);

     if (strcmp (oldName, (*firstPtr)->data) == 0) {
       /* insert name's node at start of list */
       newNode->next = *firstPtr;
       *firstPtr = newNode;
     }
     else {
       /* insert name's node after start of the list */
       struct node * prevPtr;
       
       /* start at beginning of list */
       listPtr = (*firstPtr)->next;  /* the current node to search */
       prevPtr = *firstPtr;          /* the node behind listPtr */
       
       while (listPtr && (strcmp (oldName, listPtr->data) != 0)) {
         prevPtr = listPtr;
         listPtr = prevPtr->next;
       }

       newNode->next = prevPtr->next;
       prevPtr->next = newNode;
     }
   }
   printf ("%s inserted into the list\n\n", newNode->data);
}

/* pre-condition:  firstPtr points to the pointer
                   designating the head of a list
  post-condition:  newName is inserted into the list,
                   after all initial nodes with strings that come
		   first in dictionary order
  Recursive implementation */
void addNameRecursively(struct node ** firstPtr, char * newName)
{
  if (((*firstPtr) == NULL) 
      || (strcmp (newName, (*firstPtr)->data)) < 0) 
    {
      /* base case for recursion */
      /* insert new node at the front of the list */
      /* create new node */
      struct node * newNode = malloc (sizeof (struct node));
      
      /* insert name into data field */
      strncpy (newNode->data, newName, strMax);

      /* node after this one will be the former head of the list */
      newNode->next = *firstPtr;

      /* newNode becomes the new first node on the list */
      *firstPtr = newNode;

      /* report success */
      printf ("node added for string %s\n", (*firstPtr)->data);
    }
  else
    {
      /* recursive case */
      /* insert new node later in the list */
      addNameRecursively (&((*firstPtr)->next), newName);
    }
}

/* pre-condition:  firstPtr points to the pointer designating the head of a list
  post-condition:  a node with the given deletionString is deleted from the list
  Iterative implementation */
void deleteNameIteratively(struct node ** firstPtr, char * deletionString)
{
  struct node * listPtr;

  if ((*firstPtr) == NULL)
    {
      printf ("List is empty - no deletions are possible\n");
    } 
  else
    {
    if (strcmp (deletionString, (*firstPtr)->data) == 0) {
      /* remove first item on list */
      listPtr = *firstPtr;
      *firstPtr = (*firstPtr)->next;
      free(listPtr);
      printf ("%s removed as first item on list\n\n", deletionString);
    }
    else {
      /* item to remove is not at beginning of list */
      /* start at beginning of list */
      listPtr = (*firstPtr)->next;        /* the current node to search */
      struct node * prevPtr = *firstPtr;  /* the node behind listPtr */

      while (listPtr && (strcmp (deletionString, listPtr->data) != 0)) {
        prevPtr = listPtr;
        listPtr = prevPtr->next;
      }
             
      if (listPtr) {
        /* remove item from list */
        prevPtr->next = listPtr->next;
        free (listPtr);
        printf ("%s deleted from list\n\n", deletionString);
      }
      else {
        printf ("%s not found on list\n\n", deletionString);
      }
    } /* end processing of deletionString */
  }
}

/* pre-condition:  firstPtr points to the pointer designating the head of a list
  post-condition:  a node with the given deletionString is deleted from the list
  Recursive implementation */
void deleteNameRecursively(struct node ** firstPtr, char * deletionString)
{
  /* Base case 1 for deletion:  deletion from an empty list */
  if (*firstPtr == NULL)
    {
      printf ("string %s not found on list\n", deletionString);
    }
  else if (strcmp (deletionString, (*firstPtr)->data) == 0)
    {
      /* string is in the first node, so delete at the front */
      /* remove first item on list */
      struct node * listPtr = *firstPtr;
      *firstPtr = (*firstPtr)->next;
      free(listPtr);
      printf ("%s removed from the list\n\n", deletionString);
    }
  else
    {
      /* string is not at the front of the list, so delete
         later in the list, if possible */
      deleteNameRecursively (&((*firstPtr)->next), deletionString);
    }
}

/* pre-condition:  first designates the first node of a list 
  post-condition:  The items on the list are printed from first to last
                   the list itself is unchanged
  note:  processing proceeds iteratively
*/    
void print(struct node * first)
{
  struct node * listElt = first;
  printf ("The names on the list are:\n\n");

  while (listElt) {
    printf ("%s\n", listElt->data);
    listElt = listElt->next;
  }

  printf ("\nEnd of List\n\n");
}

/* pre-condition:  first designates the first node of a list 
  post-condition:  The string coming first in dictionary order is returned
  note:  processing proceeds iteratively
*/
void findMinimum(struct node * first)
{
  if (first == NULL)
    {
      printf ("list is empty:  no string comes first in dictionary order\n");
    }
  else
    {
      struct node * listPtr = first;
      struct node * minPtr = first;
      while (listPtr != NULL)
        {
          if (strcmp (minPtr->data, listPtr->data) > 0)
            minPtr = listPtr;
          listPtr = listPtr->next;
        }
      printf ("The minimum string on the list (dictionary order) is: %s\n",
              minPtr->data);
    }
}

/* pre-condition:  first designates the first node of a list 
  post-condition:  Capitalize the first (n % strMax)+1 characters
                   in the data field, where n is the distance of the
                   node to the end of the list
  return:          the distance of the node to the end of the list
  note:  processing proceeds recursively
*/
int selectiveCaps(struct node * first)
{
  if (first == NULL)
    return 0;  // no nodes on list
  else
    {
      // compute distance to end of the list as
      //     1 + distance of next node to end
      int distanceToEnd = 1 + selectiveCaps (first->next);
      // capitalize data characters, based on distance to end of list
      for (int i = 0; (i<strMax ) && (i<((distanceToEnd % strMax))); i++)
        first->data[i] = toupper(first->data[i]);
      return distanceToEnd;
    }
}
