/*
 *  texfile.c - a text file reader
 *  Copyright (C) 2010 Martin Broadhurst
 *  www.martinbroadhurst.com
 */

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

#include "textfile.h"

#define BUFSIZE 128

MBtextfile * MBtextfile_create(FILE *fptr)
{
    MBtextfile * file = malloc(sizeof(MBtextfile));
    if (file) {
        file->fptr = fptr;
        file->inbuf = malloc(BUFSIZE);
        file->inbufsize = BUFSIZE;
        file->outbuf = NULL;
        file->outbufsize = 0;
        file->count = 0;
        file->eof = 0;
    }
    return file;
}

MBtextfile * MBtextfile_open(const char *filename)
{
    MBtextfile *file = NULL;
    FILE *fptr;
    if ((fptr = fopen(filename, "rt")) != NULL) {
        file = MBtextfile_create(fptr);
    }
    return file;
}

void MBtextfile_delete(MBtextfile *file)
{
    if (file) {
        free(file->inbuf);
        free(file->outbuf);
        free(file);
    }
}

void MBtextfile_close(MBtextfile *file)
{
    fclose(file->fptr);
    MBtextfile_delete(file);
}

static void *reallocate(void **buffer, size_t size)
{
    void *temp = realloc(*buffer, size);
    if (temp) {
        *buffer = temp;
    }
    else {
        free(*buffer);
        *buffer = NULL;
    }
    return *buffer;
}

const char *MBtextfile_read(MBtextfile *file)
{
    unsigned int eof = 0; /* At end of file */
    ssize_t cr = -1;      /* Position of first cr/lf character */
    size_t lf = 0;        /* Position of the matching cr/lf character */

    if (file->eof) {
        return NULL;
    }

    while (cr == -1) {
        unsigned long i;
        /* Find the first cr/lf character */
        for (i = 0; i < file->count && cr == -1; i++) {
            if (file->inbuf[i] == 0x0A) {
                cr = i;
                lf = cr;
                /* And its partner, if any */
                if (i < file->count && file->inbuf[i + 1] == 0x0D) {
                    lf++;
                }
            }
        }
        if (cr == -1 && !eof) {
            /* No cr/lf yet, and more bytes to read in the file */
            size_t bytes;
            size_t len;
            if (file->count == file->inbufsize) {
                /* Expand the in-buffer */
                file->inbufsize *= 2;
                if (!reallocate((void*)&file->inbuf, file->inbufsize)) {
                    return NULL;
                }
            }
            len = file->inbufsize - file->count;
            /* Read into the in-buffer at the end of the data already there */
            bytes = fread(file->inbuf + file->count, 1, len, file->fptr);
            file->count += bytes;
            eof = bytes < len;
        }
        if (eof && cr == -1) {
            /* End of file, and no cr/lf */
            file->eof = 1;
            cr = (ssize_t)file->count; /* To set the out-buffer size */
        }
    }
    if ((size_t)(cr + 1) > file->outbufsize) {
        /* Expand the out-buffer */
        file->outbufsize = cr + 1;
        if (!reallocate((void*)&file->outbuf, file->outbufsize)) {
            return NULL;
        }
    }
    /* Copy the line to the out-buffer and nul-terminate it */
    memcpy(file->outbuf, file->inbuf, cr);
    file->outbuf[cr] = '\0';
    if (!file->eof) {
        /* Move the remainder to the front of the in-buffer */
        size_t len = file->count - (lf + 1);
        memmove(file->inbuf, file->inbuf + lf + 1, len);
        file->count = len;
    }

    return file->outbuf;
}