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

#define MAX_INPUTS 128

static void write_all(int fd, const char *buf, size_t len)
{
    while (len > 0) {
        ssize_t n = write(fd, buf, len);
        if (n < 0) {
            if (errno == EINTR) {
                continue;
            }
            _exit(1);
        }
        buf += n;
        len -= (size_t)n;
    }
}

static void write_str(int fd, const char *s)
{
    write_all(fd, s, strlen(s));
}

static void log_errno_msg(const char *prefix)
{
    char buf[512];
    int n = snprintf(buf, sizeof(buf), "%s: %s\n", prefix, strerror(errno));
    if (n > 0) {
        write_all(STDERR_FILENO, buf, (size_t)n);
    }
}

static void log_usage(const char *progname)
{
    char buf[1024];
    int n = snprintf(
        buf, sizeof(buf),
        "Uso: %s [-m] [-r nreducers] [-o output_file] <input_files...>\n",
        progname
    );
    if (n > 0) {
        write_all(STDERR_FILENO, buf, (size_t)n);
    }
}

static int ensure_dir_exists(const char *path)
{
    struct stat st;

    if (stat(path, &st) == 0) {
        if (S_ISDIR(st.st_mode)) {
            return 0;
        }
        errno = ENOTDIR;
        return -1;
    }

    if (mkdir(path, 0755) < 0) {
        return -1;
    }

    return 0;
}

static void log_created(const char *stage, const char *cmd, pid_t pid)
{
    char buf[256];
    int n = snprintf(buf, sizeof(buf),
                     "[WordCount:%s] Created %s process %d.\n",
                     stage, cmd, (int)pid);
    if (n > 0) {
        write_all(STDOUT_FILENO, buf, (size_t)n);
    }
}

static int run_process(const char *stage, const char *program, char *const argv[], int *exit_status)
{
    pid_t pid;
    int status;

    pid = fork();
    if (pid < 0) {
        log_errno_msg("fork");
        return -1;
    }

    if (pid == 0) {
        execv(program, argv);
        log_errno_msg(program);
        _exit(127);
    }

    log_created(stage, program, pid);

    while (waitpid(pid, &status, 0) < 0) {
        if (errno == EINTR) {
            continue;
        }
        log_errno_msg("waitpid");
        return -1;
    }

    if (WIFEXITED(status)) {
        *exit_status = WEXITSTATUS(status);
        return 0;
    }

    *exit_status = -1;
    return -1;
}

int main(int argc, char *argv[])
{
    int multi_map = 0;
    int n_reducers = 1;
    const char *output_file = "./Result/_reduce.txt";
    const char *tmp_dir = "./tmp";

    char *inputs[MAX_INPUTS];
    int n_inputs = 0;
    int i;

    int status_map = 0;
    int status_suffle = 0;
    int status_reduce = 0;

    char *map_argv[MAX_INPUTS + 8];
    char *suffle_argv[8];
    char *reduce_argv[8];

    char nmap_buf[32];
    char nr_buf[32];

    if (argc < 2) {
        log_usage(argv[0]);
        return 1;
    }

    for (i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "-m") == 0) {
            multi_map = 1;
        } else if (strcmp(argv[i], "-r") == 0) {
            if (i + 1 >= argc) {
                log_usage(argv[0]);
                return 1;
            }
            n_reducers = atoi(argv[++i]);
            if (n_reducers <= 0) {
                write_str(STDERR_FILENO, "Error: nreducers debe ser > 0.\n");
                return 1;
            }
        } else if (strcmp(argv[i], "-o") == 0) {
            if (i + 1 >= argc) {
                log_usage(argv[0]);
                return 1;
            }
            output_file = argv[++i];
        } else {
            if (n_inputs >= MAX_INPUTS) {
                write_str(STDERR_FILENO, "Error: demasiados ficheros de entrada.\n");
                return 1;
            }
            inputs[n_inputs++] = argv[i];
        }
    }

    if (n_inputs == 0) {
        log_usage(argv[0]);
        return 1;
    }

    if (ensure_dir_exists(tmp_dir) < 0) {
        log_errno_msg("tmp_dir");
        return 1;
    }

    if (ensure_dir_exists("./Result") < 0) {
        log_errno_msg("Result");
        return 1;
    }

    /* ---------- MAP ---------- */
    i = 0;
    map_argv[i++] = "./WordCountMap";
    map_argv[i++] = multi_map ? "1" : "0";
    map_argv[i++] = (char *)tmp_dir;

    for (int j = 0; j < n_inputs; ++j) {
        map_argv[i++] = inputs[j];
    }
    map_argv[i] = NULL;

    if (run_process("map", "./WordCountMap", map_argv, &status_map) < 0) {
        write_str(STDERR_FILENO, "[WordCount:map] Error running WordCountMap.\n");
        return 1;
    }

    {
        char buf[256];
        int n = snprintf(buf, sizeof(buf),
                         "[WordCount:map] Mapper returned %d.\n",
                         status_map);
        if (n > 0) {
            write_all(STDOUT_FILENO, buf, (size_t)n);
        }
    }

    /* ---------- SUFFLE ---------- */
    snprintf(nmap_buf, sizeof(nmap_buf), "%d", multi_map ? n_inputs : 1);
    snprintf(nr_buf, sizeof(nr_buf), "%d", n_reducers);

    suffle_argv[0] = "./WordCountSuffle";
    suffle_argv[1] = (char *)tmp_dir;
    suffle_argv[2] = nmap_buf;
    suffle_argv[3] = nr_buf;
    suffle_argv[4] = NULL;

    if (run_process("suffle", "./WordCountSuffle", suffle_argv, &status_suffle) < 0 ||
        status_suffle != 0) {
        write_str(STDERR_FILENO, "[WordCount:suffle] Error running WordCountSuffle.\n");
        return 1;
    }

    /* ---------- REDUCE ---------- */
    reduce_argv[0] = "./WordCountReduce";
    reduce_argv[1] = (char *)tmp_dir;
    reduce_argv[2] = nr_buf;
    reduce_argv[3] = (char *)output_file;
    reduce_argv[4] = NULL;

    if (run_process("reduce", "./WordCountReduce", reduce_argv, &status_reduce) < 0 ||
        status_reduce != 0) {
        write_str(STDERR_FILENO, "[WordCount:reduce] Error running WordCountReduce.\n");
        return 1;
    }

    {
        char buf[512];
        int n = snprintf(buf, sizeof(buf),
                         "[WordCount:wordcountmr] WordCountMR completed processing %d file(s).\n",
                         n_inputs);
        if (n > 0) {
            write_all(STDOUT_FILENO, buf, (size_t)n);
        }
    }

    return 0;
}
