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

#define BUFFER_SZ 50

//prototypes
void usage(char *);
void print_buff(char *, int);
int  setup_buff(char *, char *, int);

//prototypes for functions to handle required functionality
int  count_words(char *, int, int);
//add additional prototypes here
int  reverse_str(char*, int, int);
int  find_word(char *, char*);
int  replace_word(char*, char*, char*, int, int);
int  print_words(char*, int, int);

int setup_buff(char *buff, char *user_str, int len){
    //TODO: #4:  Implement the setup buff as per the directions
	if (len > BUFFER_SZ) return -1;

	int str_len = 0;
 	char *temp = buff;

	while (*user_str == ' ') buff++; // skip leading spaces

	while (*user_str != '\0' && str_len < len - 1) {
		if (*user_str == ' ' && (*(user_str + 1) == ' ' || *(user_str + 1) == '\0')) {
			user_str++;
			continue;
		}
			
		*temp = *user_str;
		temp++;
		user_str++;
		str_len++;
	}
	
	*temp = '\0';

	return str_len;

}

void print_buff(char *buff, int len){
    printf("Buffer:  ");
    for (int i=0; i<len; i++){
        putchar(*(buff+i));
    }
    putchar('\n');
}

void usage(char *exename){
    printf("usage: %s [-h|c|r|w|x] \"string\" [other args]\n", exename);
}

int count_words(char *buff, int len, int str_len){
    if (len < str_len || buff == NULL) return -1;

	int ctr = 0;
	bool counting = false;
    
    while (*buff != '\0') {
		if (*buff != ' ' && !counting) {
			counting = true;
			ctr++;
		} else if (*buff == ' ' && counting) counting = false;
		
		buff++;
    }
    return ctr;
}

//ADD OTHER HELPER FUNCTIONS HERE FOR OTHER REQUIRED PROGRAM OPTIONS
int reverse_str(char *buff, int buff_len, int str_len) {
	if (buff_len == 0) {
		printf("Empty String");
		return -1;
	}
	
	char *end = buff;
	
	while (*end != '\0') end++;
	end--;
	
	char *rev_str = malloc(sizeof(char) * (str_len + 1));
	if (rev_str == NULL) {
        printf("Memory allocation error\n");
        return -1;
    }
	
	char *temp = rev_str;
	while(end >= buff) {
		*temp = *end;
		temp++;
		end--;
	}

	*temp = '\0';
	
	printf("Reversed String: %s\n", rev_str);
	free(rev_str);
	return 0;

}

int find_word(char *buff, char *word) {
	const char *b = buff;
    const char *w = word;

    int index = 0; // to track the starting index of the word

    while (*b != '\0') {
        if (*b == *w) {
            const char *b_temp = b;
            const char *w_temp = w;

            while (*w_temp != '\0' && *b_temp == *w_temp) {
                b_temp++;
                w_temp++;
            }

        	if (*w_temp == '\0') {
                return index;
            }
        }

        b++;
        index++;
    }

    return -1; // word not found
}

int replace_word(char *buff, char *word, char *replacement, int buff_len, int str_len) {
	if (str_len > buff_len || str_len == 0) return -2; // invalid input
	
	int index = find_word(buff, word);
	if (index == -1) return -1; // word doesn't exist in string
	
	int word_len = 0;
    while (word[word_len] != '\0') word_len++;

    int replacement_len = 0;
    while (replacement[replacement_len] != '\0') replacement_len++;

    if (str_len - word_len + replacement_len > buff_len) return -3; // replcaement exceeds buffer size

	if (replacement_len != word_len) {
        char *end = buff + str_len;
        char *src = buff + index + word_len;
        char *dest = buff + index + replacement_len;

        if (replacement_len > word_len) {
            while (end >= src) {
                *(dest--) = *(end--);
            }
        } else {
            while (*src != '\0') {
                *(dest++) = *(src++);
            }
            *dest = '\0';
        }
    }

    char *replace_ptr = buff + index;
    const char *replacement_ptr = replacement;

    while (*replacement_ptr != '\0') *replace_ptr++ = *replacement_ptr++;

	printf("Modified String: %s\n", buff);
	return 0;

}

