/****************************************************************************/
/****************************************************************************/
/*                                                                          */
/*   Nathan Balon                                                           */
/*   Christopher Burtka                                                     */
/*   Frank Judge                                                            */
/*                                                                          */
/*   Program 2                                                              */
/*   CIS 450                                                                */
/*   Winter 2004                                                            */
/*                                                                          */
/*   The objective of this program is to use thread synchronization.        */
/*   The program generates strings to be processed from three               */
/*   threads.  One thread reads strings from the keyboard.  Another,        */
/*   read strings from a file. Last, the strings are generated by           */
/*   perumations of characters.  The strings are then stored in one         */
/*   common ring buffer that is shared between all threads.  The program    */
/*   also has two seperate threads that are used for displaying the         */
/*   strings in the program.  This program will make use of pthreads        */
/*   and semaphores.  The semaphores will be used to enforce the            */
/*   synchronization of the threads.                                        */
/*                                                                          */
/*                                                                          */
/*   To compile this program:                                               */
/*   cc balon_prog2.c -o balon_prog2 -lrt - lpthread                        */
/*                                                                          */
/*   To execute the program:                                                */                  
/*   balon_prog2 [filename]                                                 */
/*                                                                          */
/*   To terminate the program type <control-D>                              */
/*   The program will then print statistics of the program, including       */
/*   the number of strings and the number of strings printed                */
/*                                                                          */
/*                                                                          */
/****************************************************************************/
/****************************************************************************/

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

#define BUFFER_SIZE 200                      // the size of the ring buffer
#define ALPHABET 26                         // number of letter in the alphabet
#define PRINT_THREADS 2                     // number of print threads
#define INPUT_SIZE 100                      // the maximuim size of a string 
#define LINE_LENGTH 110                     // the size of a line to print

/*                    Function Prototypes                     */
void *read_keyboard(void *arg);             // read strings from keyboard
void *read_file(void *arg);                 // read strings from file
void *perm_char(void *arg);                 // permutate chars for new strings 
void *print_string(void *arg);              // print stings


/*                    Global Variables                        */
int total_strings_wrote = 0;                // number of string wrote
int total_strings_read  = 0;                // number of strings read 


/*                    The ring buffer                         */
static int buffer_in  = 0;                  // position to put a word in the buffer
static int buffer_out = 0;                  // position to read a word from the buffer
static char *buffer[BUFFER_SIZE];           // buffer to hold strings


/*       The line of strings to be printed to the screen      */
static char line[LINE_LENGTH];              // line to print to the screen
static int char_count = 0;                  // number of char in the line to print 


/*        flags used to check if the thread is done           */
int read_keyboard_done = 1;                 // continue reading strings from keyboard
int read_file_done     = 1;                 // continue reading from file
int create_perm_done   = 1;                 // continue creating permutations


/*                   Semaphores                               */
sem_t full;                                 // check if the buffer is full
sem_t empty;                                // check if the buffer is empty
sem_t mutex;                                // let only one thread  access the buffer
sem_t print_mutex;                          // contorl printing


/**********************************************************************/
/*                                                                    */
/*   function read_file: is used by a thread to read strings from a   */
/*   file which are then added to the common buffer.                  */
/*                                                                    */
/**********************************************************************/

void *read_file(void *arg){
    int char_count = 0;                 // character counter 
    char ch;                            // character read
    FILE *file;                         // file to read from
    char *file_name;		        // file name to read from						
    char word[100];                     // word read from the file
    char *new_word;                     // new word to store in the buffer
    file_name = (char *) arg;           // file to open to read from

    /* open the file to read from */
    if((file = fopen(file_name, "r")) == NULL){
       fprintf(stderr, "Error: could not open %s\n", file_name);
       exit(-1);
    }
    
    /* read the characters in the file */
    while((ch = fgetc(file)) != EOF){
        /* if a digit or punctuation add the char to the word */
        if((char_count < INPUT_SIZE) && ((isalnum(ch)) || (ispunct(ch)))){
            word[char_count] = ch;             // add letter to the string
            char_count++;                      // increment location of letter
        }
        /* if the char is not part of a word */
        else{
            /* if first char after a word  perform clean up add to buffer*/
            if(char_count > 0){
                 /* add null terminator to the word */
                 word[char_count] = '\0';
                 /* add the word to the buffer */
                 sem_wait(&empty);
                 sem_wait(&mutex); 
                     /* allocate memory for the new word */
                     new_word = (char *)malloc(sizeof(word));              
                     new_word = memcpy(new_word, word, sizeof(word));
                 
                     /* add a pointer to the word to the buffer */   
                     buffer[buffer_in] = new_word; 
        
                     /* increment the buffer location for the next word */
                     buffer_in = (buffer_in +1) % BUFFER_SIZE;

                     /* increment the total number of strings read in */
                     total_strings_read++;
                 sem_post(&mutex);              
                 sem_post(&full);                   
                 char_count = 0;           // reset character counter to 0
            }   
        }
    }
    /* set reading from a file to done */
    sem_wait(&mutex);
        read_file_done = 0;
    sem_post(&mutex);
    pthread_exit(NULL);
}

