#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

/* Deliberate limits keep parsing storage and ownership straightforward. */
#define MAXTOK 256
#define MAXWORD 8192

enum { WORD, INPUT, OUTPUT, APPEND, PIPE, BACKGROUND };
struct token { int type; char *text; };
struct command {
    char *argv[MAXTOK + 1];
    int first, last;                 /* Token range, including redirections. */
};
static struct token tokens[MAXTOK];
static struct command commands[MAXTOK];
static volatile sig_atomic_t interrupted;

static void on_signal(int signo)
{
    if (signo == SIGINT) {
        interrupted = 1;
        (void)write(STDOUT_FILENO, "\n", 1);
    }
}

static int append(char *word, size_t *len, const char *s, size_t n)
{
    if (n >= MAXWORD - *len) {
        fprintf(stderr, "shell: word too long\n");
        return -1;
    }
    memcpy(word + *len, s, n);
    *len += n;
    word[*len] = '\0';
    return 0;
}

/* Quotes and backslashes group words. Expansion never splits words or
 * introduces operators; globbing and command substitution are not supported. */
static int tokenize(const char *s, int status, int *count)
{
    *count = 0;
    while (*s) {
        char word[MAXWORD] = "";
        size_t len = 0;
        int quote = 0, type = WORD;
        while (isspace((unsigned char)*s)) s++;
        if (!*s) break;
        if (*count == MAXTOK) {
            fprintf(stderr, "shell: too many tokens\n");
            return -1;
        }
        if (strchr("<>|&", *s)) {
            switch (*s++) {
            case '<': type = INPUT; break;
            case '>': type = OUTPUT; if (*s == '>') { type = APPEND; s++; } break;
            case '|': type = PIPE; break;
            case '&': type = BACKGROUND; break;
            }
        } else while (*s) {
            if (!quote && (isspace((unsigned char)*s) || strchr("<>|&", *s))) break;
            if (*s == quote) { quote = 0; s++; continue; }
            if (!quote && (*s == '\'' || *s == '"')) { quote = *s++; continue; }
            if (*s == '\\' && quote != '\'') {
                s++;
                if (!*s) { fprintf(stderr, "shell: trailing backslash\n"); return -1; }
                /* Inside double quotes, only escape $, quote, and backslash. */
                if (quote == '"' && !strchr("$\"\\", *s))
                    if (append(word, &len, "\\", 1) < 0) return -1;
            } else if (*s == '$' && quote != '\'') {
                const char *value = NULL;
                char name[MAXWORD], number[32];
                size_t n = 0;
                s++;
                if (*s == '?') {
                    snprintf(number, sizeof number, "%d", status);
                    value = number;
                    s++;
                } else if (isalpha((unsigned char)*s) || *s == '_') {
                    while (isalnum((unsigned char)*s) || *s == '_') {
                        if (n == sizeof name - 1) {
                            fprintf(stderr, "shell: variable name too long\n"); return -1;
                        }
                        name[n++] = *s++;
                    }
                    name[n] = '\0';
                    value = getenv(name);
                    if (!value) value = "";
                } else value = "$";
                if (append(word, &len, value, strlen(value)) < 0) return -1;
                continue;
            }
            if (append(word, &len, s++, 1) < 0) return -1;
        }
        if (quote) { fprintf(stderr, "shell: unclosed quote\n"); return -1; }
        tokens[*count].type = type;
        tokens[*count].text = type == WORD ? strdup(word) : NULL;
        if (type == WORD && !tokens[*count].text) { perror("strdup"); return -1; }
        (*count)++;
    }
    return 0;
}

