#ifdef NEEDS_TYPES
    #include <unistd.h>
    #include <sched.h>
    #include <errno.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <limits.h>
    #include <dirent.h>
    #include <termios.h>
    #include <sys/ioctl.h>
    #include <sys/time.h>
    #include <sys/wait.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <inttypes.h>
    #include <time.h>
    #include <assert.h>

    #define FILE_HANDLE_TYPE FILE *
    char host[] = "UNIX";
#else

struct files_t {
    FILE *in;
    FILE *out;
};

struct files_t *popen2(char * program, char * args[]);
int pclose2(struct files_t *fp);

#define CLEANUP_PIPE(pipe) close((pipe)[0]); close((pipe)[1])

typedef struct files_t files_t;

struct files_chain_t {
    files_t files;
    pid_t pid;
    struct files_chain_t *next;
};
typedef struct files_chain_t files_chain_t;

static files_chain_t *files_chain;
static files_t * root_handle;

static int _do_popen2(files_chain_t *link, char * program, char * args[]) {
    int child_in[2];
    int child_out[2];
    if (0 != pipe(child_in)) {
        return -1;
    }
    if (0 != pipe(child_out)) {
        CLEANUP_PIPE(child_in);
        return -1;
    }

    pid_t cpid = link->pid = fork();
    if (0 > cpid) {
       CLEANUP_PIPE(child_in);
       CLEANUP_PIPE(child_out);
       return -1;
    }
    if (0 == cpid) {
        if (0 > dup2(child_in[0], 0) || 0 > dup2(child_out[1], 1)) {
            exit(1);
        }
        CLEANUP_PIPE(child_in);
        CLEANUP_PIPE(child_out);

        for (files_chain_t *p = files_chain; p; p = p->next) {
            int fd_in = fileno(p->files.in);
            if (fd_in != 0) {
                close(fd_in);
            }
            int fd_out = fileno(p->files.out);
            if (fd_out != 1) {
                close(fd_out);
            }
        }

        execv(program, args);
        exit(1);
    }

    close(child_in[0]);
    close(child_out[1]);
    link->files.in = fdopen(child_in[1], "w");
    link->files.out = fdopen(child_out[0], "r");
    return 0;
}

files_t *popen2(char * program, char * args[]) {
    files_chain_t *link = (files_chain_t *) malloc(sizeof (files_chain_t));
    if (NULL == link) {
        return NULL;
    }

    if (0 > _do_popen2(link, program, args)) {
        free(link);
        return NULL;
    }

    link->next = files_chain;
    files_chain = link;
    return (files_t *) link;
}

int pclose2(files_t *fp) {
    files_chain_t **p = &files_chain;
    int found = 0;
    while (*p) {
        if (*p == (files_chain_t *) fp) {
            *p = (*p)->next;
            found = 1;
            break;
        }
        p = &(*p)->next;
    }

    if (!found) {
        return -1;
    }
    if (0 > fclose(fp->in) || 0 > fclose(fp->out)) {
        free((files_chain_t *) fp);
        return -1;
    }

    int status = -1;
    pid_t wait_pid;
    do {
        wait_pid = waitpid(((files_chain_t *) fp)->pid, &status, 0);
    } while (-1 == wait_pid && EINTR == errno);

    free((files_chain_t *) fp);

    if (wait_pid == -1) {
        return -1;
    }
    return status;
}

void write_be32(uint32_t value) {
    fputc(value >> 24, root_handle->in);
    fputc(value >> 16, root_handle->in);
    fputc(value >> 8, root_handle->in);
    fputc(value, root_handle->in);
}

uint32_t read_be32() {
    uint32_t value = fgetc(root_handle->out) << 24;
    value |= fgetc(root_handle->out) << 16;
    value |= fgetc(root_handle->out) << 8;
    value |= fgetc(root_handle->out);
    return value;
}

