/* -*- coding: utf-8 -*-
 * ----------------------------------------------------------------------
 * Copyright © 2012-2014, RedJack, LLC.
 * All rights reserved.
 *
 * Please see the COPYING file in this distribution for license details.
 * ----------------------------------------------------------------------
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

#include "libcork/ds/stream.h"
#include "libcork/helpers/errors.h"
#include "libcork/helpers/posix.h"

#define BUFFER_SIZE  4096


/*-----------------------------------------------------------------------
 * Producers
 */

int
cork_consume_fd(struct cork_stream_consumer *consumer, int fd)
{
    char  buf[BUFFER_SIZE];
    ssize_t  bytes_read;
    bool  first = true;

    while (true) {
        while ((bytes_read = read(fd, buf, BUFFER_SIZE)) > 0) {
            rii_check(cork_stream_consumer_data
                      (consumer, buf, bytes_read, first));
            first = false;
        }

        if (bytes_read == 0) {
            return cork_stream_consumer_eof(consumer);
        } else if (errno != EINTR) {
            cork_system_error_set();
            return -1;
        }
    }
}

int
cork_consume_file(struct cork_stream_consumer *consumer, FILE *fp)
{
    char  buf[BUFFER_SIZE];
    size_t  bytes_read;
    bool  first = true;

    while (true) {
        while ((bytes_read = fread(buf, 1, BUFFER_SIZE, fp)) > 0) {
            rii_check(cork_stream_consumer_data
                      (consumer, buf, bytes_read, first));
            first = false;
        }

        if (feof(fp)) {
            return cork_stream_consumer_eof(consumer);
        } else if (errno != EINTR) {
            cork_system_error_set();
            return -1;
        }
    }
}

int
cork_consume_file_from_path(struct cork_stream_consumer *consumer,
                            const char *path, int flags)
{
    int  fd;
    rii_check_posix(fd = open(path, flags));
    ei_check(cork_consume_fd(consumer, fd));
    rii_check_posix(close(fd));
    return 0;

error:
    rii_check_posix(close(fd));
    return -1;
}


/*-----------------------------------------------------------------------
 * Consumers
 */

struct cork_file_consumer {
    struct cork_stream_consumer  parent;
    FILE  *fp;
};

static int
cork_file_consumer__data(struct cork_stream_consumer *vself,
                         const void *buf, size_t size, bool is_first)
{
    struct cork_file_consumer  *self =
        cork_container_of(vself, struct cork_file_consumer, parent);
    size_t  bytes_written = fwrite(buf, 1, size, self->fp);
    /* If there was an error writing to the file, then signal this to
     * the producer */
    if (bytes_written == size) {
        return 0;
    } else {
        cork_system_error_set();
        return -1;
    }
}

static int
cork_file_consumer__eof(struct cork_stream_consumer *vself)
{
    /* We never close the file with this version of the consumer, so there's
     * nothing special to do at end-of-stream. */
    return 0;
}

static void
cork_file_consumer__free(struct cork_stream_consumer *vself)
{
    struct cork_file_consumer  *self =
        cork_container_of(vself, struct cork_file_consumer, parent);
    cork_delete(struct cork_file_consumer, self);
}

struct cork_stream_consumer *
cork_file_consumer_new(FILE *fp)
{
    struct cork_file_consumer  *self = cork_new(struct cork_file_consumer);
    self->parent.data = cork_file_consumer__data;
    self->parent.eof = cork_file_consumer__eof;
    self->parent.free = cork_file_consumer__free;
    self->fp = fp;
    return &self->parent;
}


struct cork_fd_consumer {
    struct cork_stream_consumer  parent;
    int  fd;
};

static int
cork_fd_consumer__data(struct cork_stream_consumer *vself,
                       const void *buf, size_t size, bool is_first)
{
    struct cork_fd_consumer  *self =
        cork_container_of(vself, struct cork_fd_consumer, parent);
    size_t  bytes_left = size;

    while (bytes_left > 0) {
        ssize_t  rc = write(self->fd, buf, bytes_left);
        if (rc == -1 && errno != EINTR) {
            cork_system_error_set();
            return -1;
        } else {
            bytes_left -= rc;
            buf += rc;
        }
    }

    return 0;
}

static int
cork_fd_consumer__eof_close(struct cork_stream_consumer *vself)
{
    int  rc;
    struct cork_fd_consumer  *self =
        cork_container_of(vself, struct cork_fd_consumer, parent);
    rii_check_posix(rc = close(self->fd));
    return 0;
}

static void
cork_fd_consumer__free(struct cork_stream_consumer *vself)
{
    struct cork_fd_consumer  *self =
        cork_container_of(vself, struct cork_fd_consumer, parent);
    cork_delete(struct cork_fd_consumer, self);
}

struct cork_stream_consumer *
cork_fd_consumer_new(int fd)
{
    struct cork_fd_consumer  *self = cork_new(struct cork_fd_consumer);
    self->parent.data = cork_fd_consumer__data;
    /* We don't want to close fd, so we reuse file_consumer's eof method */
    self->parent.eof = cork_file_consumer__eof;
    self->parent.free = cork_fd_consumer__free;
    self->fd = fd;
    return &self->parent;
}

struct cork_stream_consumer *
cork_file_from_path_consumer_new(const char *path, int flags)
{

    int  fd;
    struct cork_fd_consumer  *self;

    rpi_check_posix(fd = open(path, flags));
    self = cork_new(struct cork_fd_consumer);
    self->parent.data = cork_fd_consumer__data;
    self->parent.eof = cork_fd_consumer__eof_close;
    self->parent.free = cork_fd_consumer__free;
    self->fd = fd;
    return &self->parent;
}
