#include "io.hpp"
#include <sys/stat.h>
#include <fcntl.h>
#include <zlib.h> // link with -lz


static bool file_read(int fd, char*& buf, size_t& len) {
    struct stat ss;
    if (fstat(fd, &ss) == -1) {
        LOG_ERRNO("fstat()");
        close(fd);
        return false;
    }
    len = ss.st_size;

    buf = (char*)malloc(len + 1);
    if (read(fd, buf, len) != (ssize_t)len) {
        LOG("read()");
        close(fd);
        free(buf);
        return false;
    }
    buf[len] = '\0';

    return true;
}

bool file_read(const char* fn, char*& buf, size_t& len) {
    int fd = open(fn, O_RDONLY);
    if (fd == -1) {
        LOG_ERRNO("open(%s)", fn);
        return false;
    }
    bool rv = file_read(fd, buf, len);
    close(fd);
    return rv;
}

bool file_write(const char* fn, const char* buf, size_t len) {
    int fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0664);
    if (fd == -1) {
        LOG_ERRNO("open(%s)", fn);
        return false;
    }
    errno = 0;
    if (write(fd, buf, len) != (ssize_t)len) {
        LOG_ERRNO("write(%s)", fn);
        close(fd);
        return false;
    }
    close(fd);
    return true;
}

bool docompress(const char* src, size_t srclen, char*& dst, size_t* dstlen) {
    assert(srclen);
    *dstlen = PAD4(MAX(srclen, compressBound(srclen)));
    dst = (char*)calloc(1, *dstlen);

    int rv;
    if ((rv = compress2((unsigned char*)dst, (unsigned long*)dstlen, (unsigned char*)src, srclen, Z_BEST_COMPRESSION)) != Z_OK) {
        LOG("cannot compress: %i", rv);
        free(dst);
        dst = NULL;
        *dstlen = 0;
        return false;
    }

    if (*dstlen >= srclen) {
        memset(dst, 0, *dstlen);
        memcpy(dst, src, srclen);
        *dstlen = srclen;
    }

    return true;
}

bool decompress(const char* src, size_t srclen, char*& dst, size_t dstlen) {
    assert(!dst);
    dst = (char*)calloc(1, PAD4(dstlen));

    if (dstlen < srclen) {
        return false;
    } else if (dstlen == srclen) {
        memcpy(dst, src, dstlen);
    } else {
        // https://www.zlib.net/manual.html#Utility
        int rv;
        long unsigned int l = dstlen;
        #ifndef NDEBUG
            void* bup = memcpy(malloc(srclen), src, srclen);
        #endif
        if ((rv = uncompress((unsigned char*)dst, &l, (unsigned char*)src, srclen)) != Z_OK) {
            LOG("cannot uncompress: %i", rv);
            free(dst);
            dst = NULL;
            return false;
        };
        #ifndef NDEBUG
            assert(memcmp(bup, src, srclen) == 0);
            free(bup);
        #endif
        if (l != dstlen) {
            LOG("expected %zu bytes, got %lu", dstlen, l);
            free(dst);
            dst = NULL;
            return false;
        }
    }
    return true;
}