int print_words(char *buff, int buff_len, int str_len) {
	if (str_len > buff_len) return -1;
	if (str_len == 0) return 0;

	int word_count = 0; // to start
	int char_ctr = 0; // length of the current word
	bool at_start = true; // if we are currently counting words

	printf("Word Print\n----------\n");
	
	while (*buff != '\0') {
		if (*buff != ' ' && at_start) {
			word_count++;
			at_start = false; // we are processing a new word
			printf("%d. ", word_count);
		}

		if (*buff == ' ' && !at_start) { // we hit the end of the word
			printf(" (%d)\n", char_ctr); // length of the current word
			char_ctr = 0; //  start count for the next word
			at_start = true; // we are starting a new word
		} else { // we are just encountering a normal character
			printf("%c", *buff); // current character
			char_ctr++; // add to length of current string
		}
		buff++;
	}
	if (!at_start) printf(" (%d)\n", char_ctr); // this is for the last word
	return word_count; // hold total number of words
}


int main(int argc, char *argv[]){

    char *buff;             //placehoder for the internal buffer
    char *input_string;     //holds the string provided by the user on cmd line
    char opt;               //used to capture user option from cmd line
    int  rc;                //used for return codes
    int  user_str_len;      //length of user supplied string

    //TODO:  #1. WHY IS THIS SAFE, aka what if argv[1] does not exist?
    /* 
    Since argv[0] is the name of the executable, argv[1] is the first parameter used as input. 
    If argv[1] doesn't exist, then the program could run into problems. The code below prevents those.
    */
    if ((argc < 2) || (*argv[1] != '-')){
        usage(argv[0]);
        exit(1);
    }

    opt = (char)*(argv[1]+1);   //get the option flag

    //handle the help flag and then exit normally
    if (opt == 'h'){
        usage(argv[0]);
        exit(0);
    }

    //WE NOW WILL HANDLE THE REQUIRED OPERATIONS

    //TODO:  #2 Document the purpose of the if statement below
    /*
	If the number of arguments is less than 3 (including the executable's name), 
	print the usage of the program (the possible flags and input), then exit.
    */
    if (argc < 3){
        usage(argv[0]);
        exit(1);
    }

    input_string = argv[2]; //capture the user input string
	
	char *choice;
	char *replacement;
	if (opt == 'x') {
		if (argc == 5) {
			choice = argv[3];
			replacement = argv[4];
		} else {
			printf("Error: invalid number of arguments");
			exit(2);
		}
	}

    //TODO:  #3 Allocate space for the buffer using malloc and
    //          handle error if malloc fails by exiting with a 
    //          return code of 99
	buff = malloc(sizeof(char) * BUFFER_SZ);
	if (buff == NULL) {
		printf("Error setting up buffer");
		exit(99);
	}

    user_str_len = setup_buff(buff, input_string, BUFFER_SZ);     //see todos
    if (user_str_len < 0){
        printf("Error setting up buffer, error = %d", user_str_len);
        exit(2);
    }

    switch (opt){
        case 'c':
            rc = count_words(buff, BUFFER_SZ, user_str_len);  //you need to implement
            if (rc < 0){
                printf("Error counting words, rc = %d", rc);
                exit(2);
            }
            printf("Word Count: %d\n", rc);
            break;

        //TODO:  #5 Implement the other cases for 'r' and 'w' by extending
        //       the case statement options
        case 'r':
			rc = reverse_str(buff, BUFFER_SZ, user_str_len);
        	if (rc < 0) {
        		printf("Error reversing words, rc = %d", rc);
        		exit(2);
			}
        	break;
        case 'w':
        	rc = print_words(buff, BUFFER_SZ, user_str_len);
        	if (rc < 0) {
                printf("Error printing words, rc = %d", rc);
                exit(2);
            }
        	break;
        case 'x':
        	rc = replace_word(buff, choice, replacement, BUFFER_SZ, user_str_len);
        	if (rc < 0) {
                printf("Error replacing words, rc = %d", rc);
                exit(2);
            }
            break;
        default:
            usage(argv[0]);
            exit(1);
    }

    //TODO:  #6 Dont forget to free your buffer before exiting
    print_buff(buff,BUFFER_SZ);
    free(buff);
    exit(0);
}

//TODO:  #7  Notice all of the helper functions provided in the 
//          starter take both the buffer as well as the length.  Why
//          do you think providing both the pointer and the length
//          is a good practice, after all we know from main() that 
//          the buff variable will have exactly 50 bytes?
//  
//          In a case where the global BUFFER_SZ variable is changed, 
//          you don't want the behavior of each of the functions to
//          to be affacted negatively. Providing both the pointer to
//          the buffer and the length is good so that the length is
//          never in question for any individual function.