/** \file
 *
 * LineReader class implementation.
 *
 *  Copyright (c) 2015 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "LineReader.h"
#include <stdlib.h>
#include <fcntl.h>
#include <cerrno>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>

using namespace std;


int LineReader::readLine( char* buf, size_t bufSize )
{
    // Note: for now we resize internal buffer if bufSize is bigger
    // just to simplify logic below
    if( bufferSize_ < bufSize )
    {
        adjustBuffer( bufSize );
    }

    int fillBufferRes = fillInBuffer();

    if( fillBufferRes < 0 )
    {
        return -2;  // some actual error
    }
    // Note: fillBufferRes == 0 is an acceptable condition here; the internal read
    // operation may indicate end-of-file, but we need to handle it in general as a
    // "not-yet-more-data-at-the-moment" situation.

    if( bufferRead_ == 0 )
    {
        return 0;
    }

    char* lf = ( char* ) memchr( buffer_, '\n', bufferRead_ );

    if( lf != 0 )
    {
        size_t receivedLineSize = lf - buffer_ + 1; // +1 to include LF
        size_t transferSize = receivedLineSize <= bufSize ? receivedLineSize : bufSize;
        memcpy( buf, buffer_, transferSize );
        updateBuffer( transferSize );
        return transferSize;
    }

    // Else: we have a non-empty buffer without trailing LF.
    // Check whether we should transfer to the output buffer or not.

    else if( bufSize <= bufferRead_ )
    {
        // yes, fill the output buffer and return bufSize.
        memcpy( buf, buffer_, bufSize );
        updateBuffer( bufSize );
        return bufSize;
    }

    // Else: No complete line yet received and still room in output buffer
    else
    {
        return -1;
    }
}

// only called when bufferSize_ < bufSize in call to readLine.
void LineReader::adjustBuffer( size_t bufSize )
{
    char* new_buffer = new char[bufSize];
    memcpy( new_buffer, buffer_, bufferRead_ );
    delete[] buffer_;
    buffer_ = new_buffer;
    bufferSize_ = bufSize;
}

//
// Adds to buffer whatever is available to read without blocking.
//
// \return Number of bytes read in this call.
//         A negative return value indicates some error.
//         Note: zero will indicate either an actual end-of-file, or simply
//         no-data-at-the-moment because of the underlying non-blocking read.
//
int LineReader::fillInBuffer()
{
    if( bufferSize_ - bufferRead_ > 0 )
    {
        int bytesRead = nbRead( buffer_ + bufferRead_,
                                bufferSize_ - bufferRead_ );
        if( bytesRead > 0 )
        {
            bufferRead_ += bytesRead;
        }
        return bytesRead;
    }
    else
    {
        return -1;  // buffer is full
    }
}

// Updates internal buffer according to the given processed size.
void LineReader::updateBuffer( size_t processed )
{
    assert( bufferRead_ >= processed );
    memmove( buffer_, buffer_ + processed, bufferRead_ - processed );
    bufferRead_ -= processed;
}

// non-blocking read from the descriptor
int LineReader::nbRead( char* buffer, size_t bufferLength )
{
    fd_set read_fds;
    FD_ZERO( &read_fds );
    FD_SET( fd_, &read_fds );

    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    int retval = select( fd_ + 1, &read_fds, 0, 0, &tv );
    if( retval < 0 )
    {
        perror( "LineReader: error in select system call" );
        return retval;
    }
    else if( retval )
    {
        retval = ::read( fd_, buffer, bufferLength );
        if( retval < 0 )
        {
            perror( "LineReader: error in read system call" );
            return retval;
        }
    }
    else
    {
        // timeout with no new data
        return 0;
    }

    return retval;
}
