/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1991-1994, 1998-2000, 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 compare different versions of a file
 */

#include <ac/errno.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/unistd.h>

#include <cmalloc.h>
#include <compare.h>
#include <diff.h>
#include <error.h>
#include <error_intl.h>
#include <extract.h>
#include <fcheck.h>
#include <fhist.h>
#include <fileio.h>
#include <modlin.h>
#include <str.h>
#include <subroutine.h>


/*
 * Compare two different versions of a module.
 * Count specifies how many edit numbers were supplied to the command.
 * If zero, then we compare inputname to the latest edit.  If one, then
 * we compare inputname to the specified edit number.  If two, then we
 * compare the specified edit numbers.  If matchblab is 1, then we
 * output a message even if the files are identical.
 */
void
diffhistory(char *inputname, char *outputname, char *editname1, char *editname2,
	int matchblab)
{
	long		edit1;	/* first edit number */
	long		edit2;	/* second edit number */
	char		*temp1;
	char		*temp2;	/* temporary file names being compared */
	int		pid;	/* process id */
	short		savedforceflag; /* saved force flag */
	string_ty	*namebuf1; /* first temporary name */
	string_ty	*namebuf2; /* second temporary name */
	string_ty	*numbuf; /* buffer for number string */
	FILE		*fp;

	fc.maxchanges = INFINITY;
	fc.fileAmodify = sc.modifylines;
	fc.fileBmodify = sc.modifylines;
	pid = getpid();
	temp1 = getenv("TMPDIR");
	temp2 = PATH_STR;
	if ((temp1 == NULL) || (*temp1 == '\0'))
	{
		temp1 = "";
		temp2 = temp1;
	}
	fp = openhistoryfile(OHF_READ);
	if (editname1 == NULL)
	{
		/* compare with latest version */
		fclose_and_check(fp, sc.historyname);
		if (fc.verbosity > VERBOSE_DEFAULT)
			error_raw("[Comparing files \"%s\" and \"%s\"]",
				sc.sourcename, inputname);
		fc.fileAskip = 1;
		fcomp(sc.sourcename, inputname);
		if (fc.whatflag)
			dumpwhat(outputname);
		else if (matchblab || fc.inserts || fc.deletes)
			dumpnormal(outputname);
		return;
	}
	namebuf1 = str_format("%s%sT$1_%d", temp1, temp2, pid);
	namebuf2 = str_format("%s%sT$2_%d", temp1, temp2, pid);

	/*
	 * Here when we have to retrieve a previous version.
	 */
	edit2 = 0;
	temp1 = NULL;
	temp2 = NULL;
	savedforceflag = sc.forcewriteflag;
	sc.forcewriteflag = 1;
	edit1 = findeditnumber(fp, editname1);
	if (edit1 != sc.lastedit)
		temp1 = namebuf1->str_text;
	if (editname2)
	{
		edit2 = findeditnumber(fp, editname2);
		if (edit2 != sc.lastedit)
			temp2 = namebuf2->str_text;
	}
	fclose_and_check(fp, sc.historyname);
	if (temp1)
	{
		numbuf = str_format("%ld", edit1);
		extracthistory(numbuf->str_text, temp1, VERBOSE_NONE);
		str_free(numbuf);
	}
	else
		fc.fileAskip = 1;

	if (editname2 == NULL)
	{
		sc.forcewriteflag = savedforceflag;
		fcomp(temp1 ? temp1 : sc.sourcename, inputname);
		if (temp1)
		{
			if (fc.verbosity > VERBOSE_DEFAULT)
				error_raw("[Deleting temporary file \"%s\"]", temp1);
			unlink(temp1);
		}
		if (fc.whatflag)
			dumpwhat(outputname);
		else if (matchblab || fc.inserts || fc.deletes)
			dumpnormal(outputname);
		str_free(namebuf1);
		str_free(namebuf2);
		return;
	}

	/*
	 * Here if we have to compare against another edit
	 */
	if (temp2)
	{
		numbuf = str_format("%ld", edit2);
		extracthistory(numbuf->str_text, temp2, VERBOSE_NONE);
		str_free(numbuf);
	}
	else
		fc.fileBskip = 1;

	sc.forcewriteflag = savedforceflag;
	fcomp(temp1 ? temp1 : sc.sourcename, temp2 ? temp2 : sc.sourcename);
	if (fc.verbosity > VERBOSE_DEFAULT)
	{
		if (temp1 && temp2)
			error_raw("[Deleting temporary files \"%s\" and \"%s\"]", temp1, temp2);
		else if (temp1 || temp2)
			error_raw("[Deleting temporary file \"%s\"]", temp1 ? temp1 : temp2);
	}
	if (temp1)
		unlink(temp1);
	if (temp2)
		unlink(temp2);
	if (fc.whatflag)
		dumpwhat(outputname);
	else if (matchblab || fc.inserts || fc.deletes)
		dumpnormal(outputname);
	str_free(namebuf1);
	str_free(namebuf2);
}


/*
 * Routine to do a quick comparison of two files.
 * Returns nonzero if the two files are different.
 *
 * 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.
 */

