#define __USE_SYSBASE

#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/exall.h>
#include <libraries/utgui.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utgui_protos.h>
#include <clib/alib_protos.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/utgui_pragmas.h>
#include <string.h>
#include <stdlib.h>

#define BUFSIZE 2000

static UBYTE *version_string = "$VER: DupFinder 1.0 (24.4.95)";

UBYTE *template = "PATH/A/M,NAMES/S,MINSIZE=MIN/N,NOICONS=NOINFO/S,PRI/N";

enum {
	PATH,
	NAMES,
	MINSIZE,
	NOICONS,
	PRI,
	NUM_ARGS
};

typedef struct l {
	BPTR dir;
	struct ExAllControl *eac;
	struct ExAllData *entry;
	struct l *next; 
} list;

extern void * __asm AsmCreatePool(register __d0 ULONG, register __d1 ULONG, register __d2 ULONG, register __a6 struct ExecBase *);
extern void __asm AsmDeletePool(register __a0 void *, register __a6 struct ExecBase *);
extern void * __asm AsmAllocPooled(register __a0 void *, register __d0 ULONG, register __a6 struct ExecBase *);
extern void __asm AsmFreePooled(register __a0 void *, register __a1 void *, register __d0 ULONG, register __a6 struct ExecBase *);

UWORD exall(struct ExecBase *SysBase, struct DosLibrary *DOSBase, list **myold, char *path, ULONG *numfiles, void *pool);
__inline BOOL compare(BOOL first, BPTR thisdir, struct ExAllData *this, BPTR otherdir, struct ExAllData *other, LONG *arg_array, struct DosLibrary *DOSBase, struct ExecBase *SysBase, void *pool);

int __saveds main(void)
{
	struct ExecBase *SysBase = *(struct ExecBase **)4L;
	struct RDArgs *rdargs=NULL;
	LONG arg_array[NUM_ARGS];
	struct DosLibrary *DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 37L);
	struct Library *UTGuiBase = OpenLibrary("utgui.library", 1L);
	struct ExAllData *this, *other;
	list *mylist, *mynew, *myold;
	char **trav; //, this_string[256], other_string[256];
	void *pool;
	UWORD ret;
	APTR	progbar = NULL;
	ULONG	files = 0L, filenum;
	LONG oldpri=256;
	BOOL first;

	if(!DOSBase) return(20L);

	if(!(pool = AsmCreatePool(MEMF_ANY, 10240, 5120, SysBase)))
		goto quit;
	
	if(!(mylist = myold = AsmAllocPooled(pool, sizeof(list), SysBase))) goto quit;
	mylist->next = NULL;
	mylist->entry = NULL;
	memset(arg_array, 0, sizeof(LONG)*NUM_ARGS);
	if(!(rdargs = ReadArgs(template, arg_array, NULL))){
		Printf("Usage: dupfinder <path1> [<path2> ...] [names] [minsize=<bytes>]\n"
				 "                 [noicons] [pri=<priority>]\n");
		goto quit;
	}

	if(arg_array[PRI])
		oldpri = SetTaskPri(FindTask(0), *(LONG *)arg_array[PRI]);

	for(trav = (char **)arg_array[PATH];*trav;trav++){
		if(ret = exall(SysBase, DOSBase, &myold, *trav, &files, pool)){
			if(ret != 2)
				Printf("ExAll() failed\n");
			goto quit;
		}
	}

	if(UTGuiBase)
		progbar = ugOpenProgressBar("DupFinder", PB_ProgressText, "Comparing files...", PB_Total, files, PB_InactiveWin, TRUE, TAG_DONE);

	for(mynew = mylist->next, filenum = 0; mynew; mynew = mynew->next){
		for(this = mynew->entry; this; this=this->ed_Next, ++filenum){
			first = TRUE;
			if(progbar)
				ugUpdateProgressBar(progbar, PB_Current, filenum, TAG_DONE);
			if(this->ed_Next) other = this->ed_Next;
			else if(mynew->next) other = (mynew->next)->entry;
			else break;
			myold = mynew;
			for(;; other->ed_Next ? (other = other->ed_Next)
				 : (myold->next ? (other = (myold = myold->next)->entry)
				 : (other = NULL))){
				if(!other) break;
				if(SetSignal(0L,SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C){
					Printf("***Break\n");
					goto quit;
				}
				if(compare(first, mynew->dir, this, myold->dir, other, arg_array, DOSBase,
					SysBase, pool)) first = FALSE;
			}
			if(!first) Printf("\n");
		}
	}

quit:
	if(progbar)
		ugCloseProgressBar(progbar);

	for(myold = mynew = mylist; myold; myold=mynew){
		if(mynew) mynew=mynew->next;
		if(myold != mylist && myold->eac){
			FreeDosObject(DOS_EXALLCONTROL, myold->eac);
			UnLock(myold->dir);
		}
	}
	AsmDeletePool(pool, SysBase);
	if(oldpri!=256) SetTaskPri(FindTask(0), oldpri);
	if(rdargs) FreeArgs(rdargs);
	CloseLibrary(UTGuiBase);
	CloseLibrary((struct Library *)DOSBase);
	return(0L);
}


