/* fcom.c - file compare
 *
 *  4/30/86  daniel lipkie  LineCompare, at end, if NOT Same then error
 *  5/01/86  daniel lipkie  SYM files treaded as binary
 *                           if (quiet mode) then exit(1) on FIRST difference
 *  27-May-1986 mz          Make line arrays dynamically allocated at read
 *                          time.
 *                          Make default size be 300 lines.
 *  05-Aug-1986 DANL        MAX default line size 255
 *  10-Feb-1987 mz          Make /m imply /t
 *  10-Jun-1987 danl        fill -> fillbuf
 *  09-Nov-1987 mz          Fix 0D0D0A bug
 *                          Fix different at line 2 bug
 *  25-Nov-1987 mz          All errors to stderr
 *  30-Nov-1987 mz          Resync fails dumps entire file
 *  21-Jul-1988 bw          Don't GP fault on empty test files
 *  06-Aug-1990 davegi      Changed Move to memmove (OS/2 2.0)
 *
 *  Fcom compares two files in either a line-by-line mode or in a strict
 *  byte-by-byte mode.
 *
 *  The byte-by-byte mode is simple; merely read both files and print the
 *  offsets where they differ and the contents.
 *
 *  The line compare mode attempts to isolate differences in ranges of lines.
 *  Two buffers of lines are read and compared.  No hashing of lines needs
 *  to be done; hashing only speedily tells you when things are different,
 *  not the same.  Most files run through this are expected to be largely
 *  the same.  Thus, hashing buys nothing.
 *
 *  [0]     Fill buffers
 *  [1]     If both buffers are empty then
 *  [1.1]       Done
 *  [2]     Adjust buffers so 1st differing lines are at top.
 *  [3]     If buffers are empty then
 *  [3.1]       Goto [0]
 *
 *  This is the difficult part.  We assume that there is a sequence of inserts,
 *  deletes and replacements that will bring the buffers back into alignment.
 *
 *  [4]     xd = yd = FALSE
 *  [5]     xc = yc = 1
 *  [6]     xp = yp = 1
 *  [7]     If buffer1[xc] and buffer2[yp] begin a "sync" range then
 *  [7.1]       Output lines 1 through xc-1 in buffer 1
 *  [7.2]       Output lines 1 through yp-1 in buffer 2
 *  [7.3]       Adjust buffer 1 so line xc is at beginning
 *  [7.4]       Adjust buffer 2 so line yp is at beginning
 *  [7.5]       Goto [0]
 *  [8]     If buffer1[xp] and buffer2[yc] begin a "sync" range then
 *  [8.1]       Output lines 1 through xp-1 in buffer 1
 *  [8.2]       Output lines 1 through yc-1 in buffer 2
 *  [8.3]       Adjust buffer 1 so line xp is at beginning
 *  [8.4]       Adjust buffer 2 so line yc is at beginning
 *  [8.5]       Goto [0]
 *  [9]     xp = xp + 1
 *  [10]    if xp > xc then
 *  [10.1]      xp = 1
 *  [10.2]      xc = xc + 1
 *  [10.3]      if xc > number of lines in buffer 1 then
 *  [10.4]          xc = number of lines
 *  [10.5]          xd = TRUE
 *  [11]    if yp > yc then
 *  [11.1]      yp = 1
 *  [11.2]      yc = yc + 1
 *  [11.3]      if yc > number of lines in buffer 2 then
 *  [11.4]          yc = number of lines
 *  [11.5]          yd = TRUE
 *  [12]    if not xd or not yd then
 *  [12.1]      goto [6]
 *
 *  At this point there is no possible match between the buffers.  For
 *  simplicity, we punt.
 *
 *  [13]    Display error message.
 *
 *  Certain flags may be set to modify the behavior of the comparison:
 *
 *  -a      abbreviated output.  Rather than displaying all of the modified
 *          ranges, just display the beginning, ... and the ending difference
 *  -b      compare the files in binary (or byte-by-byte) mode.  This mode is
 *          default on .EXE, .OBJ, .SYM, .LIB, .COM, .BIN, and .SYS files
 *  -c      ignore case on compare (cmp = strcmpi instead of strcmp)
 *  -l      compare files in line-by-line mode
 *  -lb n   set the size of the internal line buffer to n lines from default
 *          of 300
 *  -m      merge the input files into one for output.  Use extention to
 *          indicate what kind of separators to use.
 *  -n      output the line number also
 *  -NNNN   set the number of lines to resynchronize to NNNN which defaults
 *          to 2
 *  -w      ignore blank lines and white space (ignore len 0, use strcmps)
 *  -t      do not untabify (use fgets instead of fgetl)
 */

