
#import "FtpDirectory.h"
#import "FtpBrowserCell.h"
#import "SortedList.h"
#import "FtpSession.h"
#import "FtpConnection.h"
#import "NXmystd.h"

#define UPDATETIMEOUT (60*60*24) // 1 day
//#define UPDATETIMEOUT (60*60*3) // 3 hours
//#define UPDATETIMEOUT 30

#define FORCEDUPDATETIMEOUT (60*60*24*14) // 2 weeks

@implementation FtpDirectory

- initForName:(const char *)name withParent:aParent;
{
	id dummyFile;
	
	[super initForName:name withParent:aParent];
	if (parent)
	{
		NXAtom parentpath = [parent path];
		char *buf;
		buf = calloc(strlen(parentpath)+strlen(myname)+50,sizeof(char));
		if (strcmp(parentpath,"/")==0)
			parentpath = "";
		sprintf(buf,"%s/%s",parentpath,name);
		mypath = NXUniqueString(buf);
		free(buf);
	}
	else
	{
		mypath = NXUniqueString("/");
	}
	list = [[SortedList alloc] init];
	updateTmpList = [[SortedList alloc] init];
	isBeingUpdated = FALSE;
	dummyFile = [[FtpFile alloc] initForName:"not loaded" withParent:self];
	[dummyFile setDummy];
	[dummyFile setNew:NO];
	[list addObject:dummyFile];
	needsDisplay = YES;
	needsUpdate = YES;
	firstUpdate = YES;
	firstUpdateDisplay = NO;
	neededUpdate = [self needsUpdate]+2*[self needsCheck];
	statusString = NXUniqueString("");
	shouldTransfer = NO;
	return self;
}

- free;  // should test for isBeingUpdated...
{
	[list freeObjects];
	list = [list free];
	updateTmpList = [updateTmpList free];
	return [super free];
}

- write:(NXTypedStream *)stream;
{
	id saveList = isBeingUpdated?cacheHelpList:list;
	[FtpDirectory setVersion:0];
	[super write:stream];
	if ([Object version] != 4)
	{
		NXWriteTypes(stream, "@cc%", &saveList, &firstUpdate, &firstUpdateDisplay, &statusString);
	}
	else
	{
		// for default cache (version 4)
		NXWriteTypes(stream, "@cc", &saveList, &firstUpdate, &firstUpdateDisplay);
	}
	return self;
}

- read:(NXTypedStream *)stream;
{
	[super read:stream];
	
	if ([Object version] >= 5)
	{
		NXReadTypes(stream, "@cc%", &list, &firstUpdate, &firstUpdateDisplay, &statusString);
		mypath = NULL;
	}
	else if ([Object version] >= 4)
	{
		NXReadTypes(stream, "@cc", &list, &firstUpdate, &firstUpdateDisplay);
		statusString = NXUniqueString("");
		mypath = NULL;
	}
	else if ([Object version] >= 2)
	{
		NXReadTypes(stream, "%@cc%", &mypath, &list, &firstUpdate, &firstUpdateDisplay, &statusString);
	}
	else
	{
		NXReadTypes(stream, "%@cc", &mypath, &list, &firstUpdate, &firstUpdateDisplay);
		statusString = NXUniqueString("");
		mypath = NULL;
	}
	
	if ([Object version] < 3)
	{
		timestamp = 0;
	}
	return self;
}

- awake;
{
	[super awake];
	neededUpdate = 0;
	isBeingUpdated = NO;
	needsUpdate = NO;
	needsDisplay = YES;
	updateTmpList = [[SortedList alloc] init];
	cacheHelpList = nil;
	shouldTransfer = 0;
	[self setPathAfterAwake];
	return self;
}

- setPathAfterAwake;
{
	if (!mypath)
	{
		if (parent)
		{
			NXAtom parentpath = [parent path];
			char *buf;
			
			if (!parentpath)
			{
				[parent setPathAfterAwake];
				parentpath = [parent path];
				if (!parentpath)
				{
					NXBeep();
					ERRORF("ERROR: parentpath == NULL");
					parentpath = NXUniqueString("/");
				}
			}
			//LOGF("name = %s,parentpath = %s",myname,parentpath);
			buf = calloc(strlen(parentpath)+strlen(myname)+50,sizeof(char));
			if (strcmp(parentpath,"/")==0)
				parentpath = "";
			sprintf(buf,"%s/%s",parentpath,myname);
			mypath = NXUniqueString(buf);
			free(buf);
			//LOGF("name = %s,parentpath = %s,mypath = %s",myname,parentpath,mypath);
		}
		else
		{
			mypath = NXUniqueString("/");
			//LOGF("Im the root");
		}
	}
	return self;
}