int main(int argc, char * argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]);
        exit(1);
    }
    
    if (!(root_handle = popen2(argv[1], argv + 2))) {
        fprintf(stderr, "Failed to run %s.\n", argv[1]);
        exit(1);
    }

    while(!feof(root_handle->out)) {
        int packet_code = fgetc(root_handle->out);
        switch(packet_code) {
            case 0x01: packet_0x01(); break;
            case 0x02: packet_0x02(); break;
            case 0x03: packet_0x03(); break;
            case 0x04: packet_0x04(); break;
            case 0x05: packet_0x05(); break;
            case 0x06: packet_0x06(); break;
            case 0x07: packet_0x07(); break;
            case 0x08: packet_0x08(); break;
            case 0x09: packet_0x09(); break;
            case 0x0A: packet_0x0A(); break;
            case 0x0B: packet_0x0B(); break;
            case 0x0C: packet_0x0C(); break;
            case 0x0D: packet_0x0D(); break;
            case 0x0E: packet_0x0E(); break;
            case 0x0F: packet_0x0F(); break;
            case 0x10: packet_0x10(); break;
            case 0x11: packet_0x11(); break;
            case 0x12: packet_0x12(); break;
            case 0x13: packet_0x13(); break;
            case 0x14: packet_0x14(); break;
            case 0x15: packet_0x15(); break;
            case 0x16: packet_0x16(); break;
            case 0x17: packet_0x17(); break;
            case 0x18: packet_0x18(); break;
            case 0x19: packet_0x19(); break;
            case 0x1A: packet_0x1A(); break;
            case 0x1B: packet_0x1B(); break;
            case 0x1C: packet_0x1C(); break;
            case 0x1D: packet_0x1D(); break;
            case 0x1E: packet_0x1E(); break;
            case 0x1F: packet_0x1F(); break;
            case 0x20: packet_0x20(); break;
            case 0x21: packet_0x21(); break;
            case 0x22: packet_0x22(); break;
            case 0x23: packet_0x23(); break;
            case 0x24: packet_0x24(); break;
            case 0x25: packet_0x25(); break;
            case 0x26: packet_0x26(); break;
            case 0x27: packet_0x27(); break;
            default: fprintf(stderr, "[IOBridge] Unknown packet code: 0x%02X\n", packet_code); exit(1);
        }
    }

    pclose2(root_handle);
}

void child_send(int c) {
    fputc(c, root_handle->in);
}

int child_recv() {
    int c = fgetc(root_handle->out);
    return c == EOF ? -1 : c;
}

void child_read(char ** buffer, uint32_t * size) {
    *buffer = (char *) malloc(*size);
    if (!*buffer) {
        fprintf(stderr, "Failed to allocate %u bytes.\n", *size);
        exit(1);
    }
    *size = fread(buffer, 1, *size, root_handle->out);
}

void stdin_read(char ** buffer, uint32_t * size) {
    *buffer = (char *) malloc(*size);
    if (!*buffer) {
        fprintf(stderr, "Failed to allocate %u bytes.\n", *size);
        exit(1);
    }
    *size = fread(buffer, 1, *size, stdin);
}

void child_write(uint8_t * buffer, uint32_t size) {
    fwrite(buffer, 1, size, root_handle->in);
}

ssize_t read_line(char ** buffer, size_t * size) {
    return getline(buffer, size, root_handle->out) != -1;
}

void write_output(uint8_t * buffer, size_t size) {
    fwrite(buffer, 1, size, stdout);
}

void write_error(uint8_t * buffer, size_t size) {
    fwrite(buffer, 1, size, stderr);
}

int get_key() {
    int x = fgetc(stdin);
    if (x == EOF) return -1;
    else return x;
}