#include <malloc.h>

#include <stdio.h>
#include <string.h>
#include <process.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <windows.h>
#include <tools.h>


int ctSync  =   -1;                     /* number of lines required to sync  */
int cLine = -1;                         /* number of lines in internal buffs */
flagType fAbbrev = FALSE;               /* abbreviated output                */
flagType fBinary = FALSE;               /* binary comparison                 */
flagType fLine   = FALSE;               /* line comparison                   */
flagType fNumb   = FALSE;               /* display line numbers              */
flagType fCase   = TRUE;                /* case is significant               */
flagType fIgnore = FALSE;               /* ignore spaces and blank lines     */
flagType fQuiet  = FALSE;               /* TRUE => no messages output        */
flagType fMerge  = FALSE;               /* TRUE => merge files onto stdout   */

int debug;
#define D_COMP      0x0001
#define D_CCALL     0x0002
#define D_RESYNC    0x0004
#define D_CALL      0x0008
#define D_SYNC      0x0010

struct lineType {
    int     line;                       /* line number                       */
    char    *text;                      /* body of line                      */
};

struct lineType *buffer1, *buffer2;

/*
 * Forward Function Declarations
 */
void usage( char *, int );
int fillbuf( struct lineType *, FILE *, int, int * );
flagType compare( int, int, int, register int, register int);
int BinaryCompare( char *, char * );
void pline(struct lineType *);
void dump(struct lineType *, int, int);
int adjust (struct lineType *, int, int);
void LineCompare (char *, char *);

char * (__cdecl *funcRead) (char *, int, FILE *);
					/* function to use to read lines */

int (__cdecl *fCmp)(const char *, const char *);
					/* function to use to compare lines */

char line[MAXLINELEN];                  /* single line buffer                */

char *extBin[]  = { ".EXE", ".OBJ", ".SYM", ".LIB", ".COM", ".BIN",
                    ".SYS", NULL };


void
usage (p, erc)
char *p;
int erc;
{
    if (!fQuiet) {
        if (p)
            fprintf (stderr, "fcom: %s\n", p);
        else
            fprintf (stderr, "usage: fcom [/a] [/b] [/c] [/l] [/lb n] [/m] [/n] [/NNNN] [/w] [/t] file1 file2\n");
        }
    exit(erc);
}

/*  return number of lines read in
 *
 *  pl          line buffer to fill
 *  fh          handle to read
 *  ct          max number to read
 *  plnum       line number counter to use
 *
 *  returns     number of lines read
 */
int fillbuf( struct lineType *pl, FILE *fh, int ct, int *plnum )
{
    char *line;
    int i, l;

    if ((line = malloc (MAXLINELEN)) == NULL)
        usage ("out of memory", 2);

    if (TESTFLAG (debug, D_CALL))
        printf ("fillbuf  (%p, %p, %d)\n", pl, fh, ct);
    i = 0;
    while (ct-- && (*funcRead) (line, MAXLINELEN, fh) != NULL) {
        if (pl->text != NULL)
            free (pl->text);
        l = strlen (line);
        if ((pl->text = malloc (l+1)) == NULL)
            usage ("out of memory", 2);
// djg  Move ((char far *)line, (char far *) (pl->text), l+1);
        memmove ((char *) (pl->text), (char *)line, l+1);
	if (funcRead == fgets)
            pl->text[l-2] = 0;
        if (fIgnore && !strcmps (pl->text, ""))
            pl->text[0] = 0;
        if (l != 0 || !fIgnore) {
            pl->line = ++*plnum;
            pl++;
            i++;
            }
        }
    if (TESTFLAG (debug, D_CALL))
        printf ("fillbuf  returns %d\n", i);
    free (line);
    return i;
}

/*  compare a range of lines
 *
 *  l1, l2      number of lines in each line buffer
 *  s1, s2      beginning location in each buffer to begin compare
 *  ct          number of contiguous lines to compare
 */
