#include "files.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>


static bool create_path(char* pn) {
    char* p = pn;
    while (*p == '/') ++p;
    while ((p = strchr(p, '/')) != NULL) {
        *p = '\0';
        if ((mkdir(pn, 0700) == -1) && (errno != EEXIST)) {
            log_errno(error, "mkdir(%s)", pn);
            *p = '/';
            return false;
        }
        *p = '/';
        ++p;
    }
    return true;
}


int file_open(const char* fn, char mode, bool nonblock) {
    if (unlikely(!fn)) return -1;
    const int flags = nonblock? O_NONBLOCK: 0;
    int fd;
    if (mode == 'w') {
        fd = open(fn, O_WRONLY|O_CREAT|O_EXCL|flags, 0600);
        if (fd == -1) {
            if (errno == ENOENT) {
                if (create_path(strdupa(fn))) {
                    fd = open(fn, O_WRONLY|O_CREAT|O_EXCL|flags, 0600);
                    if (fd == -1) {
                        log_errno(error, "open(%s,%c)", fn, mode);
                    }
                }
            } else if (errno == EEXIST) {
                if (unlink(fn) != -1) {
                    fd = open(fn, O_WRONLY|O_CREAT|O_EXCL|flags, 0600);
                    if (fd == -1) {
                        log_errno(error, "open(%s,%c)", fn, mode);
                    }
                } else {
                    log_errno(error, "unlink(%s)", fn);
                }
            } else {
                log_errno(error, "open(%s,%c)", fn, mode);
            }
        }
    } else {
        assert(mode == 'r');
        fd = open(fn, O_RDONLY|flags);
        if (fd == -1) {
            log_errno(error, "open(%s,%c)", fn, mode);
        }
    }
    log(io, "open(%s,%c,%d): %d", fn, mode, nonblock, fd);
    return fd;
}


off_t file_size(int fd) {
    static struct stat ss;
    if (fstat(fd, &ss) == -1) {
        log_errno(error, "fstat");
        return 0;
    }
    return ss.st_size;
}


bool set_blocking(int fd, bool block) {
    int flags = fcntl(fd, F_GETFL, 0);
    unless (flags != -1) {
        log_errno(error, "fcntl(%d, F_GETFL)", fd);
        return false;
    }
    flags = fcntl(fd, F_SETFL, block? (flags&(~O_NONBLOCK)): (flags|O_NONBLOCK));
    unless (flags != -1) {
        log_errno(error, "fcntl(%d, F_SETFL, |O_NONBLOCK)", fd);
        return false;
    }
    return true;
}


bool has_data(int fd, msec_t tout) {
    struct timeval tv = {
        (time_t)tout/1000,
        ((time_t)tout%1000)*1000
    };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    return (select(fd+1, &fds, NULL, NULL, &tv) != 0);
}


bool readline_r(char* buf, size_t len, char sep, readline_handler handler, intptr_t handler_ctx) {
    // find nl's
    char* p = buf;
    size_t l = len;
    char* nl;
    while (l) {
        if ((nl = strnchr(p, sep, l)) != NULL) {
            *nl = '\0';
            if (nl != p) { // skip empty lines
                log(io, "readline buf: '%s', %d", p, (int)(nl-p));
                unless (handler(p, nl-p, handler_ctx)) {
                    log(notice, "readline buf parsing failed");
                    return false;
                }
            }
            l -= (nl-p)+1;
            p = nl+1;
        } else {
            log(debug, "readline buf w/o ending separator");
            unless (handler(p, l, handler_ctx)) {
                log(notice, "readline buf parsing failed");
                return false;
            }
            break;
        }
    }
    return true;
}


