/*
 * fhist - file history and comparison tools
 * Copyright (C) 1991-1994, 1998-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/>.
 */

#include <common/ac/string.h>

#include <common/cmalloc.h>
#include <common/compare.h>
#include <common/error_intl.h>
#include <common/fcheck.h>
#include <common/fileio.h>
#include <common/input/file.h>
#include <common/input/file_text.h>
#include <common/input/quotprinenco.h>
#include <common/trace.h>

#include <fhist/fhist.h>
#include <fhist/modlin.h>

#define INPUT_LINE_SIZE 200     /* longest input line */
#define OUTPUT_LINE_SIZE 300    /* longest output line */
#define MAX_VALUES 4            /* most number of values per line */
#define MIN_WORD 4              /* shortest keyword */
#define MAX_WORD 6              /* longest keyword */
#define SEQ_EDGE 3              /* size of edge of sequence */
#define MIN_SEQUENCE ((SEQ_EDGE * 2) + MIN_WORD)        /* smallest sequence */

#define V_EDIT 1                /* edit keyword */
#define V_DATE 2                /* date keyword */
#define V_USER 3                /* user name keyword */
#define V_MODULE 4              /* module keyword */

typedef struct VALUE VALUE;
struct VALUE
{
    short           v_type;         /* type of value */
    const char      *v_str;         /* new string value */
    char            *v_begval;      /* beginning of old value */
    char            *v_endval;      /* end of old value */
};

/*
 * Routine to find a special sequence inside of a line, and return
 * pointers to the beginning and end of the inner part of the sequence.
 * Returns 1 if found a potential sequence for examination.
 */

static int
scansequence(char *cp, long linelen, char **begseq, char **endseq)
{
    char    *cp2;           /* test characters */
    long    linelen2;       /* length of line left */

    if (linelen > INPUT_LINE_SIZE)
        return 0;
    while (linelen > MIN_SEQUENCE)
    {
        if ((cp[0] != '[') || (cp[1] != '#') || (cp[2] != ' '))
        {
            cp++;
            linelen--;
            continue;
        }
        cp += SEQ_EDGE;
        linelen -= SEQ_EDGE;
        linelen2 = linelen;
        cp2 = cp;
        while ((cp2[0] != ' ') || (cp2[1] != '#') || (cp2[2] != ']'))
        {
            if (--linelen2 <= SEQ_EDGE)
                return 0;
            cp2++;
        }
        *begseq = cp;
        *endseq = cp2;
        return 1;
    }
    return 0;
}


/*
 * Routine to find keywords within special sequences on a line.
 * Returns a list of the new values for those keywords, and the locations
 * where the old values are within the line.  Return value is the number
 * of keywords found, or -1 if something invalid was found.
 */

static int
scanwords(char *line, long len, INFO *info, VALUE *vp)
{
    char    *cp;            /* current character */
    char    *op;            /* output character */
    char    *beg;           /* beginning of special sequence */
    char    *end;           /* end of special sequence */
    char    *endword;       /* end of word */
    int     count;          /* number of values found */
    int     ch;             /* current character */
    int     type;           /* type of value found */
    char    word[MAX_WORD + 2]; /* lower cased word */

    count = 0;
    endword = &word[MAX_WORD];
    while (scansequence(line, len, &beg, &end))
    {
        for (cp = beg; cp != end; )
        {
            const char      *str;           /* string value */

            if (++count > MAX_VALUES)
                return -1;
            for (op = word; ; cp++)
            {
                ch = *cp;
                if ((ch == ' ') || (ch == ','))
                    break;
                if (op == endword)
                    return -1;
                if ((ch >= 'A') && (ch <= 'Z'))
                    ch += ('a' - 'A');
                *op++ = ch;
            }
            *op = '\0';
            vp->v_begval = cp;
            while ((cp != end) && (*cp != ','))
                cp++;
            vp->v_endval = cp;
            if (cp != end)
            {
                if ((++cp == end) || (*cp++ != ' ') || (cp == end))
                return -1;
            }
            if (strcmp(word, "edit") == 0)
                type = V_EDIT;
            else if (strcmp(word, "date") == 0)
                type = V_DATE;
            else if (strcmp(word, "user") == 0)
                type = V_USER;
            else if (strcmp(word, "module") == 0)
                type = V_MODULE;
            else
                return -1;
            str = NULL;
            if (info)
            {
                switch (type)
                {
                case V_EDIT: str = info->i_edit; break;
                case V_DATE: str = info->i_date; break;
                case V_USER: str = info->i_user; break;
                case V_MODULE: str = sc.modulename; break;
                }
            }
            vp->v_str = str;
            vp->v_type = type;
            vp++;
        }
        len -= (end - line);
        line = end;
    }
    return count;
}