flagType compare (l1, s1, l2, s2, ct)
int l1, l2, ct;
register int s1, s2;
{
    if (TESTFLAG (debug, D_CCALL))
        printf ("compare (%d, %d, %d, %d, %d)\n", l1, s1, l2, s2, ct);
    if (ct <= 0 || s1+ct > l1 || s2+ct > l2)
        return FALSE;
    while (ct--) {
        if (TESTFLAG (debug, D_COMP))
            printf ("'%s' == '%s'? ", buffer1[s1].text, buffer2[s2].text);
        if ((*fCmp)(buffer1[s1++].text, buffer2[s2++].text)) {
            if (TESTFLAG (debug, D_CCALL))
                printf ("No\n");
            return FALSE;
            }
        }
    if (TESTFLAG (debug, D_CCALL))
        printf ("Yes\n");
    return TRUE;
}



BinaryCompare (f1, f2)
char *f1, *f2;
{
    register int c1, c2;
    long pos;
    FILE *fh1, *fh2;
    flagType fSame;

    fSame = TRUE;
    if ((fh1 = fopen (f1, "rb")) == NULL) {
        sprintf (line, "cannot open %s - %s", f1, error ());
        usage (line, 2);
        }
    if ((fh2 = fopen (f2, "rb")) == NULL) {
        sprintf (line, "cannot open %s - %s", f2, error ());
        usage (line, 2);
        }
    pos = 0L;
    while (TRUE) {
        if ((c1 = getc (fh1)) != EOF)
            if ((c2 = getc (fh2)) != EOF)
                if (c1 == c2)
                    ;
                else {
                    fSame = FALSE;
                    if (fQuiet)
                        exit(1);
                    else
                        printf ("%08lx: %02x %02x\n", pos, c1, c2);
                    }
            else {
                sprintf (line, "%s longer than %s", f1, f2);
                usage (line, 1);
                }
        else
        if ((c2 = getc (fh2)) == EOF)
            if (fSame)
                usage ("no differences encountered", 0);
            else
                exit (1);
        else {
            sprintf (line, "%s longer than %s", f2, f1);
            usage (line, 1);
            }
        pos++;
        }
    return( 0 );
}

/* print out a single line */
void pline (pl)
struct lineType *pl;
{
    if (fNumb)
        printf ("%5d:  ", pl->line);
    printf ("%s\n", pl->text);
}

/* output a range of lines */
void dump( struct lineType *pl, int start, int end )
{
    if (fAbbrev && end-start > 2) {
        pline (pl+start);
        printf ("...\n");
        pline (pl+end);
        }
    else
        while (start <= end)
            pline (pl+start++);
}

/*  adjust returns number of lines in buffer
 *
 *  pl          line buffer to be adjusted
 *  ml          number of line in line buffer
 *  lt          number of lines to scroll
 */
adjust( struct lineType *pl, int ml, int lt)
{
    int i;

    if (TESTFLAG (debug, D_CALL))
        printf ("adjust (%p, %d, %d) = ", pl, ml, lt);
    if (TESTFLAG (debug, D_CALL))
        printf ("%d\n", ml-lt);
    if (ml <= lt)
        return 0;
    if (TESTFLAG (debug, D_CALL))
        printf ("move (%p, %p, %04x)\n", &pl[lt], &pl[0], sizeof (*pl)*(ml-lt));
    /*  buffer has 0..lt-1 lt..ml
     *  we free 0..lt-1
     */
    for (i = 0; i < lt; i++)
        if (pl[i].text != NULL)
            free (pl[i].text);
    /*  buffer is 0..0 lt..ml
     *  compact to lt..ml ???
     */
// djg  Move ((char far *)&pl[lt], (char far *)&pl[0], sizeof (*pl)*(ml-lt));
    memmove ((char *)&pl[0], (char *)&pl[lt], sizeof (*pl)*(ml-lt));
    /*  buffer is lt..ml ??
     *  fill to be lt..ml 0..0
     */
    for (i = ml-lt; i < ml; i++)
        pl[i].text = NULL;
    return ml-lt;
}