static int parse(int nt, int *background)
{
    int nc = 0, argc = 0;
    *background = nt && tokens[nt - 1].type == BACKGROUND;
    if (*background) nt--;
    if (!nt) goto syntax;
    memset(commands, 0, sizeof commands);
    for (int i = 0; i < nt; i++) {
        int type = tokens[i].type;
        if (type == WORD) commands[nc].argv[argc++] = tokens[i].text;
        else if (type == INPUT || type == OUTPUT || type == APPEND) {
            if (++i == nt || tokens[i].type != WORD) goto syntax;
        } else if (type == PIPE) {
            if (!argc) goto syntax;
            commands[nc++].last = i;
            commands[nc].first = i + 1;
            argc = 0;
        } else goto syntax;
    }
    if (!argc) goto syntax;
    commands[nc++].last = nt;
    return nc;
syntax:
    fprintf(stderr, "shell: expected a command, redirection filename, or final '&'\n");
    return -1;
}

/* Apply in source order, after pipeline descriptors have been connected. */
static int redirect(const struct command *cmd)
{
    for (int i = cmd->first; i < cmd->last; i++) {
        int type = tokens[i].type, fd, target, flags;
        if (type == WORD) continue;
        target = type == INPUT ? STDIN_FILENO : STDOUT_FILENO;
        flags = type == INPUT ? O_RDONLY : O_WRONLY | O_CREAT |
                (type == APPEND ? O_APPEND : O_TRUNC);
        fd = open(tokens[++i].text, flags, 0666);
        if (fd < 0) { perror(tokens[i].text); return -1; }
        if (fd != target) {
            if (dup2(fd, target) < 0) { perror("dup2"); close(fd); return -1; }
            close(fd);
        }
    }
    return 0;
}

static int is_builtin(const char *name)
{
    return !strcmp(name, "cd") || !strcmp(name, "pwd") || !strcmp(name, "exit");
}

/* exit requests termination of this process only; pipeline/background built-ins
 * run in children, so they cannot change the parent shell's directory or life. */
static int builtin(char **argv, int status, int *quit)
{
    if (!strcmp(argv[0], "exit")) {
        char *end;
        long code = status;
        if (argv[1]) {
            errno = 0;
            code = strtol(argv[1], &end, 10);
            if (errno || !*argv[1] || *end) {
                fprintf(stderr, "exit: expected an integer\n"); return 2;
            }
            if (argv[2]) { fprintf(stderr, "exit: too many arguments\n"); return 2; }
        }
        *quit = 1;
        return (unsigned char)code;
    }
    if (!strcmp(argv[0], "cd")) {
        const char *path = argv[1] ? argv[1] : getenv("HOME");
        if (argv[1] && argv[2]) { fprintf(stderr, "cd: too many arguments\n"); return 2; }
        if (!path) { fprintf(stderr, "cd: HOME is unset\n"); return 1; }
        if (chdir(path) < 0) { perror("cd"); return 1; }
        return 0;
    }
    if (argv[1]) { fprintf(stderr, "pwd: no arguments supported\n"); return 2; }
    /* Grow only when the actual path exceeds the current buffer. */
    for (size_t size = 256; ; size *= 2) {
        char *path = malloc(size);
        int error;
        if (!path) { perror("malloc"); return 1; }
        if (getcwd(path, size)) {
            int result = puts(path) == EOF;
            free(path);
            return result;
        }
        error = errno;
        free(path);
        if (error != ERANGE) { errno = error; perror("pwd"); return 1; }
    }
}

