/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 2000-2002 Peter Miller;
 *	All rights reserved.
 *
 *	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 2 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, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions for reading input
 */

#include <ac/ctype.h>
#include <ac/string.h>

#include <fcheck.h>
#include <input/private.h>
#include <mem.h>
#include <trace.h>


#ifdef input_name
#undef input_name
#endif

const char *
input_name(input_ty *fp)
{
    const char      *result;

    trace(("input_name(fp = %8.8lX)\n{\n", (long)fp));
    result = fp->vptr->name(fp);
    trace(("return \"%s\";\n", result));
    trace(("}\n"));
    return result;
}


#ifdef input_length
#undef input_length
#endif

long
input_length(input_ty *fp)
{
    long	    result;

    trace(("input_length(fp = %8.8lX)\n{\n", (long)fp));
    result = fp->vptr->length(fp);
    trace(("return %ld;\n", result));
    trace(("}\n"));
    return result;
}


#ifdef input_ftell
#undef input_ftell
#endif

long
input_ftell(input_ty *fp)
{
    long	    result;

    trace(("input_ftell(fp = %8.8lX)\n{\n", (long)fp));
    result = fp->vptr->ftell(fp) - fp->pushback_len;
    trace(("return %ld;\n", result));
    trace(("}\n"));
    return result;
}


#ifdef input_read
#undef input_read
#endif

long
input_read(input_ty *fp, void *data, long len)
{
    long	    result;

    trace(("input_read(fp = %8.8lX, data = %8.8lX, len = %8.8lX)\n{\n",
	(long)fp, (long)data, len));
    if (len <= 0)
    {
	result = 0;
    }
    else
    {
	if (fp->pushback_len > 0)
	{
	    fp->pushback_len--;
	    *(char *)data = fp->pushback_buf[fp->pushback_len];
	    result = 1;
	}
	else
	{
	    result = fp->vptr->read(fp, data, len);
	}
    }
    trace(("return %ld;\n", result));
    trace(("}\n"));
    return result;
}


#ifdef DEBUG

static char *
unctrl(int c)
{
    static char	    buffer[10];

    if (c < 0 || c >= 256)
    {
	snprintf(buffer, sizeof(buffer), "%d", c);
	return buffer;
    }
    switch (c)
    {
    case '\'':
    case '\\':
	escape:
	buffer[0] = '\'';
	buffer[1] = '\\';
	buffer[2] = c;
	buffer[3] = '\'';
	buffer[4] = 0;
	break;

    case '\b': c = 'b'; goto escape;
    case '\f': c = 'f'; goto escape;
    case '\n': c = 'n'; goto escape;
    case '\r': c = 'r'; goto escape;
    case '\t': c = 't'; goto escape;

    default:
	if (isprint((unsigned char)c))
	{
	    buffer[0] = '\'';
	    buffer[1] = c;
	    buffer[2] = '\'';
	    buffer[3] = 0;
	}
	else
	    snprintf(buffer, sizeof(buffer), "'\\%o'", c);
	break;
    }
    return buffer;
}

#endif



#ifdef input_getc
#undef input_getc
#endif

int
input_getc(input_ty *fp)
{
    int		    result;

    trace(("input_getc(fp = %8.8lX)\n{\n", (long)fp));
    if (fp->pushback_len > 0)
    {
	fp->pushback_len--;
	result = fp->pushback_buf[fp->pushback_len];
    }
    else
	result = fp->vptr->get(fp);
    trace(("return %s;\n", unctrl(result)));
    trace(("}\n"));
    return result;
}


#ifdef input_ungetc
#undef input_ungetc
#endif

void
input_ungetc(input_ty *fp, int c)
{
    trace(("input_ungetc(fp = %8.8lX, c = %s)\n{\n", (long)fp, unctrl(c)));
    if (c >= 0)
    {
	if (fp->pushback_len >= fp->pushback_max)
	{
	    fp->pushback_max = 16 + 2 * fp->pushback_max;
	    fp->pushback_buf =
		    mem_change_size(fp->pushback_buf, fp->pushback_max);
	}
	fp->pushback_buf[fp->pushback_len++] = c;
    }
    trace(("}\n"));
}


void
input_delete(input_ty *fp)
{
    trace(("input_delete(fp = %8.8lX)\n{\n", (long)fp));
    if (fp->vptr->destruct)
	fp->vptr->destruct(fp);
    if (fp->pushback_buf)
	mem_free(fp->pushback_buf);
    fp->pushback_buf = 0;
    fp->pushback_len = 0;
    fp->pushback_max = 0;
    fp->vptr = 0; /* paranoia */
    mem_free(fp);
    trace(("}\n"));
}


#include <cmalloc.h>

/*
 * Routine 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 *
input_readline(input_ty *fp, long *retlen, int keep, int *is_bin_file)
{
    char	    *dp;	/* destination pointer */
    size_t	    totallen;	/* total length of data */
    static char	*linebuffer;	/* common line buffer */
    static size_t    linelength; /* total length of line */

    trace(("input_readline(fp = %8.8lX, retlen = %8.8lX, keep = %d)\n{\n",
	(long)fp, (long)retlen, keep));
    if (linelength == 0)
    {
	/* allocate line buffer */
	linelength = 16;
	linebuffer = r_alloc_and_check(linelength + 1);
    }
    totallen = 0;
    for (;;)
    {
	int c = input_getc(fp);
	if (c < 0)
	{
	    if (totallen == 0)
	       	return 0;
	    warning_last_line_unterminated(input_name(fp));
	    c = '\n';
	}
	if (c == 0)
	{
	    *is_bin_file = 1;
	    c = 0x80;
	}
	if (totallen >= linelength)
	{
	    /*
	     * doubling gives O(1) behaviour
	     * over the life of the prog
	     */
	    linelength = linelength * 2 + 16;
	    linebuffer = r_realloc_and_check(linebuffer, linelength + 1);
	}
	linebuffer[totallen++] = c;
	if (c == '\n')
	    break;
    }
    linebuffer[totallen] = 0;

    if (retlen)
	    *retlen = totallen;
    if (!keep)
	dp = linebuffer;
    else
    {
	dp = cm_alloc_and_check(totallen + 1);
	memcpy(dp, linebuffer, (size_t)totallen);
    }
    trace(("return %8.8lX;\n", (long)dp));
    trace(("}\n"));
    return dp;
}


void
input_skip_lines(input_ty *fp, int numlines)
{
    while (numlines > 0)
    {
	int c = input_getc(fp);
	if (c < 0)
    	    break;
	if (c == '\n')
    	    --numlines;
    }
}