void LineCompare (f1, f2)
char *f1, *f2;
{
    FILE *fh1, *fh2;
    int l1, l2, i, xp, yp, xc, yc;
    flagType xd, yd, fSame, fFirstLineDifferent;
    int line1, line2;

    fFirstLineDifferent = TRUE;
    fSame = TRUE;
    if ((fh1 = fopen (f1, "rb")) == NULL) {
        sprintf (line, "cannot open %s - %s", f1, error ());
        usage (line, 2);
        }
    if ((fh2 = fopen (f2, "rb")) == NULL) {
        sprintf (line, "cannot open %s - %s", f2, error ());
        usage (line, 2);
        }
    if ((buffer1 = (struct lineType *)calloc (cLine, sizeof *buffer1)) == NULL ||
        (buffer2 = (struct lineType *)calloc (cLine, sizeof *buffer1)) == NULL)
        usage ("out of memory\n", 2);
    l1 = l2 = 0;
    line1 = line2 = 0;

l0: if (TESTFLAG (debug, D_SYNC))
        printf ("At scan beginning\n");
    if (fQuiet && !fSame)
        exit(1);
    l1 += fillbuf  (buffer1+l1, fh1, cLine-l1, &line1);
    l2 += fillbuf  (buffer2+l2, fh2, cLine-l2, &line2);
    if (l1 == 0 && l2 == 0) {
        if (fSame)
            usage ("no differences encountered", 0);
        else
            usage ("differences encountered", 1);
        }

    /*  find first line that differs in buffer
     */
    xc = min (l1, l2);
    for (i=0; i < xc; i++)
        if (!compare (l1, i, l2, i, 1))
            break;
    if (fMerge)
        dump (buffer2, 0, i-1);

    /*  If we are different at a place other than at the top then we know
     *  that there will be a matching line at the head of the buffer
     */
    if (i != 0)
        fFirstLineDifferent = FALSE;

    /*  if we found one at all, then adjust all buffers so last matching
     *  line is at top.  Note that if we are doing this for the first buffers
     *  worth in the file then the top lines WON'T MATCH
     */
    if (i != xc)
        i = max (i-1, 0);

    l1 = adjust (buffer1, l1, i);
    l2 = adjust (buffer2, l2, i);

    /*  if we've matched the entire buffers-worth then go back and fill some
     *  more
     */
    if (l1 == 0 && l2 == 0) {
        fFirstLineDifferent = FALSE;
        goto l0;
        }

    /*  Fill up the buffers as much as possible; the next match may occur
     *  AFTER the current set of buffers
     */
    l1 += fillbuf  (buffer1+l1, fh1, cLine-l1, &line1);
    l2 += fillbuf  (buffer2+l2, fh2, cLine-l2, &line2);
    if (TESTFLAG (debug, D_SYNC))
        printf ("buffers are adjusted, %d, %d remain\n", l1, l2);
    xd = yd = FALSE;
    xc = yc = 1;
    xp = yp = 1;

    /*  begin trying to match the buffers
     */
l6: if (TESTFLAG (debug, D_RESYNC))
        printf ("Trying resync %d,%d  %d,%d\n", xc, xp, yc, yp);
    i = min (l1-xc,l2-yp);
    i = min (i, ctSync);
    if (compare (l1, xc, l2, yp, i)) {
        fSame = FALSE;
        if (fMerge) {
            printf ("#if OLDVERSION\n");
            dump (buffer1, fFirstLineDifferent ? 0 : 1, xc-1);
            printf ("#else\n");
            dump (buffer2, fFirstLineDifferent ? 0 : 1, yp-1);
            printf ("#endif\n");
            }
        else
        if (!fQuiet) {
            printf ("***** %s\n", f1);
            dump (buffer1, 0, xc);
            printf ("***** %s\n", f2);
            dump (buffer2, 0, yp);
            printf ("*****\n\n");
            }
        if (TESTFLAG (debug, D_SYNC))
            printf ("Sync at %d,%d\n", xc, yp);
        l1 = adjust (buffer1, l1, xc);
        l2 = adjust (buffer2, l2, yp);
        fFirstLineDifferent = FALSE;
        goto l0;
        }
    i = min (l1-xp, l2-yc);
    i = min (i, ctSync);
    if (compare (l1, xp, l2, yc, i)) {
        fSame = FALSE;
        if (fMerge) {
            printf ("#if OLDVERSION\n");
            dump (buffer1, fFirstLineDifferent ? 0 : 1, xp-1);
            printf ("#else\n");
            dump (buffer2, fFirstLineDifferent ? 0 : 1, yc-1);
            printf ("#endif\n");
            }
        else
        if (!fQuiet) {
            printf ("***** %s\n", f1);
            dump (buffer1, 0, xp);
            printf ("***** %s\n", f2);
            dump (buffer2, 0, yc);
            printf ("*****\n\n");
            }
        if (TESTFLAG (debug, D_SYNC))
            printf ("Sync at %d,%d\n", xp, yc);
        l1 = adjust (buffer1, l1, xp);
        l2 = adjust (buffer2, l2, yc);
        fFirstLineDifferent = FALSE;
        goto l0;
        }
    if (++xp > xc) {
        xp = 1;
        if (++xc >= l1) {
            xc = l1;
            xd = TRUE;
            }
        }
    if (++yp > yc) {
        yp = 1;
        if (++yc >= l2) {
            yc = l1;
            yd = TRUE;
            }
        }
    if (!xd || !yd)
        goto l6;
    fSame = FALSE;
    if (fMerge) {
        if (l1 >= cLine || l2 >= cLine)
            fprintf (stderr, "resync failed.  Files are too different\n");
        printf ("#if OLDVERSION\n");
        do {
            dump (buffer1, 0, l1-1);
            l1 = adjust (buffer1, l1, l1);
        } while (l1 += fillbuf  (buffer1+l1, fh1, cLine-l1, &line1));
        printf ("#else\n");
        do {
            dump (buffer2, 0, l2-1);
            l2 = adjust (buffer2, l2, l2);
        } while (l2 += fillbuf  (buffer2+l2, fh2, cLine-l2, &line2));
        printf ("#endif\n");
        }
    else
    if (!fQuiet) {
        if (l1 >= cLine || l2 >= cLine)
            fprintf (stderr, "resync failed.  Files are too different\n");
        printf ("***** %s\n", f1);
        do {
            dump (buffer1, 0, l1-1);
            l1 = adjust (buffer1, l1, l1);
        } while (l1 += fillbuf  (buffer1+l1, fh1, cLine-l1, &line1));
        printf ("***** %s\n", f2);
        do {
            dump (buffer2, 0, l2-1);
            l2 = adjust (buffer2, l2, l2);
        } while (l2 += fillbuf  (buffer2+l2, fh2, cLine-l2, &line2));
        printf ("*****\n\n");
        }
    exit (1);
}

