#include <stdio.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/reboot.h>

#define FILENAME_BUFFER_SIZE 256

size_t read_command(int descriptor, char *command_buffer)
{
    ssize_t bytes_read = 0;
    size_t read_so_far = 0;

    while ((bytes_read = read(descriptor, command_buffer + read_so_far, FILENAME_BUFFER_SIZE - read_so_far - 1)) > 0)
    {
        read_so_far += bytes_read;
        if (read_so_far >= FILENAME_BUFFER_SIZE - 1)
        {
            break;
        }
    }
    command_buffer[read_so_far] = 0;
    return read_so_far;
}

enum status
{
    status_success,
    status_failure,
    status_warning,
    status_fatal
};

unsigned int make_path(char *destination, const char *directory, const char *filename, const char *extension)
{
    unsigned int i = 0;

    for (; i < FILENAME_BUFFER_SIZE; i++)
    {
        if (directory[i] == 0)
        {
            break;
        }
        destination[i] = directory[i];
    }
    for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
    {
        if (filename[j] == 0)
        {
            break;
        }
        destination[i] = filename[j];
    }
    if (extension == NULL)
    {
        goto done;
    }
    for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
    {
        if (extension[j] == 0)
        {
            break;
        }
        destination[i] = extension[j];
    }
done:
    destination[i] = 0;

    return i;
}

enum status run_test(const char *file_entry_name)
{
    printf("Running %s. ", file_entry_name);

    char filename[FILENAME_BUFFER_SIZE];
    char command_buffer[FILENAME_BUFFER_SIZE];
    char file_buffer[256];
    int pipe_ends[2];

    if (pipe(pipe_ends) == -1)
    {
        perror("pipe");
        return status_fatal;
    }
    make_path(filename, "./tests/", file_entry_name, NULL);

    int child_pid = fork();
    if (child_pid == -1)
    {
        return status_fatal;
    }
    else if (child_pid == 0)
    {
        close(STDIN_FILENO);
        close(STDERR_FILENO);
        close(pipe_ends[0]); // Close the read end.

        if (dup2(pipe_ends[1], STDOUT_FILENO) == -1)
        {
            perror("dup2");
        }
        else
        {
            execl(filename, filename);
            perror("execl");
        }
        close(STDOUT_FILENO);
        close(pipe_ends[1]);
        _exit(1);
    }
    else
    {
        close(pipe_ends[1]); // Close the write end.
        read_command(pipe_ends[0], command_buffer);
        close(pipe_ends[0]);

        int wait_status = 0;

        make_path(filename, "./expectations/", file_entry_name, ".txt");

        FILE *expectation_descriptor = fopen(filename, "r");

        if (expectation_descriptor == NULL)
        {
            return status_warning;
        }
        size_t read_from_file = fread(file_buffer, 1, sizeof(file_buffer) - 1, expectation_descriptor);
        fclose(expectation_descriptor);

        file_buffer[read_from_file] = 0;
        for (unsigned int i = 0; ; ++i)
        {
            if (command_buffer[i] == 0 && file_buffer[i] == 0)
            {
                fwrite("\n", 1, 1, stdout);
                return status_success;
            }
            else if (command_buffer[i] != file_buffer[i])
            {
                printf("Failed. Got:\n%s", command_buffer);
                return status_failure;
            }
        }
    }
}

struct summary
{
    size_t total;
    size_t failure;
    size_t success;
};

void walk()
{
    DIR *directory_stream = opendir("./tests");
    struct dirent *file_entry;

    struct summary test_summary = { .total = 0, .failure = 0, .success = 0 };

    while ((file_entry = readdir(directory_stream)) != NULL)
    {
        if (file_entry->d_name[0] == '.')
        {
            continue;
        }
        ++test_summary.total;
        switch (run_test(file_entry->d_name))
        {
            case status_failure:
                ++test_summary.failure;
                break;
            case status_success:
                ++test_summary.success;
                break;
            case status_warning:
                break;
            case status_fatal:
                goto end_walk;
        }
    }
    printf("Successful: %lu, Failed: %lu, Total: %lu.\n",
            test_summary.success, test_summary.failure, test_summary.total);
end_walk:
    closedir(directory_stream);
}

int main()
{
    int dev_console = open("/dev/console", O_WRONLY);
    if (dev_console != -1)
    {
        dup2(dev_console, STDOUT_FILENO);
        walk();
        close(dev_console);
    }
    sync();
    reboot(RB_POWER_OFF);

    return 1;
}