- getObjWithName:(char *)s
{
	NXAtom name = NXUniqueString(s);
	int i;
	id obj;
	
	for (i=0;i<[list count];i++)
	{
		obj = [list objectAt:i];
		if (name == [obj name])
			return obj;
	}
	return nil;
}

- getDirWithName:(char *)s;
{
	id obj = [self getObjWithName:s];
	
	if (obj && [obj isDir])
		return obj;
	else
		return nil;
}

- getFileWithName:(char *)s;
{
	id obj = [self getObjWithName:s];
	
	if (obj && [obj isFile])
		return obj;
	else
		return nil;
}


- (int) fillMatrix:matrix;
{
	int i,numcells;
	//int rows,cols;
	id cell,obj;
	static List *selList = NULL;
	static NXStringTable *strings = NULL;

	if (!selList || strings)
	{
		selList = [[List alloc] init];
		strings = [[NXStringTable alloc] init];
	}
	//LOGF("start %s",mypath);
	needsDisplay = FALSE;
	
	[selList empty];
	[strings empty];
	[matrix getSelectedCells:selList];
	for (i=0;i<[selList count];i++)
	{
		[strings insertKey:NXUniqueString([[selList objectAt:i] stringValue]) value:"faslebla"];
		//LOGF("added name %s",[[selList objectAt:i] stringValue]);
	}

	//[[matrix cellList] makeObjectsPerform:@selector(setFile:) with:nil];
	[matrix selectCellAt:-1:-1];
	[matrix clearSelectedCell];
	[matrix renewRows:0 cols:1];
	//LOGF("mitte %s",mypath);
	for (i=0;i<[list count];i++)
	{
		obj = [list objectAt:i];
		cell = [matrix cellAt:i:0];
		//printf("cell = %p,i=%d\n",cell,i);
		if ( ! cell )
		{
			[matrix insertRowAt:i];
			cell = [matrix cellAt:i:0];
			//printf("new cell = %p,i=%d\n",cell,i);
		}
		[cell setStringValue:[obj name]];
		[cell setFileSize:[obj fileSize]];
		[cell setLeaf:[obj isFile]];
		[cell setEnabled: ! [obj isDummy]];
		[cell setLoaded:YES];
		[cell setNew:[obj isNew]];
		[cell setFile:obj];
		//[cell setState:[obj isSelected]];
		if ([strings isKey:[cell stringValue]])
		{
			//LOGF("selecting name %s",[cell stringValue]);
			//[matrix setState:1 at:i:0];
			//[matrix selectCellAt:i:0]; // selectCellAt:: REMOVES prev. selections !
			[matrix setSelectionFrom:i to:i anchor:i lit:YES]; // and this one doesnt (isn't it great !!!) !!
		}
		//else
		//	[matrix setState:0 at:i:0];

		[cell setParentNeedsUpdate:[self needsCheck]&&![obj isDummy]];
		if ([obj isDir])
		{
			[cell setNeedsCheck:[obj needsCheck]];
			[cell setNeedsUpdate:[obj needsUpdate]];
			[cell setNeedsFirstUpdate:[obj needsFirstUpdate]];
		}
	}
	numcells = i;
	if (firstUpdateDisplay)
	{
		needsDisplay = YES;
		firstUpdateDisplay = NO;
	}
	#ifdef DEBUG
	[matrix getSelectedCells:[selList empty]];
	for (i=0;i<[selList count];i++)
	{
		//LOGF("selected: %s",[[selList objectAt:i] stringValue]);
	}
	#endif
	[matrix sizeToCells];
	[matrix display];
	//LOGF("ende %s",mypath);
	return numcells;
}

- addSubDirsToCheckTo:con;
{
	int i;
	for (i=0;i<[list count];i++)
	{
		id dir = [list objectAt:i];
		if ([dir isDir])
		{
			if ([dir needsCheck] && ![dir needsFirstUpdate])
			{
				[con addToQueue:dir];
				//LOGF("dir needsCheck:%s",[dir name]);
			}
		}
	}
	return self;
}

- beginUpdate;
{
	//printf("dir:%s beginUpdate\n",mypath);
	isBeingUpdated = YES;
	cacheHelpList = [list copy];
	[updateTmpList free];
	updateTmpList = [list copy];
	return self;
}