UWORD exall(struct ExecBase *SysBase, struct DosLibrary *DOSBase, list **myold, char *path, ULONG *numfiles, void *pool)
{
	list *mynew;
	int more;
	char *buffer, *nfl_buf;
	struct ExAllData *entry;
	BPTR mylock;
	struct ExAllControl *eac;
	BOOL first=TRUE, broken=FALSE;
	UWORD ret;

	if(!(mylock = Lock(path, ACCESS_READ))){
		Printf("Can't lock \"%s\".\n", path);
		return(1);
	}

	if(!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL))){
		UnLock(mylock);
		return(1);
	}
	eac->eac_LastKey = 0;
	do {
		if(SetSignal(0L,SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C){
			if(DOSBase->dl_lib.lib_Version >= 39){
				Printf("***Break\n");
				UnLock(mylock);
				FreeDosObject(DOS_EXALLCONTROL, eac);
				return(2);
			}
			else broken = TRUE;
		}
		if(buffer = AsmAllocPooled(pool, BUFSIZE, SysBase))
			more = ExAll(mylock, (struct ExAllData *)buffer, BUFSIZE, ED_SIZE, eac);
		if(!buffer || (!more && (IoErr() !=ERROR_NO_MORE_ENTRIES))){
			FreeDosObject(DOS_EXALLCONTROL, eac);
			UnLock(mylock);
			return(1);  // Something seriously wrong.
		}
		if(eac->eac_Entries==0){
			FreeDosObject(DOS_EXALLCONTROL, eac);
			UnLock(mylock);
			return(0);
		}
		*numfiles += eac->eac_Entries;

		if(!(mynew = AsmAllocPooled(pool, sizeof(list), SysBase))){
			FreeDosObject(DOS_EXALLCONTROL, eac);
			UnLock(mylock);
			return(1);
		}
		memset(mynew, 0, sizeof(list));
		(*myold)->next = mynew;
		if(first) mynew->eac = eac;
		first = FALSE;
		mynew->entry = (struct ExAllData *) buffer;
		mynew->dir = mylock;
		*myold = mynew;

		if(!broken && (nfl_buf = AsmAllocPooled(pool, 256, SysBase))){
			for(entry = mynew->entry; entry; entry=entry->ed_Next)
			if(entry->ed_Type == ST_USERDIR){
				NameFromLock(mylock, nfl_buf, 255);
				AddPart(nfl_buf, entry->ed_Name, 255);
				if(ret = exall(SysBase, DOSBase, myold, nfl_buf, numfiles, pool)){
					if(ret != 2)
						Printf("Couldn't check \"%s\"\n", nfl_buf);
					AsmFreePooled(pool, nfl_buf, 256, SysBase);
					return(ret);
				}
			}
			AsmFreePooled(pool, nfl_buf, 256, SysBase);
		}
	} while(more);
	if(broken){
		Printf("***Break\n");
		return(2);
	}
	return(0);
}