/***********************************************************************/
/*                                                                     */
/*   function read_keyboard: reads strings from the keyboard and then  */
/*   adds them to the common buffer.                                   */
/*                                                                     */
/***********************************************************************/

void *read_keyboard(void *arg){
    char ch;                             // char read in from keyboard
    char word[100];                      // string read from keybord
    char *new_word;                      // string to be stored in the buffer 
    int char_count = 0;                  // number of char in the string
    int i = 0;

    /* display message to enter a string
    printf("Enter a string, press CTRL-D to terminate\n");
    printf("string enter> ");

    /* read characters in from keyboard until EOF*/
    while(((ch = getc(stdin)) != EOF)){	
        /* if end of line or new word add the string to the buffer */
        if((ch == ' ') || (ch == '\n') || (ch == '\t')){
            word[char_count] = '\0';

            /* add the item to the buffer */
            sem_wait(&empty);
            sem_wait(&mutex);
                /* allocate memory for the string read from the keyboard */
                new_word = (char *)malloc(sizeof(word));              
                new_word = memcpy(new_word, word, sizeof(word));
                /* add a pointer to the new word to the buffer */
                buffer[buffer_in] = new_word;
                /* increment the position of the next word */
                buffer_in = (buffer_in + 1) % BUFFER_SIZE;
                /* increment the total strings read */
                total_strings_read++;
            sem_post(&mutex);
            sem_post(&full);
           
            char_count = 0;               // reset the character count
            /* display prompt for next string */
            if(ch == '\n'){
                printf("string enter> ");
            }
        }
        /* continue adding characters to the string */
        else {
            word[char_count] = ch;
            char_count++;
        }
    }

    /* done reading from the keyboard */
    sem_wait(&mutex);
        read_keyboard_done = 0;
        if((read_file_done == 0) && (create_perm_done == 0)){
            sem_post(&full);
        }else{
            /* the user terminates program before completion */
            printf("\n***Program terminated early***\n");
            exit(0);
        }
    sem_post(&mutex);

    pthread_exit(NULL);
}
/***********************************************************************/
/*                                                                     */
/*   function perm_char: generate sting permuations from letter to     */
/*   be added to the common buffer.                                    */
/*                                                                     */
/***********************************************************************/

void *perm_char(void * arg){
    int i = 0;
    int j = 0;
    char perm[3];           // character permutation to be stored in buffer
    char *new_word;         // pointer to the new string

    /* array of all the letters in the alphabet */
    char letters[ALPHABET] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                              'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                              'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                              'Y', 'Z'};

    /* create the permutations and add them to the buffer */
    for(i = 0; i < ALPHABET; i++){
        perm[0] = letters[i];              // add first letter to the string
        for(j = 0; j < ALPHABET; j++){
            perm[1] = letters[j];          // add second letter to the string
            perm[2] = '\0';                // add null character to the string
          
            sem_wait(&empty);
            sem_wait(&mutex);
                /* allocate memory for the new string */
                new_word = (char *)malloc(sizeof(perm));              
                new_word = memcpy(new_word, perm, sizeof(perm));
            
                /* string to the buffer and increment total read */
                buffer[buffer_in] = new_word;
                buffer_in = (buffer_in +1) % BUFFER_SIZE;
                total_strings_read++;
            sem_post(&mutex);
            sem_post(&full);
        }
    }
    sem_wait(&mutex);
        create_perm_done = 0;
    sem_post(&mutex);
    pthread_exit(NULL);
}

/***********************************************************************/
/*                                                                     */
/*   function print_string: prints out the strings that have been      */
/*   deposited in the common buffer.                                   */             
/*                                                                     */
/***********************************************************************/