__cdecl main (c, v)
int c;
char *v[];
{
    int i;

    funcRead = fgetl;

    ConvertAppToOem( c, v );
    if (c == 1)
        usage (NULL, 2);
    SHIFT(c,v);
    while (fSwitChr (**v)) {
        if (!strcmp (*v+1, "a"))
            fAbbrev = TRUE;
        else
        if (!strcmp (*v+1, "b"))
            fBinary = TRUE;
        else
        if (!strcmp (*v+1, "c"))
            fCase = FALSE;
        else
        if (!strncmp (*v+1, "d", 1))
            debug = atoi (*v+2);
        else
        if (!strcmp (*v+1, "l"))
            fLine = TRUE;
        else
        if (!strcmp (*v+1, "lb")) {
            SHIFT(c,v);
            cLine = ntoi (*v, 10);
            }
        else
        if (!strcmp (*v+1, "m")) {
            fMerge = TRUE;
	    funcRead = fgets;
            }
        else
        if (!strcmp (*v+1, "n"))
            fNumb = TRUE;
        else
        if (!strcmp (*v+1, "q"))
            fQuiet = TRUE;
        else
        if (*strbskip (*v+1, "0123456789") == '\0')
            ctSync = ntoi (*v+1, 10);
        else
        if (!strcmp (*v+1, "t"))
	    funcRead = fgets;
        else
        if (!strcmp (*v+1, "w"))
            fIgnore = TRUE;
        else
            usage (NULL, 2);
        SHIFT(c,v);
        }
    if (c != 2)
        usage (NULL, 2);
    if (ctSync != -1)
        fLine = TRUE;
    else
        ctSync = 2;
    if (cLine == -1)
        cLine = 300;
    if (!fBinary && !fLine) {
        extention (v[0], line);
        for (i = 0; extBin[i]; i++)
            if (!_strcmpi (extBin[i], line))
                fBinary = TRUE;
        if (!fBinary)
            fLine = TRUE;
        }
    if (fBinary && (fLine || fNumb))
        usage ("incompatable switches", 2);
    if (fIgnore)
        if (fCase)
	    fCmp = strcmps;
        else
	    fCmp = strcmpis;
    else
    if (fCase)
	fCmp = strcmp;
    else
	fCmp = _strcmpi;
    if (fBinary)
        BinaryCompare (v[0], v[1]);
    else
        LineCompare (v[0], v[1]);
    return( 0 );
}