void * io_malloc(size_t size) {
    return malloc(size);
}
void io_free(void * ptr) {
    free(ptr);
}
void * io_realloc(void * ptr, size_t size) {
    return realloc(ptr, size);
}
FILE *io_open(char *f, char *m) {
    return fopen(f, m);
}
void io_close(FILE *f) {
    fclose(f);
}
void io_read_file(FILE * f, uint8_t ** buf, uint32_t * len) {
    *buf = (uint8_t *) malloc(*len);
    *len = fread(*buf, 1, *len, f);
}
void io_write_file(FILE * f, uint8_t * buf, uint32_t len) {
    fwrite(buf, 1, len, f);
}
uint32_t io_get_file_size(FILE * f) {
    long pos = ftell(f);
    fseek(f, 0, SEEK_END);
    uint32_t size = ftell(f);
    fseek(f, pos, SEEK_SET);
    return size;
}
void io_seek_file(FILE_HANDLE_TYPE f, uint32_t off, uint8_t whence) {
    switch (whence) {
        case 0: fseek(f, off, SEEK_SET); break;
        case 1: fseek(f, off, SEEK_CUR); break;
        case 2: fseek(f, off, SEEK_END); break;
    }
}
void io_rename(char * src, char * dst) {
    rename(src, dst);
}
void io_delete(char * f) {
    unlink(f);
}
void io_mkdir(char * src) {
    mkdir(src, 0777);
}
void io_rmdir(char * src) {
    rmdir(src);
}
void io_chdir(char * src) {
    chdir(src);
}
void io_getcwd(char ** buffer) {
    uint32_t size = PATH_MAX;
    *buffer = (char *) malloc(size);
    if (!getcwd(*buffer, size)) {
        free(*buffer);
        *buffer = NULL;
    }
}

unsigned long io_time() {
    return time(NULL);
}
void io_localtime(uint32_t * year, uint8_t * month, uint8_t * day, uint8_t * hour, uint8_t * minute, uint8_t * second) {
    struct tm * timeinfo;
    time_t rawtime;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    *year = timeinfo->tm_year + 1900;
    *month = timeinfo->tm_mon + 1;
    *day = timeinfo->tm_mday;
    *hour = timeinfo->tm_hour;
    *minute = timeinfo->tm_min;
    *second = timeinfo->tm_sec;
}
void io_gmtime(uint32_t * year, uint8_t * month, uint8_t * day, uint8_t * hour, uint8_t * minute, uint8_t * second) {
    struct tm * timeinfo;
    time_t rawtime;
    time(&rawtime);
    timeinfo = gmtime(&rawtime);
    *year = timeinfo->tm_year + 1900;
    *month = timeinfo->tm_mon + 1;
    *day = timeinfo->tm_mday;
    *hour = timeinfo->tm_hour;
    *minute = timeinfo->tm_min;
    *second = timeinfo->tm_sec;
}
void io_sleep(uint32_t time) {
    usleep(time * 1000);
}

void term_cooked() {
    struct termios cooked;
    tcgetattr(STDIN_FILENO, &cooked);
    cooked.c_lflag |= (ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &cooked);
}
void term_raw() {
    struct termios raw;
    tcgetattr(STDIN_FILENO, &raw);
    raw.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &raw);
}
void term_bounds(uint32_t * width, uint32_t * height) {
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    *width = w.ws_col;
    *height = w.ws_row;
}

void term_flush() { fflush(stdout); }
void io_flush(FILE_HANDLE_TYPE f) { fflush(f); }
uint8_t get_errno() { return errno; }

uint8_t file_exists(char * path) {
    struct stat st;
    return stat(path, &st) == 0;
}

void io_handle_packet_0x25() {
    uint32_t len = read_be32();
    uint8_t * data; child_read((char **) &data, &len);
    if(data[len - 1] == 0) data[len - 1] = 0x00;
    DIR * dir = opendir((char *) data);
    if(dir == NULL) {
        child_send(0x25);
        write_be32(0);
        io_free(data);
        return;
    }

    struct dirent * ent;
    vector(uint8_t) buffer = NULL;
    while((ent = readdir(dir)) != NULL) {
        uint32_t len = strlen(ent->d_name) + 1;
        vector_push_back(buffer, (len >> 8) & 0xff);
        vector_push_back(buffer, len & 0xff);
        for (int i = 0; i < len; i++) {
            vector_push_back(buffer, ent->d_name[i]);
        }
        vector_push_back(buffer, ent->d_type);
    }
    closedir(dir);

    child_send(0x25);
    write_be32(vector_size(buffer));
    child_write(buffer, vector_size(buffer));
    vector_free(data);
}

#endif