/*
 *  COMPARE - Perform a byte-by-byte comparison of two files
 *  Copyright (C) 1993 Torsten Poulin
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  The author can be contacted by s-mail at
 *    Torsten Poulin
 *    Banebrinken 99, 2, 77
 *    DK-2400 Copenhagen NV
 *    DENMARK
 *
 * $Id: Compare.c,v 37.5 93/06/21 08:33:41 Torsten Rel $
 * $Log:	Compare.c,v $
 * Revision 37.5  93/06/21  08:33:41  Torsten
 * Now does its own buffering (32k per file) for increased throughput.
 * 
 * Revision 37.4  93/03/01  13:16:42  Torsten
 * Fixed potentially dangerous bug: Input() could be closed.
 * 
 * Revision 37.3  93/02/17  09:24:59  Torsten
 * Got rid of compiler warning caused by a forgotten '*' in
 * the cast used in MyPrintf(). Otherwise nothing is changed ;-)
 * 
 * Revision 37.2  93/02/11  23:05:38  Torsten
 * Removed unnecessary #include.
 * Forgot to test the quiet flag before printing
 * the 'initial subsequence' messages. Fixed that.
 * 
 * Revision 37.1  93/02/11  21:18:23  Torsten
 * This is the initial version.
 * 
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <clib/macros.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#ifdef __SASC
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#endif
#include "tastlib.h"
#include "compare_rev.h"

#define PROGNAME "Compare"
#define TEMPLATE "FILE1,FILE2,SKIP1/N,SKIP2/N,QUIET/S"
#define OPT_FILE1 0
#define OPT_FILE2 1
#define OPT_SKIP1 2
#define OPT_SKIP2 3
#define OPT_QUIET 4
#define BUFLEN 32768L

char const versionID[] = VERSTAG;
char const copyright[] = "$COPYRIGHT:Copyright © 1993 Torsten Poulin$";

typedef struct {
  struct DosLibrary *DOSBase;
  UBYTE buf1[BUFLEN];
  UBYTE buf2[BUFLEN];
} Global;

LONG Compare(BPTR, BPTR, BOOL, ULONG, ULONG, Global *);

LONG entrypoint(VOID) {
  struct DosLibrary *DOSBase;
  struct RDArgs *args;
  Global *global;
  LONG  arg[5];
  LONG  rc = RETURN_OK;
  BPTR  f1 = NULL, f2 = NULL;
  UBYTE *name1, *name2;
  ULONG skip1 = 0L, skip2 = 0L;

  if (!(DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 37L)))
    return RETURN_FAIL;

  if (!(global = AllocVec(sizeof(Global), MEMF_PUBLIC | MEMF_CLEAR)))
    rc = ERROR_NO_FREE_STORE;
  else {
    global->DOSBase = DOSBase;

    arg[OPT_FILE1] = arg[OPT_FILE2] = 0L;
    arg[OPT_SKIP1] = arg[OPT_SKIP2] = arg[OPT_QUIET] = 0L;
    
    if (!(args = ReadArgs(TEMPLATE, arg, NULL)))
      rc = RETURN_FAIL;
    else {
      name1 = (UBYTE *) arg[OPT_FILE1];
      name2 = (UBYTE *) arg[OPT_FILE2];
        
      if (!arg[OPT_FILE1])
	f1 = Input();
      else if (!(f1 = Open(name1, MODE_OLDFILE)))
	rc = RETURN_ERROR;
        
      if (!arg[OPT_FILE2]) {
	if (arg[OPT_FILE1])
	  f2 = Input();
	else {
	  /* Maybe I should print something more informative... */
	  SetIoErr(ERROR_REQUIRED_ARG_MISSING);
	  rc = RETURN_FAIL;
	}
      }
      else if (!(f2 = Open(name2, MODE_OLDFILE))) {
	if (arg[OPT_FILE1])
	  Close(f1);
	rc = RETURN_ERROR;
      }

      if (arg[OPT_SKIP1])
	skip1 = *(LONG *) arg[OPT_SKIP1];

      if (arg[OPT_SKIP2])
	skip2 = *(LONG *) arg[OPT_SKIP2];

      if (f1 && f2) {
	rc = Compare(f1, f2, (BOOL) arg[OPT_QUIET], skip1, skip2, global);
	if (arg[OPT_FILE1])
	  Close(f1);
	if (arg[OPT_FILE2])
	  Close(f2);
      }
      FreeArgs(args);
    }
    FreeVec(global);
  }
  rc = printErrorMsg(rc, PROGNAME, DOSBase);
  CloseLibrary((struct Library *) DOSBase);
  return rc;
}


LONG Compare(BPTR f1, BPTR f2, BOOL quiet, ULONG skip1, ULONG skip2,
	     Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  UBYTE *bufp1, *bufend1, *bufp2, *bufend2;
  LONG  rlen1 = 0L, rlen2 = 0L;
  LONG c1, c2;
  ULONG lines = 1L, offset = 0L;

  bufp1 = bufend1 = global->buf1;
  bufp2 = bufend2 = global->buf2;

  while (skip1 -= rlen1) {
    if ((rlen1 = Read(f1, bufp1, MIN(skip1, BUFLEN))) <= 0) {
      if (rlen1 == 0)
	PutStr("Offset beyond end of FILE1\n");
      return RETURN_ERROR;
    }
    if (CheckSignal(SIGBREAKF_CTRL_C))
      return ERROR_BREAK;
  }

  while (skip2 -= rlen2) {
    if ((rlen2 = Read(f2, bufp2, MIN(skip2, BUFLEN))) <= 0) {
      if (rlen2 == 0)
	PutStr("Offset beyond end of FILE2\n");
      return RETURN_ERROR;
    }
    if (CheckSignal(SIGBREAKF_CTRL_C))
      return ERROR_BREAK;
  }

  for(;;) {
    if (bufp1 >= bufend1) {
      bufp1 = global->buf1;
      if ((rlen1 = Read(f1, bufp1, BUFLEN)) > 0)
	bufend1 = global->buf1 + rlen1;
      if (CheckSignal(SIGBREAKF_CTRL_C))
	return ERROR_BREAK;
    }
    c1 = *bufp1++;

    if (bufp2 >= bufend2) {
      bufp2 = global->buf2;
      if ((rlen2 = Read(f2, bufp2, BUFLEN)) > 0)
	bufend2 = global->buf2 + rlen2;
      if (CheckSignal(SIGBREAKF_CTRL_C))
	return ERROR_BREAK;
    }
    c2 = *bufp2++;

    if (rlen1 <= 0 || rlen2 <= 0)
      break;			/* error or EOF */

    offset++;

    if (c1 != c2) {
      if (!quiet) {
	MyPrintf(global,
		 "Files differ at offset %lu (hex 0x%lx), line %ld\n",
		 offset, offset, lines);
      }
      SetIoErr(0L);
      return RETURN_WARN;
    }

    if (c1 == '\n')
      lines++;
  }

  if (rlen1 < 0 || rlen2 < 0)
    return RETURN_ERROR;	/* An error occured while reading */

  if (rlen1 == 0 && rlen2 != 0) {
    if (!quiet)
      PutStr("FILE1 is an initial subsequence of FILE2\n");
    return RETURN_WARN;
  }

  if (rlen1 != 0 && rlen2 == 0) {
    if (!quiet)
      PutStr("FILE2 is an initial subsequence of FILE1\n");
    return RETURN_WARN;
  }
  return RETURN_OK;
}
