/*
 * copyright (c) 1997-2005  Motoyuki Kasahara
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>

#include "linebuf.h"

#ifdef USE_FAKELOG
#include "fakelog.h"
#endif


/*
 * Initialize `linebuffer'.
 */
void
initialize_line_buffer(Line_Buffer *line_buffer)
{
    line_buffer->buffer[0] = '\0';
    line_buffer->file = -1;
    line_buffer->cache_length = 0;
    line_buffer->timeout = 0;
}


/*
 * Finalize `line_buffer'.
 */
void
finalize_line_buffer(Line_Buffer *line_buffer)
{
    line_buffer->buffer[0] = '\0';
    line_buffer->file = -1;
    line_buffer->cache_length = 0;
    line_buffer->timeout = 0;
}


/*
 * Set timeout seconds.
 */
void
set_line_buffer_timeout(Line_Buffer *line_buffer, int timeout)
{
    line_buffer->timeout = timeout;
}


/*
 * Bind `file' to `line_buffer'.
 */
void
bind_file_to_line_buffer(Line_Buffer *line_buffer, int file)
{
    if (line_buffer->file < 0)
	initialize_line_buffer(line_buffer);
    line_buffer->file = file;
}


/*
 * Return file descriptor bound to `line_buffer'.
 * Return -1 when no file is bound.
 */
int
file_bound_to_line_buffer(Line_Buffer *line_buffer)
{
    return line_buffer->file;
}


/*
 * Discard cache data in `line_buffer'.
 */
void
discard_cache_in_line_buffer(Line_Buffer *line_buffer)
{
    line_buffer->cache_length = 0;
}


/*
 * Get the length of cache data in `line_buffer'.
 */
size_t
cache_length_in_line_buffer(Line_Buffer *line_buffer)
{
    return line_buffer->cache_length;
}


/*
 * Read a line from the file bound to `line_buffer', and copy the read
 * line to `line'.  It reads at most `max_line_length' bytes.
 *
 * The function recognizes both "\n" and "\r\n" as end of line.
 * "\n" or "\r\n" is not copied to `buffer', but "\0" is added, instead.
 *
 * The function returns the number of characters in the line, upon
 * successful.  It doesn't count "\n" or "\r\n" in the tail of the line,
 * so that 0 is returned for an empty line, and the line length doesn't
 * exceed one less than `max_line_length'.
 *
 * If EOF is received or an error occurs, -1 is returned.  If the
 * line is too long, `max_line_length' is returned.
 */
ssize_t
read_line_buffer(Line_Buffer *line_buffer, char *line, size_t max_line_length)
{
    char *line_p;
    char *newline;
    size_t search_length;
    size_t additional_length;
    size_t line_length;
    fd_set fdset;
    struct timeval timeval;
    int select_result;
    ssize_t read_result;

    /*
     * Return -1 if no file is bound, or if `max_line_length' is 0.
     */
    if (line_buffer->file < 0)
	return -1;
    if (max_line_length == 0)
	return -1;

    /*
     * Read a file until newline is appeared.
     */
    line_length = 0;
    line_p = line;

    for (;;) {
	if (0 < line_buffer->cache_length) {
	    /*
	     * Find a newline in the cache data.
	     */
	    if (max_line_length - line_length < line_buffer->cache_length)
		search_length = max_line_length - line_length;
	    else
		search_length = line_buffer->cache_length;

	    newline = (char *)memchr(line_buffer->buffer, '\n', search_length);

	    /*
	     * Append cache data in front of the newline to `line'.
	     */
	    if (newline != NULL)
		additional_length = newline - line_buffer->buffer + 1;
	    else
		additional_length = search_length;
	    memcpy(line_p, line_buffer->buffer, additional_length);
	    line_p += additional_length;
	    line_length += additional_length;
	    line_buffer->cache_length -= additional_length;

	    /*
	     * If cache data not copied to `line' are remained in the
	     * buffer, we move them to the beginning of the buffer.
	     */
	    memmove(line_buffer->buffer,
		line_buffer->buffer + additional_length,
		line_buffer->cache_length);

	    if (newline != NULL)
		break;
	}

	/*
	 * Check for the length of the current line.  Return if the
	 * line is too long.
	 *
	 * Note that the following conditional expression can be
	 * substituted to (line_buffer->cache_length != 0), because
	 * remained cache data mean that the line is too long.
	 */
	if (max_line_length <= line_length)
	    return line_length;

	/*
	 * Call select().
	 */
	errno = 0;
	FD_ZERO(&fdset);
	FD_SET(line_buffer->file, &fdset);

	if (line_buffer->timeout == 0) {
	    select_result = select(line_buffer->file + 1, &fdset, NULL, NULL,
		NULL);
	} else {
	    timeval.tv_sec = line_buffer->timeout;
	    timeval.tv_usec = 0;
	    select_result = select(line_buffer->file + 1, &fdset, NULL, NULL,
		&timeval);
	}
	if (select_result < 0) {
	    if (errno == EINTR)
		continue;
	    return -1;
	} else if (select_result == 0) {
	    return -1;
	}

	/*
	 * Read from a file.  (No cache data are remaind.)
	 */
	errno = 0;
	read_result = recv(line_buffer->file, line_buffer->buffer,
	    LINEBUF_BUFFER_SIZE, 0);
	if (read_result < 0) {
	    if (errno == EINTR)
		continue;
	    return -1;
	} else if (read_result == 0) {
	    if (line_length == 0) {
		return -1;
	    }
	    return line_length;
	}
	line_buffer->cache_length += read_result;
    }

    /*
     * Overwrite `\n' with `\0'.
     */
    line_p--;
    *line_p = '\0';
    line_length--;

    /*
     * If the line is end with `\r\n', remove not only `\n' but `\r'.
     */
    if (0 < line_length && *(line_p - 1) == '\r') {
	line_p--;
	*line_p = '\0';
	line_length--;
    }

    return line_length;
}


