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

#include "mallocvar.h"
#include "nstring.h"

#include "io.h"


void
pm_freadline(FILE *        const fileP,
             const char ** const lineP,
             const char ** const errorP) {
/*----------------------------------------------------------------------------
   Read a line (assuming the file is text with lines delimited by newlines)
   for file *fileP.  Return that line in newly malloced storage as
   *lineP.

   The newline delimiter is not part of the line.

   As a special case, if the file doesn't end in a newline, the characters
   after the last newline are a line.

   If there are no more lines in the file, return *lineP == NULL.
-----------------------------------------------------------------------------*/
    char * buffer;
    size_t bufferSize;
    size_t cursor;
    bool gotLine;
    bool eof;

    bufferSize = 1024;  /* initial value */
    *errorP = NULL; /* initial value */
    
    MALLOCARRAY(buffer, bufferSize);

    for (cursor = 0, gotLine = false, eof = false;
         !gotLine && !eof && !*errorP; ) {
        if (cursor + 1 >= bufferSize) {
            if (bufferSize > INT_MAX/2) {
                free(buffer);
                buffer = NULL;
            } else {
                bufferSize *= 2;
                REALLOCARRAY(buffer, bufferSize);
            }
        }

        if (!buffer)
            pm_asprintf(errorP,
                        "Couldn't get memory for a %u-byte file read buffer.",
                        (unsigned int)bufferSize);
        else {
            int const rc = getc(fileP);
        
            if (rc < 0) {
                if (feof(fileP))
                    eof = true;
                else
                    pm_asprintf(errorP,
                                "Failed to read a character from file.  "
                                "Errno = %d (%s)",
                                errno, strerror(errno));
            } else {
                char const c = (char)rc;

                if (c == '\n')
                    gotLine = true;
                else {
                    buffer[cursor++] = c;
                }
            }
        }
    }
    if (*errorP) {
        if (buffer)
            free(buffer);
    } else {
        if (eof && cursor == 0) {
            *lineP = NULL;
            free(buffer);
        } else {
            assert(cursor < bufferSize);
            buffer[cursor++] = '\0';

            *lineP = buffer;
        }
    }
}