- update:(char *)s withSize:(long)size isFile:(BOOL)isFile;
{
	id obj;
	id class;
	
	//printf("dir: |%s|\n",s);
	obj = [self getObjWithName:s];
	
	if (!isBeingUpdated)
		return nil;
	if ( obj && [obj isFile] == isFile ) // new obj is already there
	{
		[obj setNew:NO];
		if ([obj fileSize] != size)
		{
			if ([obj fileSize] != -1)
				[obj setNew:YES];
			[obj setFileSize:size];
		}
		[updateTmpList removeObject:obj];
		return obj;
	}
	else if (obj)	// file <--> dir
	{
		[list removeObject:obj];
		[updateTmpList removeObject:obj];
		[cacheHelpList removeObject:obj];
		obj = [obj free];
	}
	
	if (isFile)
		class = [FtpFile class];
	else
		class = [FtpDirectory class];
	
	obj = [[class alloc] initForName:NXUniqueString(s) withParent:self];
	if (firstUpdate)
		[obj setNew:NO];
	[obj setFileSize:size];
	return [self add:obj];
}

- endUpdate;
{
	int i;
	
	//printf("dir:%s endUpdate\n",mypath);
	timestamp = time(0);
	shouldTransfer = 0;
	needsUpdate = NO;
	if (!isBeingUpdated)
		return self;
	
	if (firstUpdate)
	{
		firstUpdate = NO;
		firstUpdateDisplay = YES;
	}
	needsDisplay = YES;
	[parent setNeedsDisplay:YES];
	for (i=0;i < [updateTmpList count];i++)
	{
		[list removeObject:[updateTmpList objectAt:i]];
	}
	//[updateTmpList freeObjects];
	isBeingUpdated = NO;
	cacheHelpList = [cacheHelpList free];
	return self;
}


- add:obj;
{
	if ( [obj isFile] || [obj isDir] )
	{
		timestamp++;
		[list addObjectSorted:obj];
		return obj;
	}
	else
		return nil;
}

- remove:obj;
{
	if ( [obj isFile] || [obj isDir] )
	{
		if ([list removeObject:obj])
			return self;
		else
			return nil;
	}
	else
		return nil;
}



- (BOOL) needsCheck;
{
	if (difftime (time(0), timestamp) > UPDATETIMEOUT)
		return YES;
	else
		return NO;
}

- (BOOL) needsUpdate;
{
	return needsUpdate|firstUpdate|(difftime (time(0), timestamp) > FORCEDUPDATETIMEOUT);
}

- setNeedsNoUpdate;
{
	timestamp = time(0);
	return self;
}

- setNeedsUpdate;
{
	needsUpdate = YES;
	return self;
}

- (BOOL) needsFirstUpdate;
{
	return firstUpdate;
}

- (const char *) path;
{
	//printf("FtpDirectory:|%s| path=|%s|\n",myname,mypath);
 	return mypath;
}

- setStatusString:(const char *)s;
{
	//LOGF("%s:\nstatus:|%s|",mypath,s);
	statusString = NXUniqueString(s);
	return self;
}

- (BOOL)compareStatusString:(const char *)s;
{
	LOGF("%s:\noldStatus:|%s|\nnewStatus:|%s|\nisEqual: %s",mypath,statusString,s,(NXUniqueString(s) == statusString)?"YES":"NO");
	return (NXUniqueString(s) == statusString)?TRUE:FALSE;
}

- setShouldTransferIfNeeded;
{
	if (shouldTransfer < 1)
		shouldTransfer = 1;
	return self;
}

- setShouldTransferAlways;
{
	if (shouldTransfer < 2)
		shouldTransfer = 2;
	return self;
}

- (BOOL)shouldTransferIfNeeded;
{
	return shouldTransfer > 0;
}

- (BOOL)shouldTransferAlways;
{
	return shouldTransfer > 1;
}


- (BOOL) isFile;
{
	return NO;
}

- (BOOL) isDir;
{
	return YES;
}

- (BOOL)isDummy;
{
	return NO;
}

- (BOOL) needsDisplay;
{
	BOOL flag = NO;
	if (neededUpdate != [self needsUpdate]+2*[self needsCheck])
		flag = YES;
	neededUpdate = [self needsUpdate]+2*[self needsCheck];
	flag|=needsDisplay;
	needsDisplay = NO;
	return flag;
}

-  setNeedsDisplay:(BOOL)flag;
{
	needsDisplay = flag;
	return self;
}

// by Peter Lount
- fullPath:(char*)theFullPathPtr;
{
	sprintf(theFullPathPtr,
		"%s",[self path]
	);
	return self;
}


@end
