#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <glob.h>

#define MAX_INPUT_SIZE 1024
#define MAX_ARG_NUM 16
#define MAX_PATH_SIZE 256

int execute_command(char *args[]);
void handle_redirection(char *args[], int *input_fd, int *output_fd);
void handle_pipes(char *args1[], char *args2[]);
char **tokenize_input(char *input);
char **expand_wildcards(char *arg);
void execute_builtin_command(char *args[]);
int is_builtin_command(char *command);
void print_error(const char *message);

int main(int argc, char *argv[]) {
    if (argc == 1) {
        // Interactive mode
        printf("Welcome to my shell!\n");

        char input[MAX_INPUT_SIZE];
        while (1) {
            printf("mysh> ");
            fflush(stdout);

            if (fgets(input, MAX_INPUT_SIZE, stdin) == NULL) {
                break; // End of input
            }

            // Remove newline character from input
            input[strcspn(input, "\n")] = 0;

            // Tokenize input
            char **tokens = tokenize_input(input);

            // Check for exit command
            if (tokens[0] != NULL && strcmp(tokens[0], "exit") == 0) {
                printf("mysh: exiting\n");
                break;
            }

            // Execute command
            if (tokens[0] != NULL) {
                if (!is_builtin_command(tokens[0])) {
                    // If not a built-in command, expand wildcards and execute
                    char **expanded_args = expand_wildcards(tokens[0]);
                    free(tokens[0]);
                    tokens = expanded_args;
                    execute_command(tokens);
                } else {
                    // If a built-in command, execute directly
                    execute_builtin_command(tokens);
                }
            }

            // Free memory
            for (int i = 0; tokens[i] != NULL; i++) {
                free(tokens[i]);
            }
            free(tokens);
        }
    } else if (argc == 2) {
        // Batch mode
        FILE *script_file = fopen(argv[1], "r");
        if (script_file == NULL) {
            print_error("script file not found");
        }

        char input[MAX_INPUT_SIZE];
        while (fgets(input, MAX_INPUT_SIZE, script_file) != NULL) {
            // Remove newline character from input
            input[strcspn(input, "\n")] = '\0';

            // Tokenize input
            char **tokens = tokenize_input(input);

            // Check for exit command
            if (tokens[0] != NULL && strcmp(tokens[0], "exit") == 0) {
                printf("mysh: exiting\n");
                break;
            }

            // Execute command
            if (tokens[0] != NULL) {
                if (!is_builtin_command(tokens[0])) {
                    // If not a built-in command, expand wildcards and execute
                    char **expanded_args = expand_wildcards(tokens[0]);
                    free(tokens[0]);
                    tokens = expanded_args;
                    execute_command(tokens);
                } else {
                    // If a built-in command, execute directly
                    execute_builtin_command(tokens);
                }
            }

            // Free memory
            for (int i = 0; tokens[i] != NULL; i++) {
                free(tokens[i]);
            }
            free(tokens);
        }

        fclose(script_file);
    } else {
        fprintf(stderr, "Usage: %s [script-file]\n", argv[0]);
        return EXIT_FAILURE;
    }

    return 0;
}

int execute_command(char *args[]) {
    if (is_builtin_command(args[0])) {
        execute_builtin_command(args);
        return 0;  // Indicate success for built-in commands
    }

    pid_t pid;
    int status;

    pid = fork();
    if (pid == 0) {
        // Child process
        int input_fd = 0; // Default to stdin
        int output_fd = 1; // Default to stdout

        handle_redirection(args, &input_fd, &output_fd);

        if (input_fd != 0) {
            dup2(input_fd, 0);
            close(input_fd);
        }

        if (output_fd != 1) {
            dup2(output_fd, 1);
            close(output_fd);
        }

        execvp(args[0], args);

        // If execvp fails
        print_error("command not found");
    } else if (pid < 0) {
        // Forking error
        print_error("forking error");
    } else {
        // Parent process
        waitpid(pid, &status, 0);
        return WEXITSTATUS(status);
    }

    // Add a return statement here
    return -1;
    
}

