/*
 * fhist - file history and comparison tools
 * Copyright (C) 1991-1994, 1998-2000, 2002, 2008, 2010, 2012 Peter Miller
 *
 * Derived from a work
 * Copyright (C) 1990 David I. Bell.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Functions to position within an ascii file and read lines from it.
 */

#include <common/ac/errno.h>
#include <fcntl.h>
#include <common/ac/libintl.h>
#include <common/ac/stdarg.h>
#include <common/ac/stdio.h>
#include <common/ac/string.h>
#include <common/ac/unistd.h>
#include <libexplain/fflush.h>
#include <libexplain/fread.h>
#include <libexplain/fseek.h>
#include <libexplain/fwrite.h>
#include <libexplain/getc.h>
#include <libexplain/malloc.h>
#include <libexplain/realloc.h>

#include <common/error_intl.h>
#include <common/fcheck.h>
#include <common/fileio.h>
#include <common/cmalloc.h>
#include <common/str.h>

#define LINEALLOCSIZE 132       /* allocation size for line buffer */


/*
 * Position absolutely within a file to the indicated byte position,
 * returns a negative value if failed.
 */

void
seekf(FILE *fp, long pos)
{
    /* FIXME: should be "off_t pos" */
    explain_fseek_or_die(fp, pos, SEEK_SET);
}


/*
 * Skip forwards through a file by the specified number of lines.
 * Returns nonzero on end of file or any error.
 */

int
skipf(FILE *fp, long count)
{
    int             c;

    while (count > 0)
    {
        c = explain_getc_or_die(fp);
        if (c == EOF)
        {
            return -1;
        }
        if (c == '\n')
            --count;
    }
    return 0;
}


/*
 * Function to read in the next line from a file, no matter how long it is.
 * This returns a pointer to the string, and also indirectly its length.
 * The string is normally returned from a static string, which is reused
 * for each call.  But if keep is non-zero, then the returned string
 * belongs to the caller and must later be freed.  The returned line
 * is ended with a newline and null character, but the returned length
 * does not count the null character.  Returns 0 on an error, with the
 * error stored in the fp structure.
 */

char *
readlinef(FILE *fp, long *retlen, int keep, const char *filename,
    int *is_bin_file)
{
    char            *cp;                /* pointer to character string */
    char            *dp;                /* destination pointer */
    long            cursize;            /* current size */
    long            count;              /* length of data read from file */
    long            totallen;           /* total length of data */
    static char     *linebuffer;        /* common line buffer */
    static long     linelength;         /* total length of line */

    totallen = 0;
    if (linelength == 0)
    {
        /* allocate line buffer */
        linebuffer = explain_malloc_or_die(LINEALLOCSIZE + 1);
        linelength = LINEALLOCSIZE;
    }
    cp = linebuffer;
    cursize = linelength;
    for (;;)
    {
        count = readf(fp, cp, cursize, is_bin_file);
        if (count <= 0)
        {
            if (totallen == 0)
                return 0;
            warning_last_line_unterminated(filename);
            count = 1;
            cp[0] = '\n';
        }
        totallen += count;
        if (cp[count - 1] == '\n')
            break;
        linebuffer =
            explain_realloc_or_die(linebuffer, linelength + LINEALLOCSIZE + 1);
        cp = linebuffer + totallen;
        linelength += LINEALLOCSIZE;
        cursize = LINEALLOCSIZE;
    }
    if (retlen)
        *retlen = totallen;
    if (!keep)
        dp = linebuffer;
    else
    {
        dp = cm_alloc_and_check(totallen + 1);
        memcpy(dp, linebuffer, (size_t)totallen);
    }
    dp[totallen] = 0;
    return dp;
}


/*
 * Read data from the current position in a file until a newline is found
 * or the supplied buffer is full.  These two cases can be distinguished by
 * checking the last character of the returned data.  Returns the number of
 * characters in the line (including the newline), or -1 if an error or end
 * of file in the middle of the line is found.  Line is terminated by a null
 * character if space allows.  Returns zero if the end of file occurs at
 * the front of the line.
 */

long
readf(FILE *fp, char *buf, long size, int *is_bin_file)
{
    char            *bp;
    char            *ep;
    int             c;

    bp = buf;
    ep = buf + size;
    while (bp < ep)
    {
        c = explain_getc_or_die(fp);
        if (c == EOF)
        {
            if (bp != buf)
                break;
            return -1;
        }
        if (c == 0)
        {
            c = 0x80;
            *is_bin_file = 1;
        }
        *bp++ = c;
        if (c == '\n')
            break;
    }
    return (bp - buf);
}


/*
 * Write data at the current position in a file.
 * This buffers the data in the current block, thus flushing is needed later.
 * Prints a fatal error message and exits on errors.
 */

void
writefx(FILE *fp, const char *buf, long size)
{
    explain_fwrite_or_die(buf, 1, size, fp);
}


void
copyfx(FILE *ip, FILE *op, long count)
{
    char            buffer[1 << 10];

    while (count)
    {
        size_t          len;

        if (count < 0 || (size_t)count > sizeof(buffer))
            len = sizeof(buffer);
        else
            len = count;
        len = explain_fread_or_die(buffer, 1, len, ip);
        if (len == 0)
            break;
        explain_fwrite_or_die(buffer, 1, len, op);
        if (count >= 0)
            count -= len;
    }
    explain_fflush_or_die(op);
}


void
typefx(FILE *fp)
{
    copyfx(fp, stdout, -1L);
}


/* vim: set ts=8 sw=4 et : */
