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

#include <common/ac/errno.h>
#include <common/ac/stdio.h>
#include <common/ac/stdlib.h>
#include <common/ac/string.h>
#include <libexplain/malloc.h>
#include <libexplain/program_name.h>
#include <libexplain/strdup.h>

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


enum
{
    arglex_token_binary = arglex_token_MAX1,
    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,
    arglex_token_MAX2
};

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)
{
    const char      *progname;

    progname = explain_program_name_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);
}


/*
 * 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(const char *dir_name, const char *filename)
{
    char            *cp;
    const char      *endname;

    endname = strrchr(filename, '/');
    if (!endname)
        endname = filename;
    cp = explain_malloc_or_die(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;
    const char      *nameA;
    const char      *nameB;
    const 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;
}


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