void handle_redirection(char *args[], int *input_fd, int *output_fd) {
    int i = 0;
    while (args[i] != NULL) {
        if (strcmp(args[i], "<") == 0) {
            *input_fd = open(args[i + 1], O_RDONLY);
            if (*input_fd == -1) {
                print_error("input redirection error");
            }
            args[i] = NULL;
        } else if (strcmp(args[i], ">") == 0) {
            *output_fd = open(args[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0640);
            if (*output_fd == -1) {
                print_error("output redirection error");
            }
            args[i] = NULL;
        }
        i++;
    }
}

void handle_pipes(char *args1[], char *args2[]) {
    int pipe_fd[2];
    int status;

    if (pipe(pipe_fd) == -1) {
        print_error("pipe creation error");
    }

    pid_t pid1, pid2;

    pid1 = fork();
    if (pid1 == 0) {
        // Child process 1
        close(pipe_fd[0]); // Close unused read end

        // Redirect stdout to the write end of the pipe
        dup2(pipe_fd[1], 1);
        close(pipe_fd[1]);

        execvp(args1[0], args1);

        // If execvp fails
        print_error("command not found");
    } else if (pid1 < 0) {
        // Forking error
        print_error("forking error");
    }

    pid2 = fork();
    if (pid2 == 0) {
        // Child process 2
        close(pipe_fd[1]); // Close unused write end

        // Redirect stdin to the read end of the pipe
        dup2(pipe_fd[0], 0);
        close(pipe_fd[0]);

        execvp(args2[0], args2);

        // If execvp fails
        print_error("command not found");
    } else if (pid2 < 0) {
        // Forking error
        print_error("forking error");
    }

    // Close both ends of the pipe in the parent process
    close(pipe_fd[0]);
    close(pipe_fd[1]);

    // Wait for both child processes to finish
    waitpid(pid1, &status, 0);
    waitpid(pid2, &status, 0);
}

char **tokenize_input(char *input) {
    char **tokens = malloc(MAX_ARG_NUM * sizeof(char *));
    if (tokens == NULL) {
        print_error("memory allocation error");
    }

    int token_count = 0;
    char *token = strtok(input, " ");
    while (token != NULL) {
        tokens[token_count++] = strdup(token);
        if (tokens[token_count - 1] == NULL) {
            print_error("memory allocation error");
        }

        token = strtok(NULL, " ");
    }

    tokens[token_count] = NULL;
    return tokens;
}

char **expand_wildcards(char *arg) {
    glob_t glob_result;

    if (glob(arg, GLOB_NOCHECK, NULL, &glob_result) != 0) {
        print_error("wildcard expansion error");
    }

    char **expanded_args = malloc((glob_result.gl_pathc + 1) * sizeof(char *));
    if (expanded_args == NULL) {
        print_error("memory allocation error");
    }

    for (size_t i = 0; i < glob_result.gl_pathc; i++) {
        expanded_args[i] = strdup(glob_result.gl_pathv[i]);
        if (expanded_args[i] == NULL) {
            print_error("memory allocation error");
        }
    }

    expanded_args[glob_result.gl_pathc] = NULL;

    globfree(&glob_result);

    return expanded_args;
}

void execute_builtin_command(char *args[]) {
    if (strcmp(args[0], "cd") == 0) {
        if (args[1] != NULL) {
            if (chdir(args[1]) == -1) {
                print_error("cd error");
            }
        } else {
            fprintf(stderr, "cd: missing argument\n");
        }
    } else if (strcmp(args[0], "pwd") == 0) {
        char cwd[MAX_PATH_SIZE];
        if (getcwd(cwd, sizeof(cwd)) != NULL) {
            printf("%s\n", cwd);
        } else {
            print_error("pwd error");
        }
    } else if (strcmp(args[0], "which") == 0) {
        if (args[1] != NULL) {
            char *path = NULL;

            // Check if the command is a built-in command
            if (is_builtin_command(args[1])) {
                printf("%s: shell built-in command\n", args[1]);
            } else {
                // Search for the program in the specified directories
                char *directories[] = {"/usr/local/bin", "/usr/bin", "/bin"};

                for (int i = 0; i < (int)(sizeof(directories) / sizeof(directories[0])); i++) {
                    char command_path[MAX_PATH_SIZE];
                    snprintf(command_path, MAX_PATH_SIZE, "%s/%s", directories[i], args[1]);

                    if (access(command_path, X_OK) == 0) {
                        path = strdup(command_path);
                        break;
                    }
                }

                if (path != NULL) {
                    printf("%s\n", path);
                    free(path);
                } else {
                    fprintf(stderr, "%s: command not found\n", args[1]);
                }
            }
        } else {
            fprintf(stderr, "which: missing argument\n");
        }
    } else if (strcmp(args[0], "echo") == 0) {
        // Handle echo command
        for (int i = 1; args[i] != NULL; i++) {
            printf("%s", args[i]);
            if (args[i + 1] != NULL) {
                printf(" ");
            }
        }
        printf("\n");
    }
}

int is_builtin_command(char *command) {
    return (strcmp(command, "cd") == 0 || strcmp(command, "pwd") == 0 || strcmp(command, "which") == 0);
}

void print_error(const char *message) {
    fprintf(stderr, "mysh: %s\n", message);
    exit(EXIT_FAILURE);
}


the code above cannot currently handle echo commands. for example, if i run the program with a sh file that has three lines of echo commands, it will instead output three blank lines instead of three strings. other commands such as pwd, which, ls, and cd work fine so what is the issue