/*
 * Read just `stream_length' bytes from the file bound to `line_buffer',
 * and copy the read bytes to `stream'.
 *
 * Unlike read_line_buffer(), it doesn't append `\0' to the read data,
 * nor remove newline character in the read data.
 *
 * If it succeeds, the number of bytes actually read is returned.
 * If EOF is received or an error occurs, -1 is returned.
 */
ssize_t
binary_read_line_buffer(Line_Buffer *line_buffer, char *stream,
    size_t stream_length)
{
    char *stream_p;
    size_t done_length;
    fd_set fdset;
    struct timeval timeval;
    int select_result;
    ssize_t read_result;

    /*
     * Return -1 if no file is bound.
     */
    if (line_buffer->file < 0)
	return -1;

    /*
     * Return 0 if `stream_length' is 0.
     */
    if (stream_length == 0)
	return 0;

    /*
     * Test whether cache data are left in `line_buffer->buffer'.
     * If they are, copy them to `stream'.
     */
    stream_p = stream;
    done_length = 0;

    if (0 < line_buffer->cache_length) {
	if (stream_length <= line_buffer->cache_length)
	    done_length = stream_length;
	else
	    done_length = line_buffer->cache_length;

	memcpy(stream_p, line_buffer->buffer, done_length);
	stream_p += done_length;
	line_buffer->cache_length -= done_length;
	memmove(line_buffer->buffer,
	    line_buffer->buffer + done_length,
	    line_buffer->cache_length);
    }

    /*
     * Read the file until the number of read bytes (`done_length') is
     * reached to `stream_length'.
     */
    while (done_length < stream_length) {
	/*
	 * Call select().
	 */
	errno = 0;
	FD_ZERO(&fdset);
	FD_SET(line_buffer->file, &fdset);

	if (line_buffer->timeout == 0) {
	    select_result = select(line_buffer->file + 1, NULL, &fdset, NULL,
		NULL);
	} else {
	    timeval.tv_sec = line_buffer->timeout;
	    timeval.tv_usec = 0;
	    select_result = select(line_buffer->file + 1, NULL, &fdset, NULL,
		&timeval);
	}
	if (select_result < 0) {
	    if (errno == EINTR)
		continue;
	    return -1;
	} else if (select_result == 0) {
	    return -1;
	}

	/*
	 * Read from a file.
	 */
	errno = 0;
	read_result = recv(line_buffer->file, stream_p,
	    stream_length - done_length, 0);
	if (read_result < 0) {
	    if (errno == EINTR)
		continue;
	    return read_result;
	} else if (read_result == 0) {
	    if (done_length == 0) {
		return -1;
	    }
	    return done_length;
	}
	stream_p += read_result;
	done_length += read_result;
    }

    return stream_length;
}


/*
 * Skip too long line read by read_line_buffer().
 *
 * If a line read by read_line_buffer() doesn't contain a newline
 * character, the line is too long.  This function reads and discards
 * the rest of the line.
 *
 * If EOF is received or an error occurs, -1 is returned.
 * Otherwise, 0 is returned.
 */
int
skip_line_buffer(Line_Buffer *line_buffer)
{
    ssize_t line_length;

    /*
     * Read data until the end of the line is found.
     */
    for (;;) {
	line_length = read_line_buffer(line_buffer, line_buffer->buffer,
	    LINEBUF_BUFFER_SIZE);
	if (line_length < 0)
	    return -1;
	if (line_length < LINEBUF_BUFFER_SIZE)
	    break;
    }

    return 0;
}


