/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1991-1994, 1998-2002 Peter Miller;
 *	All rights reserved.
 *
 *	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 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 to modify lines of files for special purposes.
 */

#include <ac/string.h>

#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <fcheck.h>
#include <fhist.h>
#include <fileio.h>
#include <input/file_text.h>
#include <input/file.h>
#include <input/quotprinenco.h>
#include <modlin.h>
#include <trace.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 */
	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 */
	char	*str;		/* string value */
	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; ) {
			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 */
	char	*str;		/* string for replacement */
	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++) {
		while (cp != vp->v_begval)
			*op++ = *cp++;
		str = vp->v_str;
		if (str) {
			*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(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;
}