static int execute(int nc, int background, int status, int *quit)
{
    pid_t pids[MAXTOK];
    int previous = -1, launched = 0, failed = 0, result = 1;
    if (nc == 1 && !background && is_builtin(commands[0].argv[0])) {
        int saved_in = dup(STDIN_FILENO), saved_out = dup(STDOUT_FILENO);
        if (saved_in < 0 || saved_out < 0) {
            perror("dup");
            if (saved_in >= 0) close(saved_in);
            if (saved_out >= 0) close(saved_out);
            return 1;
        }
        result = redirect(&commands[0]) < 0 ? 1 : builtin(commands[0].argv, status, quit);
        if (fflush(stdout) == EOF) { perror("stdout"); result = 1; }
        if (dup2(saved_in, STDIN_FILENO) < 0 || dup2(saved_out, STDOUT_FILENO) < 0) {
            perror("dup2"); *quit = 1; result = 1;
        }
        close(saved_in);
        close(saved_out);
        clearerr(stdout);
        return result;
    }
    fflush(NULL);
    for (int i = 0; i < nc; i++) {
        int next[2] = {-1, -1};
        pid_t pid;
        if (i + 1 < nc && pipe(next) < 0) { perror("pipe"); failed = 1; break; }
        pid = fork();
        if (pid == 0) {
            signal(SIGINT, background ? SIG_IGN : SIG_DFL);
            signal(SIGQUIT, background ? SIG_IGN : SIG_DFL);
            if (background && i == 0) {
                previous = open("/dev/null", O_RDONLY);
                if (previous < 0) { perror("/dev/null"); _exit(1); }
            }
            if ((previous >= 0 && dup2(previous, STDIN_FILENO) < 0) ||
                (next[1] >= 0 && dup2(next[1], STDOUT_FILENO) < 0)) {
                perror("dup2"); _exit(1);
            }
            if (previous >= 0) close(previous);
            if (next[0] >= 0) close(next[0]);
            if (next[1] >= 0) close(next[1]);
            if (redirect(&commands[i]) < 0) _exit(1);
            if (is_builtin(commands[i].argv[0])) {
                int child_quit = 0;
                int code = builtin(commands[i].argv, status, &child_quit);
                if (fflush(stdout) == EOF) code = 1;
                _exit(code);
            }
            execvp(commands[i].argv[0], commands[i].argv);
            int code = errno == ENOENT ? 127 : 126;
            perror(commands[i].argv[0]);
            _exit(code);
        }
        if (next[1] >= 0) close(next[1]);
        if (pid < 0) {
            perror("fork");
            if (next[0] >= 0) close(next[0]);
            failed = 1;
            break;
        }
        pids[launched++] = pid;
        if (previous >= 0) close(previous);
        previous = next[0];
    }
    if (previous >= 0) close(previous);
    if (failed) for (int i = 0; i < launched; i++) kill(pids[i], SIGKILL);
    if (background && !failed) return 0;
    for (int i = 0; i < launched; i++) {
        int ws;
        pid_t waited;
        do { waited = waitpid(pids[i], &ws, 0); } while (waited < 0 && errno == EINTR);
        if (waited < 0) { perror("waitpid"); failed = 1; }
        else if (i == launched - 1)
            result = WIFEXITED(ws) ? WEXITSTATUS(ws) : 128 + WTERMSIG(ws);
    }
    return failed ? 1 : result;
}

int main(void)
{
    char *line = NULL;
    size_t capacity = 0;
    int status = 0, quit = 0, interactive = isatty(STDIN_FILENO);
    struct sigaction action = {0};
    action.sa_handler = on_signal;
    sigemptyset(&action.sa_mask);
    if (sigaction(SIGINT, &action, NULL) < 0) {
        perror("sigaction"); return 1;
    }
    signal(SIGQUIT, SIG_IGN);
    /* Unbuffered input prevents read-ahead from stealing a child's stdin. */
    setvbuf(stdin, NULL, _IONBF, 0);
    while (!quit) {
        int nt = 0, nc, background;
        /* Reap completed background children between commands. */
        while (waitpid(-1, NULL, WNOHANG) > 0) {}
        interrupted = 0;
        if (interactive) { fputs("$ ", stdout); fflush(stdout); }
        for (;;) {
            errno = 0;
            if (getline(&line, &capacity, stdin) >= 0) break;
            if (errno == EINTR) {
                clearerr(stdin);
                while (waitpid(-1, NULL, WNOHANG) > 0) {}
                if (interrupted) { status = 130; break; }
                continue;
            }
            if (ferror(stdin)) { perror("getline"); status = 1; }
            quit = 1;
            break;
        }
        if (quit || interrupted) continue;
        if (tokenize(line, status, &nt) < 0) status = 2;
        else if (nt) {
            nc = parse(nt, &background);
            status = nc < 0 ? 2 : execute(nc, background, status, &quit);
        }
        for (int i = 0; i < nt; i++) free(tokens[i].text);
    }
    free(line);
    return status;
}
