/*  DIFF - File Comparator  */

/*
Copyright (c) 1983 by Kevin B. Kenny

     This medium and the information contained herein are confidential to
and the property of Kevin B. Kenny (the author) and are not to be disclosed
to any third party without the written permission of the author or his
designate.  The author also claims copyright to the material as an
"unregistered unpublished work" under USC 13, Section 105 of the United
States Copyright Act of 1976.

*/

#include <stdio.h>
#include <ctype.h>
#include <dos.h>
#include "cmdutil.h"

/*  Command line options    */

int dispmode;               /* Display mode */
#define DM_NORM 0           /* Normal display */
#define DM_ALTER 1          /* Display alterations only */
#define DM_MATCH 2          /* Display matching lines */
#define MAXLINE 135

int resync;             /* Resynchronization count */
char *header;           /* Header for display */
int blanksf;            /* Flag = 1 if blanks s/b compressed */
int single;             /* Flag = 1 to ignore case differences */
char * file1;           /* Name of first file */
char * file2;           /* Name of second file */

/*  Input file status   */

FILE * file [2];            /* IObuf's of input files */

struct file_line {
    struct file_line *next, *prev;     /* Chain linkage */
    int line_no;                       /* Line number in file */
    char * line_text;                  /* Text of the line */
    };

struct file_line * first_line [2];  /* First line from file in memory */
struct file_line * last_line [2];   /* Last line from file in memory */
struct file_line * cur_line [2];    /* Current line in memory */
int linenumb [2];                   /* Last line read from each file */
int eofhit [2];                     /* EOF read in from file */

/*  Status of resynchronizer    */

int equal;              /* Flag to indicate equality */
int match;              /* Flag to indicate match reached */
int left;               /* File being read backwards */
int right;              /* FIle being read forwards */
int loffset;            /* Number of mismatches in "left" */
int roffset;            /* Number of mismatches in "right" */
int difflen [2];        /* Number of mismatches by file */
int hold;               /* Working word for pointer switches */

/*  Procedures to be defined below */

extern char *curline ();

main (argc, argv)
  int argc;
  char ** argv;
  {

    fprintf(stderr, "K**2 DIFF MS-DOS version 85-05-09\n");
    fprintf (stderr, "copyright (c) 1983, 1984, 1985 by Kevin Kenny.\n");
    getopts(argc, argv);
    setup();
    rundiff();
    }

/*  Read an process the options from the command line   */

static struct option optable [8] = {    /* Option descriptions for procarg */
#define FILE_OPT -1
        "Alterations", NAKED_KWD,
#define ALTER_OPT 0         /* Flag to show just changed text */
        "Matching", NAKED_KWD,
#define MATCH_OPT 1         /* Flag to show just matching text */
        "Resynchronize", NVAL_KWD,
#define RESYN_OPT 2         /* Line count for resynchronization */
        "Header", SVAL_KWD,
#define HEAD_OPT 3          /* Header for output lines */
        "Blanks", NAKED_KWD,
#define BLANK_OPT 4         /* Ignore multiple blanks */
        "Singlecase", NAKED_KWD,
#define SINGLE_OPT 5
        EOF};

getopts (argc, argv)
  int argc;
  char ** argv;
  {

    int info;       /* Information item returned from procarg */
    int error;      /* Error flag */

    dispmode = DM_NORM;     /* Set default options */
    resync = 3;
    header = "-------------------------------------------------- ";
    blanksf = FALSE;
    single = FALSE;
    file1 = NULL;
    file2 = NULL;

    ++argv; --argc;     /* Discard command name from argv */

    error = FALSE;      /* No error has occurred yet */

    while (argc) {      /* Process one command option */
        switch(procarg (&argc, &argv, optable, &info)) {
            case ALTER_OPT:
                dispmode = DM_ALTER; break;
            case MATCH_OPT:
                dispmode = DM_MATCH; break;
            case RESYN_OPT:
                resync = info; break;
            case HEAD_OPT:
                header = info; break;
            case BLANK_OPT:
                blanksf = TRUE; break;
            case SINGLE_OPT:
                single = TRUE; break;
            case FILE_OPT:
                if (file1 == NULL) file1 = info;
                else if (file2 == NULL) file2 = info;
                else error = TRUE;
                break;
            default:
                error = TRUE;
            }
        }

    if (file2 == NULL || error) {
        showsyntax("diff <file1> <file2>", optable);
        exit();
        }
    }

/*  Set up the DIFF process     */

setup () {

    if ((file [0] = fopen(file1, "r")) == NULL) {
        fprintf (stderr, "%s: can't open file.\n", file1);
        exit();
        }
    if ((file [1] = fopen(file2, "r")) == NULL) {
        fprintf(stderr, "%s: can't open file.\n", file2);
        exit();
        }
    first_line[0] = first_line[1] =
    last_line[0] = last_line[1] =
    cur_line[0] = cur_line[1] =       NULL;
    linenumb[0] = linenumb[1] = 0;
    eofhit[0] = eofhit[1] = FALSE;
    equal = TRUE;                   /* Initially, files are assumed equal */
    fflush(stderr);
    }

/*  Do the DIFF process     */

rundiff () {
    while (!ateof(0) || !ateof(1)) {  /* Run until end of files */
        domatch();         /* Handle matching lines */
        if (ateof(0) && ateof(1)) break;
        equal = FALSE;      /* We've found a difference */
        synch();           /* Get us back in sync */
        dodiff();          /* Handle changed lines */
        }
    if (equal) fprintf(stderr,"\nThe files are equal\n");
    }

/*  Do matching lines       */

