#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <wait.h>
#include <limits.h>

#define MAX_ARGS 6 // 5 arguments and a null pointer

void print_prompt(char *prompt) {
    printf("(%d) %s", getpid(), prompt);
}

void one_child() {
    print_prompt("Hello, I am a parent process\n");
    if (fork() == 0) { // Child process
        print_prompt("Hello, I am a child process\n");
        exit(0);
    } else { // Parent process
        wait(NULL);
    }
}

void add_numbers() {
    int number = 1;
    int send_to_child[2];
    int receive_from_child[2];
    pipe(send_to_child);
    pipe(receive_from_child);

    if (fork() == 0) { // Child process
        int subtotal = 0;
        while (number != 0) {
            // Read the numbers from the send_to_child pipe
            read(send_to_child[0], &number, sizeof(int));
            // Add the number to the subtotal
            subtotal += number;

            print_prompt("The subtotal is ");
            printf("%d\n", subtotal);

            // Write the sum to the receive_from_child pipe
            write(receive_from_child[1], &subtotal, sizeof(int));
        }
        exit(0);
    } else { // Parent process
        int total = 0;
        while (number != 0) {
            print_prompt("Please input a number, 0 to terminate:\n");
            // Read the number from the user, and convert it to an int
            char buffer[100];
            fgets(buffer, 100, stdin);
            number = atoi(buffer);

            // Display what was entered
            print_prompt("You entered: ");
            printf("%d\n", number);

            // Write to child process
            write(send_to_child[1], &number, sizeof(int));

            // Read the total from the child process
            read(receive_from_child[0], &total, sizeof(int));
        }
        wait(NULL);
        print_prompt("The total is ");
        printf("%d\n", total);
    }
    close(send_to_child[0]);
    close(send_to_child[1]);
    close(receive_from_child[0]);
    close(receive_from_child[1]);
}

void free_exec(char *buffer, char **subcommand) {
    free(buffer);
    for (int i = 0; i < MAX_ARGS; i++) {
        free(subcommand[i]);
    }
    free(subcommand);
}

void exec() {
    print_prompt("Please input exec subcommand:\n");

    char *buffer = calloc(100, sizeof(char)); // input buffer size 100
    char **subcommand = calloc(MAX_ARGS, sizeof(char *)); //size 6 to allow for 5 arguments and a null pointer
    for (int i = 0; i < MAX_ARGS; i++) {
        subcommand[i] = NULL;     //make sure each pointer is a null pointer
    }
    for (int i = 0; i < MAX_ARGS; i++) {
        fgets(buffer, 100, stdin);
        buffer[strcspn(buffer, "\n")] = 0; // Remove trailing newline
        if (strcmp(buffer, ";") == 0) { // end of command, do not add to subcommand
            break;
        }
        char *arg = calloc(strlen(buffer), sizeof(char) + 1); //allocate memory for argument of exact size
        strncpy(arg, buffer, strlen(buffer)); //copy buffer into arg
        subcommand[i] = arg; //add arg to subcommand
    }

    if (fork() == 0) { // Child process
        execvp(subcommand[0], subcommand);
        exit(0);
    } else { // Parent process
        wait(NULL);
        free_exec(buffer, subcommand); //needs to be after wait to not use after free
    }
}

int numPlaces(int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n < 10) return 1;
    if (n < 100) return 2;
    if (n < 1000) return 3;
    if (n < 10000) return 4;
    if (n < 100000) return 5;
    if (n < 1000000) return 6;
    if (n < 10000000) return 7;
    if (n < 100000000) return 8;
    if (n < 1000000000) return 9;
    return 10;
}

int pow2(int num) {
    int ret = 2;
    for (int i = 0; i < num - 1; i++) {
        ret *= 2;
    }
    return ret;
}

void processtree() {
    print_prompt("Please input a number:\n");
    char buffer[100];
    fgets(buffer, 100, stdin);
    int number = atoi(buffer);
    int power = pow2(number);
    int string_length = numPlaces(power);

    print_prompt("The value is 0\n");
    for (int i = 1; i < power; i++) {
        if (fork() == 0) { // Child process
            char num[string_length];
            sprintf(num, "The value is %d\n", i);
            print_prompt(num);
            exit(0);
        }
    }
    while (wait(NULL) > 0);
}

int main() {
    // Listen loop
    while (true) {
        print_prompt("Please input a shell command:\n");
        char input[100];
        fgets(input, 100, stdin); // Input for commands

        if (strcmp(input, "stop\n") == 0) { // Stop command
            break;
        }
        if (strcmp(input, "onechild\n") == 0) { // One child command
            one_child();
        }
        if (strcmp(input, "addnumbers\n") == 0) { // Add numbers command
            add_numbers();
        }
        if (strcmp(input, "exec\n") == 0) { // Exec command
            exec();
        }
        if (strcmp(input, "processtree\n") == 0) { // Process tree command
            processtree();
        }
    }
}