#define __USE_SYSBASE

#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.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 <string.h>
#include <stdlib.h>

#define HASHSIZE 16381

#define hashnumber(x) (x % HASHSIZE)

#define addfreelist(x)	if(x){((freenode *)x)->next = freelist->next; \
						freelist->next = (freenode *)x;}


static UBYTE *version_string = "$VER: DupeFinder 1.2 (2.6.96)";

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

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

enum {
	OK,
	SKIP,
	EXIT
};

#define FIRST 1
#define LAST  2

typedef struct _no {
	ULONG size;
	struct _no *next;
	char *buf;
	char *path;
	BOOL listed;
	char file[1];
} node;

typedef struct _fn {
	char buf[512];
	struct _fn *next;
} freenode;

typedef struct _pn {
	char *name;
	struct _pn *next;
} parentnode;

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 *);

char *exall(node **hash, struct ExecBase *SysBase, struct DosLibrary *DOSBase, char *path, LONG *arg_array, void *pool);
__inline int compare(int flags, node *this, node *other, BOOL names, freenode *freelist, struct DosLibrary *DOSBase, struct ExecBase *SysBase, void *pool);
void insert_node(node *t, node *n, node **list, int *files);
int hashstring(char *s);
void printnamesident(BOOL *firstmatch, node *this, node *other, struct DosLibrary *DOSBase);

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);
	char **trav;
	void *pool;
	LONG oldpri;
	node *list, *trlist;
	node **hash;
	int i, status, flags;
	freenode freelist[1];

	if(!DOSBase) return(20L);

	if(!(pool = AsmCreatePool(MEMF_ANY|MEMF_CLEAR, 4096, 2048, SysBase)))
		goto quit;
	
	memset(arg_array, 0, sizeof(LONG)*NUM_ARGS);
	if(!(rdargs = ReadArgs(template, arg_array, NULL))){
		PutStr("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]);

	if(!(hash = AsmAllocPooled(pool, HASHSIZE*sizeof(node *), SysBase)))
		goto quit;

	for(trav = (char **)arg_array[PATH];*trav;trav++){
		char *fail;

		if(fail = exall(hash, SysBase, DOSBase, *trav, arg_array, pool)){
			PutStr(fail);
			goto quit;
		}
	}

/*	for(i=0; i<HASHSIZE; i++){
		for(list = hash[i]; list; list = list->next)
			Printf("En her\n");
	}
*/
	freelist->next = NULL;
	for(i=0; i<HASHSIZE; i++){
		for(list = hash[i]; list && list->next; list = list->next){
			flags = FIRST;

			if(SetSignal(0L,SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C){
				PutStr("***Break\n");
				goto quit;
			}
			if(list->listed){
				addfreelist(list->buf);
				continue;
			}
			for(trlist = list->next; trlist; trlist = trlist->next){
				if(!trlist->next) flags |= LAST;
				status = compare(flags, list, trlist, arg_array[NAMES], freelist, DOSBase, SysBase, pool);
				switch(status){
				case SKIP: break;
				case EXIT: goto quit;
				}
				flags = 0;
			}
			addfreelist(list->buf);
		}
		if(list)
			addfreelist(list->buf);
	}

quit:
	if(arg_array[PRI])
		SetTaskPri(FindTask(0), oldpri);
	if(rdargs)
		FreeArgs(rdargs);
	AsmDeletePool(pool, SysBase);
	CloseLibrary((struct Library *)DOSBase);
	return(0L);
}



char *exall(node **hash, struct ExecBase *SysBase, struct DosLibrary *DOSBase, char *path, LONG *arg_array, void *pool)
{
	BPTR lock;
	__aligned struct FileInfoBlock fib[1];
	node *new;
	int hashno;
	char *fail=NULL;
	int len;
	char *newpath;

	if(!(lock = Lock(path, ACCESS_READ))){
		return("Can't lock directory.\n");
	}

	if(!Examine(lock, fib)) { UnLock(lock); return("Can't examine"); }
	if(fib->fib_DirEntryType != ST_USERDIR) { UnLock(lock); return("Not a directory"); }

	for(;ExNext(lock, fib) && !fail;){
		if(fib->fib_DirEntryType == ST_FILE){
			len = strlen(fib->fib_FileName);
			if(arg_array[NOICONS] && len>=5
			   && !stricmp(&(fib->fib_FileName)[len-5], ".info"))
				continue;
			if(arg_array[MINSIZE] && (fib->fib_Size < *(LONG *)arg_array[MINSIZE]))
				continue;
			if(!(new = AsmAllocPooled(pool, sizeof(node)+len, SysBase))){
				UnLock(lock);
				return("Out of memory");
			}
			if((new->size=fib->fib_Size)==0)
				continue;
			memcpy(new->file, fib->fib_FileName, len+1);
			new->path = path;
			if(arg_array[NAMES]) hashno = hashstring(fib->fib_FileName);
			else hashno = hashnumber(new->size);
			new->next = hash[hashno];
			hash[hashno] = new;
		}
		else if(fib->fib_DirEntryType == ST_USERDIR){
			len = strlen(path) + strlen(fib->fib_FileName) +2;
			if(len>500)  // Do not go further into the tree here
				continue;
			if(!(newpath = AsmAllocPooled(pool, len, SysBase))){
				UnLock(lock);
				return("Out of memory");
			}
			strcpy(newpath, path);
			AddPart(newpath, fib->fib_FileName, 255);
			fail = exall(hash, SysBase, DOSBase, newpath, arg_array, pool);
		}
		if(SetSignal(0L,SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
			fail = "***Break\n";
	}

	UnLock(lock);
	return(fail);
}


#define ASIZE 512
#define BSIZE 0x40000 // 4^x

__inline int compare(int flags, node *this, node *other, BOOL names, freenode *freelist,
                      struct DosLibrary *DOSBase, struct ExecBase *SysBase, void *pool)
{
	static char *other_buffer, *this_buffer=NULL;
//	char other_string[256], this_string[256];
	int actual = this->size;
	static BPTR fh_this=NULL;
	BPTR fh_other=NULL;
	static BOOL firstmatch=TRUE, inbuffer;
	int rsize=0x4000;
	int status=OK;
	static char pathbuf[512];

	if(flags&FIRST){
		inbuffer=FALSE;
		if(!firstmatch) PutStr("\n");
		firstmatch = TRUE;
	}
	if(names && (stricmp(this->file, other->file)))
		goto quit;

	if(this->size != other->size){
		if(names){
			printnamesident(&firstmatch, this, other, DOSBase);
			goto quit;
		}
		goto quit;
	}

	if(!this_buffer){
		if(!(this_buffer = AsmAllocPooled(pool, BSIZE, SysBase))
			|| !(other_buffer = AsmAllocPooled(pool, BSIZE, SysBase))){
			PutStr("Out of memory.\n");
			this_buffer=NULL;
			status = EXIT;
			goto quit;
		}
	}

	if(!this->buf){
		strcpy(pathbuf, this->path);
		AddPart(pathbuf, this->file, 511);
		if(fh_this = Open(pathbuf, MODE_OLDFILE)){
			if(freelist->next){
				this->buf = freelist->next->buf;
				freelist->next = freelist->next->next;
			}
			else if(!(this->buf = AsmAllocPooled(pool, sizeof(freenode), SysBase))){
				PutStr("Out of memory\n");
				status = EXIT;
				goto quit;
			}
			if(Read(fh_this, this->buf, 512)==-1){
				status = SKIP;
				goto quit;
			}
		} else {
			status = SKIP;
			goto quit;
		}
	}

	if(!other->buf){
		strcpy(pathbuf, other->path);
		AddPart(pathbuf, other->file, 511);
		if(fh_other = Open(pathbuf, MODE_OLDFILE)){
			if(freelist->next){
				other->buf = freelist->next->buf;
				freelist->next = freelist->next->next;
			}
			else if(!(other->buf = AsmAllocPooled(pool, sizeof(freenode), SysBase))){
				PutStr("Out of memory\n");
				status = EXIT;
				goto quit;
			}
			if(Read(fh_other, other->buf, 512)==-1)
				goto quit;
		} else goto quit;
	}

	if(memcmp(this->buf, other->buf, (actual<512) ? actual : 512))
		goto quit;

	actual-=512;

	if(actual<=0){
		if(!names){
			if(firstmatch){
				Printf("\"%s/%s\"  ;identical to\n\"%s/%s\"\n",
					   this->path, this->file, other->path, other->file);
				firstmatch = FALSE;
			}
			else Printf("\"%s/%s\"\n", other->path, other->file);
			other->listed = TRUE;
		}
		goto quit;
	}
	strcpy(pathbuf, this->path);
	AddPart(pathbuf, this->file, 511);
	if(!fh_this && !(fh_this = Open(pathbuf, MODE_OLDFILE))){
		Printf("Can't open \"%s\".\n", this->file);
		status = SKIP;
		goto quit;
	}
	Seek(fh_this, 512, OFFSET_BEGINNING);

	if(!fh_other){
		strcpy(pathbuf, other->path);
		AddPart(pathbuf, other->file, 511);
		if(!(fh_other = Open(pathbuf, MODE_OLDFILE)))
			goto quit;
		Seek(fh_other, 512, OFFSET_BEGINNING);
	}
	
	while(actual>0){
		if(Read(fh_this, this_buffer, rsize)!=-1){
			if(Read(fh_other, other_buffer, rsize)!=-1){
				if(memcmp(this_buffer, other_buffer, (actual<rsize) ? actual : rsize)){
					if(names){
						printnamesident(&firstmatch, this, other, DOSBase);
						goto quit;
					}
					goto  quit;
				}
			} else {
				Printf("Error reading from \"%s\" (error: %ld)\n", other->file, IoErr());
				goto quit;
			}
		} else {
			Printf("Error reading from \"%s\" (error: %ld)\n", this->file, IoErr());
			status = SKIP;
			goto quit;
		}
		actual-=rsize;
		if(rsize < BSIZE)
			rsize *= 4;
	}
	if(!names){
		if(firstmatch==TRUE){
			Printf("\"%s/%s\"  ;identical to\n\"%s/%s\"\n",
				   this->path, this->file, other->path, other->file);
			firstmatch = FALSE;
		}
		else Printf("\"%s/%s\"\n", other->path, other->file);
		other->listed = TRUE;
	}
quit:
	if((flags&LAST) && fh_this) {Close(fh_this); fh_this=NULL;}
	if(fh_other) Close(fh_other);
	return(status);
}


int hashstring(char *s)
{
	int i;

	for(i = 0; *s; ++s){
		*s |= 32;
		i = (64*i + *s) % HASHSIZE;
	}
	return(i);
}


void printnamesident(BOOL *firstmatch, node *this, node *other, struct DosLibrary *DOSBase)
{
	if(*firstmatch){
		Printf("\"%s/%s\"  ;not identical to, but has same file name as"
			   "\n\"%s/%s\"\n", this->path, this->file,
			   other->path, other->file);
		(*firstmatch) = FALSE;
	}
	else Printf("\"%s\"\n", other->file);
	other->listed = TRUE;
}
