
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include "mecha.h"

#include "fd_reader.h"

#define READER_CHUNK_SZ 1024

struct s_FDReader
{
    char *buf;

    int allocSize;

    ssize_t startOffset;
    ssize_t endOffset;

    int fd;

    bool foundEnd;
};

FDReader *
fd_reader_new (int fd)
{
    FDReader *instance = (FDReader *) malloc (sizeof (FDReader));

    instance->buf = malloc (sizeof (char) * READER_CHUNK_SZ);
    instance->allocSize = READER_CHUNK_SZ;

    instance->startOffset = 0;
    instance->endOffset = 0;

    instance->fd = fd;

    instance->foundEnd = FALSE;

    return (instance);
}

void
fd_reader_destroy (void *ptr)
{
    FDReader *instance = (FDReader *) ptr;

    free (instance->buf);

    free (instance);
}

static int
fd_reader_populateBuffer (FDReader *instance, int requestedLen, bool guarantee)
{
    /* if we have more in our buffer than is requested, we're okay. */
    if (instance->endOffset - instance->startOffset > requestedLen)
    {
        return (0);
    }

    if (instance->foundEnd == TRUE)
    {
        return (0);
    }

    /* reset the offsets if we have an empty buffer */
    if (instance->startOffset == instance->endOffset)
    {
        instance->startOffset = 0;
        instance->endOffset = 0;
    }

    /* we don't have enough room to write what we want to onto our buffer */
    if (instance->startOffset + requestedLen > instance->allocSize)
    {
        /* 
         * if we're just too close to the end, lets move back to the beginning
         * of the buffer
         */
        if (requestedLen <= instance->allocSize)
        {
            memmove (instance->buf, instance->buf + instance->startOffset,
                    instance->endOffset - instance->startOffset);

            instance->endOffset = instance->endOffset - instance->startOffset;
            instance->startOffset = 0;
        }
        else
        {
            int newLen = sizeof (char) * (instance->startOffset + 
                    requestedLen + READER_CHUNK_SZ);

            /* we need to get more buffer space */
            instance->buf = realloc (instance->buf, newLen);

            logFmsg (CONT_LOG_DEBUG, "modcgi/fd_reader: resizing from %d -> %d", 
                    instance->allocSize, newLen);

            instance->allocSize = newLen;
        }
    }

    do
    {
        ssize_t len = read (instance->fd, instance->buf + instance->endOffset, 
                instance->allocSize - instance->endOffset);

        if (len == -1)
        {
            return (errno);
        }
        else if (len == 0)
        {
            instance->foundEnd = TRUE;

            break;
        }

        instance->endOffset += len;
    }
    while (requestedLen > instance->endOffset - instance->startOffset && guarantee == TRUE);

    return (0);
}


ssize_t
fd_reader_readLine (FDReader *instance, char *buf, int len)
{
    char *p;
    ssize_t i;
    int status;

    status = fd_reader_populateBuffer (instance, len, TRUE);

    if (status != 0)
    {
        return (-status);
    }

    p = instance->buf + instance->startOffset;

    for (i = 0; i < (len - 1) && instance->startOffset < instance->endOffset; i++)
    {
        instance->startOffset++;

        if (p[i] == '\r')
        {
            buf[i] = '\n';

            buf[i + 1] = 0;

            /* if we're using windows, which uses: \r\n */
            if (p[i + 1] == '\n')
            {
                instance->startOffset++;
            }

            /* 
             * if "i" is the offset of our last character, we have to add one
             * to get the length
             */
            return (i + 1);
        }
        else if (p[i] == '\n')
        {
            buf[i] = '\n';

            buf[i + 1] = 0;

            /* if we're using a mac, which uses: \n\r */
            if (p[i + 1] == '\r')
            {
                instance->startOffset++;
            }

            /* 
             * if "i" is the offset of our last character, we have to add one
             * to get the length
             */
            return (i + 1);
        }
        else
        {
            buf[i] = p[i];
        }
    }

    /* make sure we always end in a NULL */
    buf[i] = 0;

    return (i);
}

ssize_t
fd_reader_read (FDReader *instance, char *buf, int len)
{
    int status;

    status = fd_reader_populateBuffer (instance, len, FALSE);

    if (status != 0)
    {
        return (-status);
    }

    /* 
     * if we have less bytes to return than the buffer has room, we need to
     * only return what we have.
     *
     * this will happen if we have found the end of the file
     */
    if (len > instance->endOffset - instance->startOffset)
    {
        len = instance->endOffset - instance->startOffset;
    }

    if (len != 0)
    {
        memcpy (buf, instance->buf + instance->startOffset, len);

        instance->startOffset += len;
    }

    return (len);
}