tristate_t readline_r(int fd, char sep, readline_ctx_t* ctx, readline_handler handler, intptr_t handler_ctx) {
    static readline_ctx_t _ctx;
    _ctx.len = 0;
    char* buf = ctx? ctx->buf: _ctx.buf;
    size_t& len = ctx? ctx->len: _ctx.len;

    while (true) {
        // read and append
        ssize_t rv = read(fd, buf+len, sizeof(readline_ctx_t::buf)-len-1); // add 0
        if (is_noop_errno(rv)) {
            // wait for incoming read event
            unless (ctx) {
                log(error, "readline on non-blocking fd w/o ctx");
                return TRI_FALSE;
            } else {
                //log(io, "readline again");
            }
            return TRI_NONE;
        } else if (!rv) {
            // EOF
            log(io, "readline eof");
            if (len) {
                log(notice, "won't process last %zu bytes (not terminated?)", len);
            }
            len = 0;
            return TRI_TRUE;
        } else if (rv < 0) {
            // err
            log_errno(notice, "read");
            len = 0;
            return TRI_FALSE;
        }
        len += rv;
        buf[len] = '\0';

        // find nl's
        char* p = buf;
        size_t l = len;
        char* nl;
        while (l && ((nl = strnchr(p, sep, l)) != NULL)) {
            *nl = '\0';
            if (nl != p) { // skip empty lines
                log(io, "readline: '%s', %d", p, (int)(nl-p));
                unless (handler(p, nl-p, handler_ctx)) {
                    log(notice, "readline parsing failed");
                    len = 0;
                    return TRI_FALSE; // fatal
                }
            }
            l -= (nl-p)+1;
            p = nl+1;
        }

        if (p == buf) { // no nl found
            if (l >= sizeof(readline_ctx_t::buf)-1) {
                log(notice, "truncated line!");
                len = 0;
                return TRI_FALSE;
            } else {
                len = l;
            }
        } else {
            if (l) {
                log(debug, "moving %zu bytes", l);
                memmove(buf, p, l);
            }
            len = l;
        }
    }
}


tristate_t separator_write(int fd, const char* b1, size_t l1, const char* b2, size_t l2, const char* b3, size_t l3) {
    int ionum;
    size_t iolen;
    iovec iov[7];
    iov[0].iov_base = (void*)">";
    iov[0].iov_len  = 1;
    iov[1].iov_base = (void*)b1;
    iov[1].iov_len  = l1;
    if (b2) {
        iov[2].iov_base = (void*)"~";
        iov[2].iov_len  = 1;
        iov[3].iov_base = (void*)b2;
        iov[3].iov_len  = l2;
        if (b3) {
            iov[4].iov_base = (void*)"~";
            iov[4].iov_len  = 1;
            iov[5].iov_base = (void*)b3;
            iov[5].iov_len  = l3;
            iov[6].iov_base = (void*)"<";
            iov[6].iov_len  = 1;
            ionum = 7;
            iolen = l1 + l2 + l3 + 4;
        } else {
            iov[4].iov_base = (void*)"<";
            iov[4].iov_len  = 1;
            ionum = 5;
            iolen = l1 + l2 + 3;
        }
    } else {
        iov[2].iov_base = (void*)"<";
        iov[2].iov_len  = 1;
        ionum = 3;
        iolen = l1 + 2;
    }

    ssize_t rv = writev(fd, iov, ionum);
    if (rv == -1) {
        if (is_noop_errno(rv)) {
            log_errno(debug, "write(%d)", fd);
            return TRI_NONE;
        } else {
            log_errno(error, "write(%d)", fd);
            return TRI_FALSE;
        }
    } else if ((size_t)rv != iolen) {
        log(error, "write(%d): %zd of %zu", fd, rv, iolen);
        return TRI_FALSE;
    } else {
        log(io, "write(%d,%zu+%zu+%zu)", fd, l1, l2, l3);
        return TRI_TRUE;
    }
}


tristate_t separator_read(int fd, bool (*handler)(int, char*, size_t), msec_t tout) {
    while (true) {
        if (tout && !has_data(fd, tout)) {
            return TRI_NONE;
        }

        char b[BUF_SIZE];
        ssize_t r = read(fd, b, sizeof(b)-1);
        if (r == -1) {
            if (is_noop_errno(r)) {
                log_errno(io, "read(%d)", fd);
                return TRI_NONE;
            } else {
                log_errno(error, "read(%d)", fd);
                return TRI_FALSE;
            }
        } else if (!r) {
            log(debug, "read(%d): eof", fd);
            return TRI_FALSE; // eof
        } else {
            log(io, "read(%d): %zd", fd, r);
            b[(size_t)r] = '\0';
        }

        char* p = b;
        while (*p) {
            char* start = strchr(p, '>');
            if (!start) {
                log(error, "cannot find start '>'");
                break;
            }
            char* end = strchr(start+1, '<');
            if (!end) {
                // TODO: support some stack for state, as in readline_r()
                log(error, "cannot find end '<'");
                break;
            }
            *end = '\0';
            p = end+1;
            log(io, "calling %p(%d, %p, %zu)", handler, fd, start+1, end-start-1);
            if (!handler) {
                // discard
            } else if (!handler(fd, start+1, end-start-1)) {
                return TRI_FALSE;
            }
        }
    }

    assert(false);
    return TRI_FALSE;
}