/*
 *	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: operating system entry point, and parse arguments
 *
 * fcomp - a file comparison program
 */

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

#include <arglex.h>
#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <fileio.h>
#include <help.h>
#include <isdir.h>
#include <progname.h>
#include <quit.h>
#include <sub.h>
#include <version.h>


enum
{
	arglex_token_binary,
	arglex_token_binary_not,
	arglex_token_blank,
	arglex_token_context,
	arglex_token_debug,
	arglex_token_edit,
	arglex_token_failures,
	arglex_token_join,
	arglex_token_line_numbers,
	arglex_token_matching,
	arglex_token_output,
	arglex_token_quiet,
	arglex_token_spaces,
	arglex_token_upcase,
	arglex_token_verbose,
	arglex_token_what,
};

static arglex_table_ty argtab[] =
{
	{ "-BINary",	arglex_token_binary,		},
	{ "-No_BINary",	arglex_token_binary_not,	},
	{ "-Blank",	arglex_token_blank,		},
	{ "-Context",	arglex_token_context,		},
	{ "-Debug",	arglex_token_debug,		},
	{ "-Edit",	arglex_token_edit,		},
	{ "-Failures",	arglex_token_failures,		},
	{ "-Join",	arglex_token_join,		},
	{ "-Number",	arglex_token_line_numbers,	},
	{ "-Matching",	arglex_token_matching,		},
	{ "-Output",	arglex_token_output,		},
	{ "-Quiet",	arglex_token_quiet,		},
	{ "-Spaces",	arglex_token_spaces,		},
	{ "-Upcase",	arglex_token_upcase,		},
	{ "-Verbose",	arglex_token_verbose,		},
	{ "-What",	arglex_token_what,		},
	{ 0, 0, }, /* end marker */
};


/*
 * Type out the possible commands.
 */

static void
usage(void)
{
	char		*progname;

	progname = progname_get();
	fprintf(stderr, "usage: %s [ <option>... ] <fileA> <fileB>\n", progname);
	fprintf(stderr, "       %s -Help\n", progname);
	fprintf(stderr, "       %s -VERSion\n", progname);
	quit(1);
}


/*
 * give the user some help
 */

static void
main_help(void)
{
	help(NULL, usage);
}


/*
 * Expand a global variable name.
 * Returns original argument if string is not a global variable,
 * or returns copied value of global variable.
 */

static char *
expand(char *cp)
{
	char	*var;		/* variable value */

	if (!cp || *cp != '.')
		return cp;
	var = getenv(cp);
	if (!var || !*var)
		return cp;
	cp = r_alloc_and_check(strlen(var) + 1);
	strcpy(cp, var);
	return cp;
}


/*
 * Make a directory name into a file name by applying the last
 * component of the second name to it.  For example:
 *	dirfile("/etc", "/foo/bar")
 * results in "/etc/bar".
 */

static char *
dirfile(char *dir_name, char *filename)
{
	char	*cp;
	char	*endname;

	endname = strrchr(filename, '/');
	if (!endname)
		endname = filename;
	cp = r_alloc_and_check(strlen(dir_name) + strlen(endname) + 2);
	strcpy(cp, dir_name);
	strcat(cp, "/");
	strcat(cp, endname);
	return cp;
}


/*
 * This is the main program.
 * This performs all of the parsing of the command line, and then
 * calls a subroutine to do all the work.  This allows the subroutine
 * to be loaded with other programs and called by them easily.
 */

