/*
 *      fhist - file history and comparison tools
 *      Copyright (C) 2000-2002, 2008, 2010 Peter Miller
 *
 *      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/>.
 */

#include <common/ac/ctype.h>
#include <common/ac/string.h>
#include <libexplain/malloc.h>
#include <libexplain/realloc.h>

#include <common/fcheck.h>
#include <common/input/private.h>
#include <common/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 =
                explain_realloc_or_die(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)
        free(fp->pushback_buf);
    fp->pushback_buf = 0;
    fp->pushback_len = 0;
    fp->pushback_max = 0;
    fp->vptr = 0; /* paranoia */
    free(fp);
    trace(("}\n"));
}


#include <common/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 = explain_malloc_or_die(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 = explain_realloc_or_die(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;
    }
}
