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

#define BUFSIZE (1024*8)
static void to_unix(char* buf);
static void unix_to_dos(char* buf2, const char* buf);

int usage()
{
    fprintf(stderr, "usage: line_endings unix|dos FILES\n"
            "\n"
            "Convert FILES to either unix or dos line endings.\n");
    return 1;
}

typedef struct Node {
    struct Node *next;
    char buf[BUFSIZE*2+3];
} Node;

int
main(int argc, char** argv)
{
    enum { UNIX, DOS } ending;
    int i;

    if (argc < 2) {
        return usage();
    }

    if (0 == strcmp("unix", argv[1])) {
        ending = UNIX;
    }
    else if (0 == strcmp("dos", argv[1])) {
        ending = DOS;
    }
    else {
        return usage();
    }

    for (i=2; i<argc; i++) {
        int fd;
        int len;

        // force implied
        chmod(argv[i], S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);

        fd = open(argv[i], O_RDWR);
        if (fd < 0) {
            fprintf(stderr, "unable to open file for read/write: %s\n", argv[i]);
            return 1;
        }

        len = lseek(fd, 0, SEEK_END);
        lseek(fd, 0, SEEK_SET);

        if (len > 0) {
            Node* root = malloc(sizeof(Node));
            Node* node = root;
            node->buf[0] = 0;

            while (len > 0) {
                node->next = malloc(sizeof(Node));
                node = node->next;
                node->next = NULL;

                char buf[BUFSIZE+2];
                ssize_t amt;
                ssize_t amt2 = len < BUFSIZE ? len : BUFSIZE;
                amt = read(fd, buf, amt2);
                if (amt != amt2) {
                    fprintf(stderr, "unable to read file: %s\n", argv[i]);
                    return 1;
                }
                buf[amt2] = '\0';
                to_unix(buf);
                if (ending == UNIX) {
                    strcpy(node->buf, buf);
                } else {
                    char buf2[(BUFSIZE*2)+3];
                    unix_to_dos(buf2, buf);
                    strcpy(node->buf, buf2);
                }
                len -= amt2;
            }

            (void)ftruncate(fd, 0);
            lseek(fd, 0, SEEK_SET);
            while (root) {
                ssize_t amt2 = strlen(root->buf);
                if (amt2 > 0) {
                    ssize_t amt = write(fd, root->buf, amt2);
                    if (amt != amt2) {
                        fprintf(stderr, "unable to write file: %s\n", argv[i]);
                        return 1;
                    }
                }
                node = root;
                root = root->next;
                free(node);
            }
        }
        close(fd);
    }
    return 0;
}

void
to_unix(char* buf)
{
    char* p = buf;
    char* q = buf;
    while (*p) {
        if (p[0] == '\r' && p[1] == '\n') {
            // dos
            *q = '\n';
            p += 2;
            q += 1;
        }
        else if (p[0] == '\r') {
            // old mac
            *q = '\n';
            p += 1;
            q += 1;
        }
        else {
            *q = *p;
            p += 1;
            q += 1;
        }
    }
    *q = '\0';
}

void
unix_to_dos(char* buf2, const char* buf)
{
    const char* p = buf;
    char* q = buf2;
    while (*p) {
        if (*p == '\n') {
            q[0] = '\r';
            q[1] = '\n';
            q += 2;
            p += 1;
        } else {
            *q = *p;
            p += 1;
            q += 1;
        }
    }
    *q = '\0';
}