int
main(int argc, char **argv)
{
	char		*cp;
	char		*nameA;
	char		*nameB;
	char		*ofile;
	int		dirA;
	int		dirB;
	sub_context_ty	*scp;

	arglex_init(argc, argv, argtab);
	switch (arglex())
	{
	case arglex_token_help:
		main_help();
		quit(0);

	case arglex_token_version:
		version();
		quit(0);

	default:
		break;
	}
	nameA = NULL;
	nameB = NULL;
	ofile = NULL;
	fc.verbosity = VERBOSE_DEFAULT;
	fc.maxchanges = INFINITY;
	while (arglex_token != arglex_token_eoln)
	{
		switch (arglex_token)
		{
		default:
			bad_argument(usage);
			/* NOTREACHED */

		case arglex_token_string:
		 	/* a file name */
			cp = expand(arglex_value.alv_string);
			if (!nameA)
				nameA = cp;
			else if (!nameB)
				nameB = cp;
			else
			{
				fatal_intl
				(
					0,
					i18n("only two input files allowed")
				);
			}
			break;

		case arglex_token_binary:
			/* compare binary bytes */
			if (fc.hexify_flag)
				goto duplicate;
			fc.hexify_flag = 1;
			break;

		case arglex_token_binary_not:
			/* ignore binary files */
			if (fc.cant_do_binary)
				goto duplicate;
			fc.cant_do_binary = 1;
			break;

		case arglex_token_blank:
			/* ignore blank lines */
			if (fc.blankflag)
			{
				duplicate:
				scp = sub_context_new();
				sub_var_set_charstar(scp, "Name", arglex_value.alv_string);
				fatal_intl
				(
					scp,
				     i18n("duplicate $name command line option")
				);
				/* NOTREACHED */
				sub_context_delete(scp);
			}
			fc.blankflag = 1;
			break;

		case arglex_token_debug:
			/* type debugging information */
			if (fc.debugflag)
				goto duplicate;
			fc.debugflag = 1;
			break;

		case arglex_token_edit:
			/* output an edit script */
			if (fc.editscriptflag)
				goto duplicate;
			fc.editscriptflag = 1;
			break;

		case arglex_token_matching:
			/* show lines which match */
			if (fc.matchflag)
				goto duplicate;
			fc.matchflag = 1;
			break;

		case arglex_token_line_numbers:
			/* show line numbers */
			if (fc.numflag)
				goto duplicate;
			fc.numflag = 1;
			break;

		case arglex_token_quiet:
			/* display quick summary */
			if (fc.quickflag)
				goto duplicate;
			fc.quickflag = 1;
			break;

		case arglex_token_spaces:
			/* ignore extra spaces or tabs */
			if (fc.spaceflag)
				goto duplicate;
			fc.spaceflag = 1;
			break;

		case arglex_token_upcase:
			/* translate to upper case */
			if (fc.upcaseflag)
				goto duplicate;
			fc.upcaseflag = 1;
			break;

		case arglex_token_verbose:
			/* be verbose */
			if (fc.verbosity != VERBOSE_DEFAULT)
				goto duplicate;
			fc.verbosity = VERBOSE_FULL;
			break;

		case arglex_token_what:
			/* show what changes are */
			if (fc.whatflag)
				goto duplicate;
			fc.whatflag = 1;
			break;

		case arglex_token_context:
			if (fc.context)
				goto duplicate;
			if (arglex() != arglex_token_number)
			{
				scp = sub_context_new();
				sub_var_set_charstar(scp, "Name", "-Context");
				fatal_intl
				(
					scp,
				 i18n("numeric value required for $name option")
				);
				/* NOTREACHED */
				sub_context_delete(scp);
			}
			fc.context = arglex_value.alv_number;
			if (fc.context < 1)
			{
				scp = sub_context_new();
				sub_var_set_format(scp, "Number", "%d", fc.context);
				fatal_intl
				(
					scp,
				      i18n("context of $number is out of range")
				);
				/* NOTREACHED */
				sub_context_delete(scp);
			}
			break;

		case arglex_token_failures:
			if (fc.maxchanges != INFINITY)
				goto duplicate;
			if (arglex() != arglex_token_number)
			{
				scp = sub_context_new();
				sub_var_set_charstar(scp, "Name", "-Failures");
				fatal_intl
				(
					scp,
				 i18n("numeric value required for $name option")
				);
			}
			fc.maxchanges = arglex_value.alv_number;
			if (fc.maxchanges < 0)
			{
				scp = sub_context_new();
				sub_var_set_format(scp, "Number", "%d", fc.maxchanges);
				fatal_intl
				(
					scp,
			      i18n("maximum changes of $number is out of range")
				);
				/* NOTREACHED */
				sub_context_delete(scp);
			}
			break;

		case arglex_token_join:
			if (fc.maxjoin)
				goto duplicate;
			if (arglex() != arglex_token_number)
			{
				scp = sub_context_new();
				sub_var_set_charstar(scp, "Name", "-Join");
				fatal_intl
				(
					scp,
				 i18n("numeric value required for $name option")
				);
			}
			fc.maxjoin = arglex_value.alv_number;
			if (fc.maxjoin < 1)
			{
				scp = sub_context_new();
				sub_var_set_format(scp, "Number", "%d", fc.maxjoin);
				fatal_intl
				(
					scp,
					i18n("join of $number is out of range")
				);
				/* NOTREACHED */
				sub_context_delete(scp);
			}
			break;

		case arglex_token_output:
			/* specify output file */
			if (ofile)
				goto duplicate;
			switch (arglex())
			{
			default:
				fatal_intl(0, i18n("file name required after -Output option"));

			case arglex_token_string:
				ofile = expand(arglex_value.alv_string);
				break;

			case arglex_token_stdio:
				ofile = "";
				break;
			}
			break;
		}
		arglex();
	}
	if (nameA && !*nameA)
		nameA = 0;
	if (nameB && !*nameB)
		nameB = 0;
	if (!nameA || !nameB)
		error_intl(0, i18n("must have two input files"));
	if (ofile && !*ofile)
		ofile = 0;
	if (fc.matchflag + fc.whatflag + fc.editscriptflag >= 2)
		fatal_intl(0, i18n("too many output formats specified"));

	/*
	 * If just one argument is a directory, then modify it by using
	 * the other argument so as to reference a corresponding file
	 * within the directory.
	 */
	dirA = isdir(nameA);
	dirB = isdir(nameB);
	if (dirA && dirB)
	{
		scp = sub_context_new();
		sub_var_set_charstar(scp, "Name1", nameA);
		sub_var_set_charstar(scp, "Name2", nameB);
		fatal_intl
		(
			scp,
		i18n("arguments \"$name1\" and \"$name2\" are both directories")
		);
		/* NOTREACHED */
		sub_context_delete(scp);
	}
	if (dirA)
		nameA = dirfile(nameA, nameB);
	if (dirB)
		nameB = dirfile(nameB, nameA);

	/*
	 * compare the two files
	 */
	fcomp(nameA, nameB);

	/*
	 * report the difference
	 */
	if (fc.whatflag)
		dumpwhat(ofile);
	else if (fc.matchflag)
		dumpmatch(ofile);
	else if (fc.editscriptflag)
		dumplines(ofile);
	else
		dumpnormal(ofile);
	quit(0);
	return 0;
}