/*
 * This routine detects lines which contain strings with the format:
 *      [# keyword value, keyword value, ..., keyword value #]
 * and changes the values to appropriate strings as specified by the keywords.
 * If the format is not correct, any keyword is unknown, or the line is too
 * long, then the line is unchanged.  Keywords can be in either lower or upper
 * case.  This routine is only called for the first 20 lines of the file in
 * order to increase the speed of processing the rest of the file data.
 * The info structure provides the values for insertion.  If the pointer is
 * NULL, then all the values will be cleared.
 */

char *
modifyline(char *cp, long *linelen, INFO *info)
{
    char    *op;            /* current output character */
    VALUE   *vp;            /* current value on line */
    char    *endline;       /* ending position in line */
    long    len;            /* line length */
    int     count;          /* number of values on line */
    VALUE   values[MAX_VALUES]; /* information about values */

    if (sc.modifylines == 0)
        return cp;
    len = *linelen;
    if ((len < MIN_SEQUENCE) || (len > INPUT_LINE_SIZE))
        return cp;
    count = scanwords(cp, len, info, values);
    if (count <= 0)
        return cp;
    if (!sc.modifybuffer)
        sc.modifybuffer = cm_alloc_and_check(OUTPUT_LINE_SIZE);

    /*
     * Copy the old line to the new one, except replace the old
     * values with the new ones.
     */
    endline = cp + len;
    op = sc.modifybuffer;
    for (vp = values; --count >= 0; vp++)
    {
        const char      *str;           /* string for replacement */

        while (cp != vp->v_begval)
            *op++ = *cp++;
        str = vp->v_str;
        if (str)
        {
            /* no memcpy because char != wchar_t */
            *op++ = ' ';
            while (*str)
                *op++ = *str++;
        }
        cp = vp->v_endval;
    }
    while (cp != endline)
        *op++ = *cp++;
    *op = '\0';
    *linelen = (op - sc.modifybuffer);
    return sc.modifybuffer;
}


/*
 * Routine to search through the beginning of a file to find the edit
 * number, and return its value.  If the edit number is not found, or
 * if its value is null, then -1 is returned.
 *
 * The history (.e) file is binary, because we need to seek in it.
 * The source (.s) file is text, because we don't need to seek in it.
 * The input files are text, by definition.
 * The output files are text, by definition.
 */

long
findedit(const char *inputname, int binary)
{
    char            *cp;        /* current line */
    input_ty        *fp;        /* file being read */
    VALUE           *vp;        /* current value */
    long            linelen;    /* line length */
    long            num;        /* scanned number */
    int             count;      /* number of values on line */
    long            i;          /* line counter */
    VALUE           values[MAX_VALUES]; /* returned values */
    int             bin;        /* IGNORED */

    trace(("findedit(inputname = \"%s\", binary = %d)\n{\n", inputname,
    binary));
    if (!binary)
        fp = input_file_text_open(inputname);
    else
    {
        fp = input_file_open(inputname);
        fp = input_quoted_printable_encode(fp, 1);
    }
    for (i = sc.modifylines; i > 0; i--)
    {
        cp = input_readline(fp, &linelen, 0, &bin);
        if (cp == NULL)
            break;
        count = scanwords(cp, linelen, (INFO *) NULL, values);
        for (vp = values; --count >= 0; vp++)
        {
            if (vp->v_type != V_EDIT)
                continue;
            cp = vp->v_begval + 1;
            num = 0;
            while ((*cp >= '0') && (*cp <= '9'))
                num = (num * 10) + (*cp++ - '0');
            if ((cp == vp->v_endval) && (num > 0))
            {
                input_delete(fp);
                trace(("return %ld;\n", num));
                trace(("}\n"));
                return num;
            }
        }
    }
    input_delete(fp);
    trace(("return -1;\n}\n"));
    return -1;
}


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