domatch () {
    if (!atmatch()) return;        /* Handle non-match at start */
    
    if (dispmode == DM_MATCH)       /* Displaying matches? */
        printf("%s%d, %d\n", header, lineno (0), lineno (1));

    while (atmatch() && !ateof(0)) {
        if (dispmode == DM_MATCH)   /* Display matching lines */
            printf("%s", curline (0));
        advance(0); advance(1);   /* Bump past matching lines */
        discard(0); discard(1);   /* Throw away matched lines */
        }
    }

/*  Resynchronize comparison when mismatched lines found    */

synch () {
    left = 0;                   /* Move pointer leftward from
                                   current position in first file */
    right = 1;                  /* Move pointer rightward in second
                                   file. */
    loffset = roffset = 0;      /* Both pointers at current line */

    do {
        ++roffset; advance(right);              /* Move pointer rightward */
        if (--loffset >= 0) backup (left);      /* Back leftward pointer up */
        else {                           /* Pointer reached divergence point */
            chkabrt();
            hold = right; right = left; left = hold;     /* Swap input files */
            loffset = roffset; roffset = 0;              /* Switch pointers */
            }

        match = TRUE;                      /* Innocent until proven guilty */
        for (hold = 0; hold < resync; ++hold) {
            if (!atmatch()) {              /* Test if at least "resync" */
                match = FALSE;             /* consecutive lines match   */
                break;
                }
            advance(0); advance(1);       /* Advance over matched lines*/
            }
        while (hold) {
            backup(0); backup(1);         /* Back up over matched lines*/
            --hold;
            }
        } while (!match);                /* Repeat synch until match found */
    difflen [left] = loffset;
    difflen [right] = roffset;
    while (loffset) {
        backup(left); --loffset;         /* Back up over unmatched data */
        }
    while (roffset) {
        backup(right); --roffset;
        }
    }

/*  Process differences */

dodiff () {

    if (dispmode != DM_MATCH) printf("%s     %d", header, lineno (0));
    if (dispmode == DM_ALTER && difflen[0] != 0)
        printf(", %d", lineno (0) + difflen [0] - 1);
    if (dispmode != DM_MATCH) printf("\n");

    for (hold = 0; hold < difflen[0]; ++hold) {
        if (dispmode == DM_NORM)
            printf("%s", curline (0));
        advance(0);
        }

    if (dispmode == DM_NORM) printf("%s%d\n", header, lineno (1));

    for (hold = 0; hold < difflen [1]; ++hold) {
        if (dispmode != DM_MATCH)
            printf("%s", curline (1));
        advance(1);
        }
    }

/*  Test if a file is at EOF    */

int ateof (f)
  int f;
  {
    prepbuf(f);
    return(cur_line[f]->line_text == EOF);
    }

/*  Return the current line of a file   */

char * curline (f)
  int f;
  {

    prepbuf(f);
    return(cur_line[f]->line_text);
    }

/*  Return the current line number within file */

int lineno (f)
  int f;
  {

    prepbuf(f);
    return(cur_line[f]->line_no);
    }

/*  Test if the current lines of the two files match    */

int atmatch () {
    char *s1, *s2;

    prepbuf(0); prepbuf(1);
    if (ateof(0) && ateof(1)) return (TRUE);
    if (ateof(0) || ateof(1)) return (FALSE);

    s1 = curline(0); s2 = curline(1);
    while (*s1 == *s2
      ||  (blanksf && isspace(*s1) && isspace(*s2))
      ||  (single  && tolower(*s1) == tolower(*s2))) {
        if (*s1 == 0) return (TRUE);
        if (blanksf && isspace(*s1)) {
            while (isspace(*++s1));
            while (isspace(*++s2));
            }
        else {
            ++s1; ++s2;
            }
        }
    return(FALSE);
    }

/*  Advance to the next line of a file  */

advance (f) 
  int f;
  {

    cur_line[f] = cur_line[f]->next;
    prepbuf(f);
    }

/*  Back up to the previous line in a file */

backup (f)
  int f;
  {

    if ((cur_line[f] = cur_line[f]->prev) == NULL) {
        fprintf(stderr, "\nin backup: can't happen");
        exit();
        }
    }

/*  Discard all lines in memory up to the present point */

discard (f)
  int f;
  {

    while (first_line[f] != cur_line[f]) {
        if (first_line[f]->line_text != EOF) free (first_line[f]->line_text);
        hold = first_line[f]->next;
        free(first_line[f]);
        first_line[f] = hold;
        }
    if (first_line[f] == NULL) {
        last_line[f] = NULL;
        }
    }

/*  Bring the current line to memory if it isn't there  */

prepbuf (f)
  int f;
  {

    struct file_line *newl;
    char *txt;
    char text[MAXLINE];

    if (cur_line[f] != NULL) return;
    newl = malloc(sizeof(*newl));
    if (!newl) punt();
    if (!eofhit[f] && (fgets(text, MAXLINE, file [f]) != NULL)) {
        txt = malloc(strlen(text) + 1);
        if (!txt) punt();
        strcpy(txt, text);
        newl->line_text = txt;
        }
    else {
        newl->line_text = EOF;
        eofhit[f] = TRUE;
        }
    newl->line_no = ++linenumb[f];
    if (last_line[f] != NULL) {
        last_line[f]->next = newl;
        }
    else {
        first_line[f] = newl;
        }

    newl->prev = last_line[f];
    newl->next = NULL;
    last_line[f] = cur_line[f] = newl;
    return;

    }

punt () {

    fprintf(stderr, "Files have too many differences to compare.");
    exit();
    }

chkabrt () {
    struct reg r;

    r.r_ax = CONDIO; r.r_dx = 0xFF;
    intcall(&r, &r, DOSINT);
    if (r.r_ax & 0xFF == 0x03) {
        printf(stderr, "\nDIFF aborted by operator\n");
        exit();
        }
    }
