/*
**  Quick I/O package.
**
**  A set of routines optimized for reading through files line by line.
**  This package uses internal buffering like stdio, but is even more
**  aggressive about its buffering.  The basic read call reads a single line
**  and returns the whole line, provided that it can fit in the buffer.
*/

#include "portable/system.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "inn/libinn.h"
#include "inn/qio.h"


/*
**  Open a quick file from a descriptor.
*/
QIOSTATE *
QIOfdopen(const int fd)
{
    QIOSTATE *qp;

    qp = xmalloc(sizeof(*qp));
    qp->_fd = fd;
    qp->_length = 0;
    qp->_size = QIO_BUFFERSIZE;
    qp->_buffer = xmalloc(qp->_size);
    qp->_start = qp->_buffer;
    qp->_end = qp->_buffer;
    qp->_count = 0;
    qp->_flag = QIO_ok;

    return qp;
}


/*
**  Open a quick file from a file name.
*/
QIOSTATE *
QIOopen(const char *name)
{
    int fd;

    fd = open(name, O_RDONLY);
    if (fd < 0)
        return NULL;
    return QIOfdopen(fd);
}


/*
**  Close an open quick file.
*/
void
QIOclose(QIOSTATE *qp)
{
    close(qp->_fd);
    free(qp->_buffer);
    free(qp);
}


/*
**  Rewind a quick file.  Reads the first buffer full of data automatically,
**  anticipating the first read from the file.  Returns -1 on error, 0 on
**  success.
*/
int
QIOrewind(QIOSTATE *qp)
{
    ssize_t nread;

    if (lseek(qp->_fd, 0, SEEK_SET) < 0)
        return -1;
    nread = read(qp->_fd, qp->_buffer, qp->_size);
    if (nread < 0)
        return nread;
    qp->_count = nread;
    qp->_start = qp->_buffer;
    qp->_end = qp->_buffer + nread;
    return 0;
}


/*
**  Get the next newline-terminated line from a quick file, replacing the
**  newline with a nul.  Returns a pointer to that line on success and NULL
**  on failure or end of file, with _flag set appropriately.
*/
char *
QIOread(QIOSTATE *qp)
{
    char *p, *line;
    ssize_t nread;
    size_t nleft;

    /* Loop until we get a result or fill the buffer. */
    qp->_flag = QIO_ok;
    while (1) {
        nleft = qp->_end - qp->_start;

        /* If (unsigned) nleft = 0, the buffer currently contains no data that
           hasn't previously been returned by QIOread, so we can overwrite the
           buffer with new data.  Otherwise, first check the existing data
           to see if we have a full line. */
        if (nleft == 0) {
            qp->_start = qp->_buffer;
            qp->_end = qp->_buffer;
        } else {
            p = memchr(qp->_start, '\n', nleft);
            if (p != NULL) {
                *p = '\0';
                qp->_length = p - qp->_start;
                line = qp->_start;
                qp->_start = p + 1;
                return (qp->_flag == QIO_long) ? NULL : line;
            }

            /* Not there.  See if our buffer is full.  If so, tag as having
               seen too long of a line.  This will cause us to keep reading
               as normal until we finally see the end of a line and then
               return NULL. */
            if (nleft >= qp->_size) {
                qp->_flag = QIO_long;
                qp->_start = qp->_end;
                nleft = 0;
            }

            /* We need to read more data.  If there's read data in buffer,
               then move the unread data down to the beginning of the buffer
               first. */
            if (qp->_start > qp->_buffer) {
                if (nleft > 0)
                    memmove(qp->_buffer, qp->_start, nleft);
                qp->_start = qp->_buffer;
                qp->_end = qp->_buffer + nleft;
            }
        }

        /* Read in some more data, and then let the loop try to find the
           newline again or discover that the line is too long. */
        do {
            nread = read(qp->_fd, qp->_end, qp->_size - nleft);
        } while (nread == -1 && errno == EINTR);
        if (nread <= 0) {
            if (nread < 0)
                qp->_flag = QIO_error;
            return NULL;
        }
        qp->_count += nread;
        qp->_end += nread;
    }
}