#define BSIZE 32768

__inline BOOL compare(BOOL first, BPTR thisdir, struct ExAllData *this, BPTR otherdir,
                      struct ExAllData *other, LONG *arg_array,
                      struct DosLibrary *DOSBase, struct ExecBase *SysBase, void *pool)
{
	char other_string[256], this_string[256];
	static char *other_buffer = NULL, *this_buffer = NULL;
	int actual = this->ed_Size, bsize = 512;
	BPTR fh_this, fh_other;

	// Compare files
	if(this->ed_Type!=ST_FILE
		|| !this->ed_Size
		|| (!arg_array[NAMES] && this->ed_Size != other->ed_Size)
		|| (arg_array[MINSIZE] ? (this->ed_Size < *(LONG *)arg_array[MINSIZE]) : FALSE))
		return(FALSE);

	if(this->ed_Size != other->ed_Size){
		if(!stricmp(this->ed_Name, other->ed_Name)){
			NameFromLock(thisdir, this_string, 255);
			AddPart(this_string, this->ed_Name, 255);
			NameFromLock(otherdir, other_string, 255);
			AddPart(other_string, other->ed_Name, 255);
			if(!strcmp(this_string, other_string)) return(FALSE);
			if(first) Printf("\"%s\"  ;identical to\n\"%s\" ;name only\n",
								  this_string, other_string);
			else Printf("\"%s\" ;name only\n", other_string);
			return(TRUE);
		}
		return(FALSE);
	}

	NameFromLock(thisdir, this_string, 255);
	AddPart(this_string, this->ed_Name, 255);
	NameFromLock(otherdir, other_string, 255);
	AddPart(other_string, other->ed_Name, 255);

	if(!strcmp(this_string, other_string)) return(FALSE);

	if(arg_array[NOICONS] && (strlen(this_string)>5
		&& !stricmp(&this_string[strlen(this_string)-5], ".info")))
		return(FALSE);

	if(!(fh_this = Open(this_string, MODE_OLDFILE))){
		Printf("Can't open \"%s\".\n", this_string);
		return(FALSE);
	}
	if(!(fh_other = Open(other_string, MODE_OLDFILE))){
		Printf("Can't open \"%s\".\n", other_string);
		Close(fh_this);
		return(FALSE);
	}

	if(!this_buffer)
		if(!(this_buffer = AsmAllocPooled(pool, BSIZE, SysBase))){
			PutStr("Out of memory.\n");
			return(FALSE);
		}
	if(!other_buffer)
		if(!(other_buffer = AsmAllocPooled(pool, BSIZE, SysBase))){
			PutStr("Out of memory.\n");
			return(FALSE);
		}

	while(actual>0){
		if(Read(fh_this, this_buffer, bsize)!=-1){
			if(Read(fh_other, other_buffer, bsize)!=-1){
				if(memcmp(this_buffer, other_buffer, (actual<bsize) ? actual : bsize)){
					Close(fh_other);
					Close(fh_this);
					if(arg_array[NAMES] && !strcmp(this->ed_Name, other->ed_Name)){
						if(first) Printf("\"%s\"  ;identical to\n\"%s\" ;name only\n",
											  this_string, other_string);
						else Printf("\"%s\" ;name only\n", other_string);
						return(TRUE);
					}
					return(FALSE);
				}
			} else {
				Printf("Error reading from \"%s\" (error: %ld)\n", other_string, IoErr());
				Close(fh_other);
				Close(fh_this);
				return(FALSE);
			}
		} else {
			Printf("Error reading from \"%s\" (error: %ld)\n", this_string, IoErr());
			Close(fh_other);
			Close(fh_this);
			return(FALSE);
		}
		actual-=bsize;
		bsize = BSIZE;
	}
	if(first) Printf("\"%s\"  ;identical to\n\"%s\"\n", this_string, other_string);
	else Printf("\"%s\"\n", other_string);
	other->ed_Type = ~ST_FILE;
	Close(fh_other);
	Close(fh_this);
	return(TRUE);
}