void *print_string(void *arg){ 
    int word_length = 0;                 // length of the word 
    char *word;                          // word from the buffer 
    char *space = " ";                    // space to be printed 
    int value;

    while(1){              
        sem_wait(&full);
        sem_wait(&mutex);
            /* if program is done accpeting input from the keyboard print last line */
            if((read_keyboard_done == 0) && (read_file_done == 0) &&
                     (create_perm_done == 0) &&
                     ((total_strings_wrote - total_strings_read) == 0)){
                 sem_wait(&print_mutex);
                     if(line[0] != '\0'){
                         printf("\n%s\n\n",line);
                         line[0] = '\0';
                     }
                 sem_post(&print_mutex);           
      
                 sem_post(&mutex);   // release the mutex
                 sem_post(&full);    // post so the other thread can exit 
                 pthread_exit(NULL);
            }
            /* get a word from the buffer */
            word = buffer[buffer_out];
            buffer_out = (buffer_out +1) % BUFFER_SIZE;
            word_length = strlen(word);
            total_strings_wrote++;
        sem_post(&mutex);
        sem_post(&empty);

        /* print the string */
        sem_wait(&print_mutex);
            /* if the length of the string is greater than 110 print the line */
            if((word_length + char_count + 1) > 110){
                printf("%s\n", line);

                /* initialize the next line */
                line[0] = '\0';
                char_count = 0;

                /* add the word that was to long to the next line */
                strcat(line, word);
                char_count = word_length;
                strncat(line, space, 1);
                char_count ++;
                free(word);
            }
            /* else add the string to the line to be printed later */
            else {
                 strcat(line, word);
                 char_count = char_count + word_length;
                 strncat(line, space, 1);
                 char_count++;
                 free(word);
            }
        sem_post(&print_mutex);
        
        sem_wait(&mutex);
        if((read_keyboard_done == 0) && (read_file_done == 0) &&
                 (create_perm_done == 0) &&
                 (total_strings_wrote - total_strings_read != 0)){
            sem_post(&full);        
        }
        sem_post(&mutex);
    }
}

/***********************************************************************/
/*                                                                     */
/*   The main function is used to create the threads in the program    */
/*   that both read and write to the buffer                            */
/*                                                                     */
/***********************************************************************/

int main(int argc, char *argv[]){
    int i = 0;              //counter

    pthread_t read_keyboard_th, read_file_th, perm_char_th;   /* reading threads */
    pthread_t print_string_th[PRINT_THREADS];                 /* printing threads */

    /*  check that the filename was supplied to read from */
    if(argc != 2){
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(-1);
    }

    /* initialize the semaphores */
    sem_init(&mutex, 0, 1);
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    sem_init(&print_mutex, 0, 1);

    /* create the thread to read strings from the file */
    if(pthread_create(&read_file_th, NULL, read_file, (void *)argv[1]) != 0){
        fprintf(stderr, "Error: could not create a thread to read file.\n");
    }
 
    /* create a thread that create letter permutations */
    if(pthread_create(&perm_char_th, NULL, perm_char, NULL) != NULL){
        fprintf(stderr, "Error: could not create the permutation thread.\n");
    }

    /* create the thread to read input from the keyboard */
    if(pthread_create(&read_keyboard_th, NULL, read_keyboard, NULL) != 0){
        fprintf(stderr, "Error: could not create a thread to read from keyboard.\n");
    }

    /* create the threads to print the strings */
    for(i = 0; i < PRINT_THREADS; i ++){
        if(pthread_create(&print_string_th[i], NULL, print_string, NULL) != 0){
            fprintf(stderr, "Error creating a theard to print a string\n");
        }
    }

    /* wait for the threads to complete before exiting the program */ 
    pthread_join(perm_char_th, NULL);
    pthread_join(read_file_th, NULL);
    pthread_join(read_keyboard_th, NULL);
    for(i = 0; i < PRINT_THREADS; i++){
        pthread_join(print_string_th[i], NULL);
    }
    
    /* print the program statistics */
    printf("Program Totals:\n");
    printf("------------------------------------------------------");
    printf("------------------------------------------------------\n");
    printf("Total strings read: %d\n", total_strings_read);
    printf("Total string wrote: %d\n", total_strings_wrote);
    exit(0);
}