static int
quickcomp(char *nameA, char *nameB, long skipA)
{
	FILE	*fpA;		/* first file */
	FILE	*fpB;		/* second file */
	char	*cp;		/* current temporary line */
	char	*cpA;		/* current line for file A */
	char	*cpB;		/* current line for file B */
	long	linenumber;	/* current line number */
	long	cplen;		/* length of current temporary line */
	long	linelenA;	/* length of first file line */
	long	linelenB;	/* length of second file line */
	int	bin_a, bin_b;

	fpA = fopen_and_check(nameA, "r");
	fpB = fopen_and_check(nameB, "r");
	if (fc.verbosity > VERBOSE_DEFAULT)
		error_raw("[Comparing files \"%s\" and \"%s\"]", nameA, nameB);
	cplen = 132;
	cp = cm_alloc_and_check(cplen + 1);
	if (skipA > 0)
		skipf(fpA, skipA, nameA);
	linenumber = 0;
	bin_a = 0;
	bin_b = 0;
	for (;;)
	{
		cpA = readlinef(fpA, &linelenA, 0, nameA, &bin_a);
		if (cpA == NULL)
		{
			cpB = readlinef(fpB, &linelenB, 0, nameB, &bin_b);
			break;
		}
		if (linelenA > cplen)
		{
			cplen = linelenA;
			cp = cm_realloc_and_check(cp, cplen + 1);
		}
		strcpy(cp, cpA);
		cpA = cp;
		cpB = readlinef(fpB, &linelenB, 0, nameB, &bin_b);
		if (cpB == NULL)
			break;
		if (++linenumber <= sc.modifylines)
		{
			cpA = modifyline(cpA, &linelenA, (INFO *) NULL);
			cpB = modifyline(cpB, &linelenB, (INFO *) NULL);
		}
		if ((linelenA != linelenB) || strcmp(cpA, cpB))
			break;
	}
	if (bin_a)
		binary_warning(nameA);
	if (bin_b)
		binary_warning(nameB);
	fclose_and_check(fpA, nameA);
	fclose_and_check(fpB, nameB);
	cm_free(cp);
	return (cpA || cpB);
}


/*
 * See if a file is up to date, and if so, then delete it.
 * This is useful when cleaning up a directory.
 */

void
cleanhistory(char *inputname)
{
	FILE	*fp;

	fp = openhistoryfile(OHF_READ);
	fclose_and_check(fp, sc.historyname);
	if (access(inputname, 0) && (errno = ENOENT))
	{
		if (fc.verbosity > VERBOSE_DEFAULT)
		{
			sub_context_ty	*scp;

			scp = sub_context_new();
			sub_var_set_charstar(scp, "File_Name", inputname);
			sub_var_set_charstar(scp, "Module", sc.modulename);
			error_intl
			(
				scp,
		i18n("file \"$filename\" does not exist for module \"$module\"")
			);
			sub_context_delete(scp);
		}
		return;
	}
	if (quickcomp(sc.sourcename, inputname, 1L))
	{
		if (fc.verbosity > VERBOSE_DEFAULT)
		{
			sub_context_ty	*scp;

			scp = sub_context_new();
			sub_var_set_charstar(scp, "File_Name", inputname);
			sub_var_set_charstar(scp, "Module", sc.modulename);
			error_intl
			(
				scp,
i18n("file \"$filename\" differs from latest edit of module \"$module\"")
			);
		}
		return;
	}
	if (unlink(inputname))
	{
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_errno_set(scp);
		sub_var_set_charstar(scp, "File_Name", inputname);
		fatal_intl(scp, i18n("unlink \"$filename\": $errno"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}
	if (fc.verbosity)
	{
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_var_set_charstar(scp, "File_Name", inputname);
		error_intl
		(
			scp,
			i18n("deleted up to date file \"$filename\"")
		);
	}
}


/*
 * Check to see if a file is up to date, and report it if not.
 */
void
checkhistory(char *inputname)
{
	FILE	*fp;

	fp = openhistoryfile(OHF_READ);
	fclose_and_check(fp, sc.historyname);
	if (access(inputname, 0) && (errno == ENOENT))
	{
		if (fc.verbosity > VERBOSE_DEFAULT)
		{
			sub_context_ty	*scp;

			scp = sub_context_new();
			sub_var_set_charstar(scp, "File_Name", inputname);
			sub_var_set_charstar(scp, "Module", sc.modulename);
			error_intl
			(
				scp,
		i18n("file \"$filename\" does not exist for module \"$module\"")
			);
			sub_context_delete(scp);
		}
		return;
	}
	if (quickcomp(sc.sourcename, inputname, 1L))
	{
		if (fc.quickflag)
			printf("%s\n", sc.modulename);
		else
		{
			sub_context_ty	*scp;
			string_ty	*s;

			scp = sub_context_new();
			sub_var_set_charstar(scp, "File_Name", inputname);
			sub_var_set_charstar(scp, "Module", sc.modulename);
			s =
				subst_intl
				(
					scp,
       i18n("file \"$filename\" differs from latest edit of module \"$module\"")
				);
			sub_context_delete(scp);
			printf("%s\n", s->str_text);
			str_free(s);
		}
		return;
	}
	if (fc.verbosity > VERBOSE_DEFAULT)
		error_raw("[File \"%s\" is up to date]", inputname);
}


/*
 * Compare a file to the newest version of a module, but output nothing.
 * Whether or not there are any differences is returned in the variables
 * inserts and deletes.  The edit list is built so that it can be
 * immediately used to perform an update.
 */
void
comphistory(char *inputname)
{
	FILE	*fp;

	fp = openhistoryfile(OHF_READ);
	fclose_and_check(fp, sc.historyname);
	fc.maxchanges = INFINITY;
	fc.fileAmodify = sc.modifylines;
	fc.fileBmodify = sc.modifylines;
	fc.fileAskip = 1;
	if (fc.verbosity > VERBOSE_DEFAULT)
		error_raw("[Comparing files \"%s\" and \"%s\"]",
			sc.sourcename, inputname);
	fcomp(sc.sourcename, inputname);
}
