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

#define READ_BUF_SIZE 4096
#define LINE_BUF_SIZE 4096

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 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[256];
    int n = snprintf(buf, sizeof(buf),
                     "Uso: %s <input1> [input2 ...]\n", progname);
    if (n > 0) write_all(STDERR_FILENO, buf, (size_t)n);
}

static int wait_child(pid_t pid)
{
    int status;
    while (waitpid(pid, &status, 0) < 0) {
        if (errno == EINTR) continue;
        return -1;
    }
    if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return 0;
    errno = ECHILD;
    return -1;
}

static int emit_words_from_input(const char *input_file, long *word_count)
{
    int p1[2], p2[2];
    pid_t pid_grep, pid_tr;
    char rbuf[READ_BUF_SIZE];
    char line[LINE_BUF_SIZE];
    size_t line_len = 0;
    ssize_t nread;

    if (pipe(p1) < 0) return -1;
    if (pipe(p2) < 0) {
        close(p1[0]); close(p1[1]);
        return -1;
    }

    pid_grep = fork();
    if (pid_grep < 0) return -1;

    if (pid_grep == 0) {
        dup2(p1[1], STDOUT_FILENO);
        close(p1[0]); close(p1[1]);
        close(p2[0]); close(p2[1]);
        execlp("grep", "grep", "-oE", "[[:alpha:]]+", input_file, (char *)NULL);
        _exit(127);
    }

    pid_tr = fork();
    if (pid_tr < 0) return -1;

    if (pid_tr == 0) {
        dup2(p1[0], STDIN_FILENO);
        dup2(p2[1], STDOUT_FILENO);
        close(p1[0]); close(p1[1]);
        close(p2[0]); close(p2[1]);
        execlp("tr", "tr", "[:upper:]", "[:lower:]", (char *)NULL);
        _exit(127);
    }

    close(p1[0]); close(p1[1]);
    close(p2[1]);

    while ((nread = read(p2[0], rbuf, sizeof(rbuf))) > 0) {
        ssize_t i;
        for (i = 0; i < nread; ++i) {
            char c = rbuf[i];
            if (c == '\n') {
                if (line_len > 0) {
                    line[line_len] = '\0';

                    /* una única write por tupla para no fragmentar el pipe */
                    {
                        char out[LINE_BUF_SIZE + 8];
                        int n = snprintf(out, sizeof(out), "%s 1\n", line);
                        if (n > 0) write_all(STDOUT_FILENO, out, (size_t)n);
                    }

                    (*word_count)++;
                    line_len = 0;
                }
            } else {
                if (line_len + 1 < sizeof(line)) {
                    line[line_len++] = c;
                }
            }
        }
    }

    close(p2[0]);

    if (wait_child(pid_grep) < 0) return -1;
    if (wait_child(pid_tr) < 0) return -1;

    return 0;
}

int main(int argc, char *argv[])
{
    int i;
    long total_words = 0;

    setlocale(LC_ALL, "");

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

    for (i = 1; i < argc; ++i) {
        if (emit_words_from_input(argv[i], &total_words) < 0) {
            log_errno_msg("emit_words_from_input");
            return 1;
        }
    }

    {
        char buf[256];
        int n = snprintf(buf, sizeof(buf),
                         "[WordCount:map] Processed %ld words.\n",
                         total_words);
        if (n > 0) write_all(STDERR_FILENO, buf, (size_t)n);
    }

    return (int)(total_words % 256);
}
