
//	Copyright H. Giesen, University of Koblenz-Landau 1996


#import "Controller.h"
#import "BibliographicFile.h"
#import "BrowserController.h"
#import "BibTeXObject.h"
#import "Preferences.h"
#import "MyBrowserCell.h"


static	char *types[] = 
	{	"bib", "bib~",
		NULL
	};


struct rusage stime, etime;
static	int	sMSec, eMSec;


// temporary stream
static NXStream	*localStream = NULL;

#define NEWLINE		fwrite( "\n", sizeof(char), 1, fp )


#define	OUTPUT(obj)	\
	{	fwrite( "\n", sizeof(char), 1, fp );\
		[obj copyToFile:fp];\
	}

#define	GET_BUFFER NXGetMemoryBuffer(memStream, &theBuffer, &length, &maxLength)
		
//	special entryNames
static	int	STRING = -1;
static	int	PREAMBLE = -1;
static	Preferences *preferences = nil;



@implementation BibliographicFile

	/*************	class methods	***********/

+ (BOOL) canLoadFromFile:(const char *)fName
{
	int	i;
	char *extension;

	// is fullName valid ??
	extension = rindex( fName, '.' );

	if( extension==0 )	return NO;

	extension++;
	for( i=0; types[i]; i++ )
		if( strcmp( extension, types[i] )==0 ) return YES;

	return NO;
}


+ (char **) fileTypes
{
	return types;
}

	/*************	end of class methods	***********/


- (void)alert:(const char *)txt :(const char *)para
{
	NXRunAlertPanel("Bibliography",
				txt,
				"  OK ",	// 1: default button
				NULL,		// 0: alternate
				NULL, 		//-1: other
				para
				);
}


- (BOOL)fileStatus
{
	struct	stat fBuf;
	int	rtn = stat(myName, &fBuf);

	if( rtn<0 ){
		[self alert:"cannot 'stat' the file\n%s" :strerror(errno)];
		return NO;
	}
	lastModified = fBuf.st_mtime;
	return YES;
}


- (int) compare:(locType)a with:(locType)b
{
	int	len = (a.length < b.length) ?
				a.length : b.length;
	int rtn = strncasecmp( &theBuffer[a.start], &theBuffer[b.start], len);
	
	if( rtn!=0 ) return rtn;
	return (a.length - b.length);
}



//	the following lists are supported
//	listOfObjects	contains all bibObjects 'sorted' be creation time
//	sortedListOfObjects	the bibObjects are sorted by key
//	listOfTypedObjects	bibObjects sorted by key, they are the same entryType

- (List *)listOfBibObjects
{
	return listOfObjects;
}


- (List *)listOfSortedBibObjects
{
	return sortedListOfObjects;
}


- (List *)listForType:(int)theType sorted:(BOOL)sort
{
	int	i;
	List	*theList;

	if( listOfTypedObjects==nil )
		listOfTypedObjects = [[List alloc] init];

	[listOfTypedObjects empty];

	if( entryCountList[theType]==0 ) return listOfTypedObjects;

	if( sort )	theList = sortedListOfObjects;
	else		theList = listOfObjects;
	for(i=0; i<[theList count]; i++ ){
		id	obj = [theList objectAt:i];

		if( [obj entryType]==theType ){
			[listOfTypedObjects addObject:obj];
			if( entryCountList[theType]==[listOfTypedObjects count] )
				return listOfTypedObjects;	// no further objects
		}
	}

	return listOfTypedObjects;
}



// inserts 'theObject' into the already sorted list 'sortedListOfObjects'
// returns the index of the insertion point
- (int)insertBibObject:(BibTeXObject*)theObject
{
	int	j = [sortedListOfObjects count] - 1;
	locType keyRange = [theObject key];

	GET_BUFFER;
	while( j>=0 ){	//	insertion sort
		locType keyRangeL = [[sortedListOfObjects objectAt:j] key];

		if( [self compare:keyRangeL with:keyRange] > 0 ) j--;
		else break;
	}
		
	[sortedListOfObjects insertObject:theObject at:j+1];
	[listOfObjects addObjectIfAbsent:theObject];

	return (j+1);
}


//	comb sort
- (void) combSort:(List *)aList
{
	int	gap, switches, top, i, j;
	int	numElements = [aList count];

	gap = numElements;
	do{
		gap = (int)( (float)gap/1.3 );
		switch( gap ){
			case 0 : gap = 1; break;
			case 9 :
			case 10: gap = 11; break;
			default: break;
		}
		switches = 0;	// no swaps
		top = numElements - gap;
		for( i=0; i<top; i++ ){
			id	hi, hj;
			locType a, b;

			j = i+gap;
			hi = [aList objectAt:i];	a = [hi key];
			hj = [aList objectAt:j];	b = [hj key];
			if( [self compare:a with:b] > 0 ) {
				switches++;
				// swap with neighbor
				[aList replaceObjectAt:i with:hj];
				[aList replaceObjectAt:j with:hi];
			}
		}
	} while( switches || gap>1 );
}


//	creates and parses a new bibObject
//	returns this object if successful else nil
- (BibTeXObject*)parseNewObject
{
	int	count;
	id	newObject = [[BibTeXObject alloc] initFromStream:memStream owner:self];

	if( report ){
		if( report==-2 ) return nil;	// abort
	}
	return newObject;

	if( newObject ){
		count = entryCountList[ [newObject entryType] ];
		entryCountList[ [newObject entryType] ]++;
		if( count==0 ){	// was empty
			[[[NXApp delegate] browserController]
			entryListDidChange:[newObject entryType] to:1];
		}
	}
	return newObject;
}


- (int)countForType:(int)theType
{
	return entryCountList[ theType ];
}


- (void) initObjectList
{
	id		obj;

	getrusage( RUSAGE_SELF, &stime );

	listOfObjects = [[List alloc] initCount:50];
	[BibTexParser initParsing];
	GET_BUFFER;
	report = 0;
	// get the first entryObject
	obj = [self parseNewObject];
	if( obj ){
		headerLength = [obj range].start;
	}

//	the following builds an entryObject-list without sorting
	while( obj ){
		[self addBibObject:obj];
		obj = [self parseNewObject];
	}
	
	sortedListOfObjects = [listOfObjects copyFromZone:[listOfObjects zone]];
	// now sort with comb sort
	[self combSort:sortedListOfObjects];

	getrusage( RUSAGE_SELF, &etime );
	sMSec = stime.ru_utime.tv_sec*1000 + stime.ru_utime.tv_usec/1000;
	eMSec = etime.ru_utime.tv_sec*1000 + etime.ru_utime.tv_usec/1000;
	uTime = eMSec - sMSec;

	sMSec = stime.ru_stime.tv_sec*1000 + stime.ru_stime.tv_usec/1000;
	eMSec = etime.ru_stime.tv_sec*1000 + etime.ru_stime.tv_usec/1000;
	sTime = eMSec - sMSec; 
}


- (int)uTime { return uTime; }

- (int)sTime { return sTime; }

- parsingReport:(int)msg { report = msg; return self; }

- (char *)fullPath { return myName; }

- (char *)fileName { return rindex( myName, '/' )+1; }

- (int) fileSize { GET_BUFFER; return length; }

- (int) entries { return [listOfObjects count]; }

- (NXStream	*)stream { return memStream; }

- (int)lastModified { return (int)lastModified; }

- setDirty { isDirty = YES; return self; }

- (BOOL)isDirty { return isDirty; }

- (BOOL)isNewFile { return isNewFile; }


- (void) getSpecials
{
	if( preferences==nil ){
		preferences = [Preferences new];
		STRING = [preferences indexOfEntryname:"STRING"];
		PREAMBLE = [preferences indexOfEntryname:"PREAMBLE"];
	}
}


- initNewFile:(const char *)fName
{
	[super init];
	[self getSpecials];
	myName = NXCopyStringBuffer(fName);	
	memStream = NXOpenMemory( NULL, 0, NX_READWRITE );

	listOfObjects = [[List alloc] initCount:50];
	sortedListOfObjects = [[List alloc] initCount:50];
	isNewFile = YES;
	return self;
}


- initFromFile:(const char *)fName
{
	if( fName==NULL) return [super free];	// should never happen
	
	[super init];
	[self getSpecials];
	myName = NXCopyStringBuffer(fName);

	// read the file

	memStream = NXMapFile(myName, NX_READWRITE);
	if( memStream==NULL ){
		free( myName );
		return [super free];
	}
	isNewFile = NO;
	if( [self fileStatus]==NO ) return [super free];
	[self initObjectList];
	fLength = NXTell( memStream );	// here start new/updated entries
	//fprintf( stderr, "size is %d\n", fLength );
	lastSearchPos = -1;
	return self;
}


- changeFilenameTo:(const char *)newName
{
	if( myName ) free( myName );
	myName = NXCopyStringBuffer(newName);
	isDirty = YES;
	return self;
	
}


- saveList:(List *)aList to:(FILE *)fp
{
	int	i;

	if( [aList count] ){
		for(i=0; i< [aList count]; i++ )
			OUTPUT( [aList objectAt:i] );
	}

	return self;
}


// save returns -1 on error, NX_OKTAG otherwise

- (int) save
{
	int	i;
	List	*typedList;
	FILE	*fp;	

	fp = fopen( myName, "w+" );
	
	if( fp==NULL ) return -1;
	if( [self fileStatus]==NO ) return -1;

	GET_BUFFER;
	
	// first write out the file header
	if( headerLength>2 )
		fwrite( &theBuffer[ 0 ], sizeof(char), headerLength-1, fp );
	
	// first write out all PREAMBLEs
	typedList = [self listForType:PREAMBLE sorted:YES];
	[self saveList:typedList to:fp];

	// then write out all STRINGs
	typedList = [self listForType:STRING sorted:YES];
	[self saveList:typedList to:fp];

	for(i=0; i< [sortedListOfObjects count]; i++ ){
		id	obj = [sortedListOfObjects objectAt:i];

		if( [obj entryType]==STRING ) continue;
		if( [obj entryType]==PREAMBLE ) continue;
		// crossref should be the last to print
		NEWLINE;
		OUTPUT(obj);
	}

	NEWLINE;
	NEWLINE;

	fclose( fp );
	isDirty = NO;
	isNewFile = NO;

	[self fileStatus];	// ?????
	return NX_OKTAG;
}


- objectAt:(unsigned int)index
{
	return [sortedListOfObjects objectAt:index];
}


- objectAtCharpos:(int)pos andSelectInMatrix:matrix
{
	int	i;
	locType	range;

	for( i=0; i<[sortedListOfObjects count]; i++ ){
		range = [[sortedListOfObjects objectAt:i] range];
		if( (range.start<=pos) && (pos<(range.start + range.length)) ){
			if( matrix ){
				[matrix selectCellAt:i :0];
				[matrix scrollCellToVisible:i :0];
			}
			return [sortedListOfObjects objectAt:i];
		}
	}
	return NULL;
}


- showFile
{
	// only for testing :
	if( theText==NULL )		
		[NXApp loadNibSection:"BibText.nib" owner:self];

	NXFlush(memStream);
	NXSeek(memStream, 0, NX_FROMSTART);
	[theText readText:memStream];
	[[theText window] setTitleAsFilename:myName];
	[[theText window] orderFront:self];
	return self;
}


- findKey:(const char *)theKey
{
	int left=0;
	int	right=[sortedListOfObjects count]-1;
	char keyBuffer[64];

	GET_BUFFER;	
	//	invariant : theKey must be between left and right
	while( left <= right ){
		int	cmp;
		int	i=(left + right)/2;
		id	theObj = [sortedListOfObjects objectAt:i];
		locType keyRange = [theObj key];

		[theObj copy:keyRange.start :keyRange.length toBuffer:keyBuffer];
		cmp = strcasecmp( keyBuffer, theKey );
		if( cmp == 0 ) return theObj;
		if( cmp > 0 ) right = i-1;
		else left = i+1;
	}
	//	invariant : theKey must be between 'left' and 'right'
	return nil;	// not found
}


- free
{	
	[sortedListOfObjects free];
	[listOfTypedObjects free];
	[[listOfObjects freeObjects] free];
	free( myName );
	NXCloseMemory( memStream, NX_FREEBUFFER );
	if( theText )[[theText window] free];
	return [super free];
}


//	adds a new bibObject to the list of bibObjects and
//	updates the type counter
//	if a sorted bibObject list exists this object is inserted
//	with insertion sort. This list does not exist during initialisation
- (void) addBibObject:(BibTeXObject*)obj
{
	int	j;
	locType keyRange;

	[listOfObjects addObjectIfAbsent:obj];

	entryCountList[ [obj entryType] ]++;
	if( entryCountList[ [obj entryType] ]==1 ){
		[[[NXApp delegate] browserController]
			entryListDidChange:[obj entryType] to:1];
	}

	if( sortedListOfObjects==nil ) return;

	j = [sortedListOfObjects count] - 1;
	keyRange = [obj key];

	GET_BUFFER;
	while( j>=0 ){	//	insertion sort
		locType keyRangeL = [[sortedListOfObjects objectAt:j] key];

		if( [self compare:keyRangeL with:keyRange] > 0 ) j--;
		else break;
	}
	[sortedListOfObjects insertObject:obj at:j+1];
}


- (void) deleteBibObject:(BibTeXObject*)obj
{
	[sortedListOfObjects removeObject:obj];
	[listOfObjects removeObject:obj];
	entryCountList[ [obj entryType] ]--;
	if( entryCountList[ [obj entryType] ]==0 ){
		[[[NXApp delegate] browserController]
			entryListDidChange:[obj entryType] to:0];
	}
	[listOfTypedObjects removeObject:obj];
	[obj free];
}


- (void) changeKeyOfBibObject:(BibTeXObject*)obj
{
	[sortedListOfObjects removeObject:obj];
	[self insertBibObject:obj];
}


- (void) changeTypeOfBibObject:(BibTeXObject*)obj from:(int)old to:(int)new
{
	entryCountList[ old ]--;
	if( entryCountList[ old ]==0 ){
		[[[NXApp delegate] browserController]
			entryListDidChange:old to:0];
	}
	entryCountList[ new ]++;
	if( entryCountList[ new ]==1 ){
		[[[NXApp delegate] browserController]
			entryListDidChange:new to:1];
	}
}


- addObjects:(List *)objList	// list of matrixCells
{
	int		i;
	id		obj;
	int		lastPos;
	BOOL	sl;
	char *localBuffer;
	int	locBufferLength, locBufferMaxLength;

	if ( localStream==NULL ){
		localStream = NXOpenMemory( NULL, 0, NX_READWRITE );
	}
	NXSeek(localStream, 0, NX_FROMSTART);
	for( i=0; i<[objList count]; i++ ){
		NXPutc( localStream, '\n' );
		[[[objList objectAt:i] object] copyToStream:localStream];
		NXPutc( localStream, '\n' );
	}
	NXFlush( localStream );
	lastPos = NXTell( localStream );
	NXGetMemoryBuffer( localStream,
		&localBuffer, &locBufferLength, &locBufferMaxLength);
	NXSeek( memStream, 0, NX_FROMEND);	// append here
	NXWrite( memStream, localBuffer, lastPos );
	NXFlush( memStream );
	
	// parse the new string
	NXSeek( memStream, -lastPos, NX_FROMEND);	// go back
	obj = [self parseNewObject];
	
	sl = NO;
	while( obj ){
		[self addBibObject:obj];
		[[NXApp delegate] update:obj for:self isNew:YES shiftLit:sl];
		sl = YES;
		obj = [self parseNewObject];
	}

	isDirty = YES;
	return self;
}


- saveChanges:(int)len :(char *)buffer
{
	char	backUpname[512];
	struct	stat fBuf;
	int	rtn;


	// check if we have already a backupfile
	if( changeFilePointer==NULL ){	// create a new backupfile
		if( preferences==nil ){
			preferences = [Preferences new];
		}
		sprintf( backUpname, "%s/_%s",
			[preferences tempDirectory], [self fileName] );
		rtn = stat(backUpname, &fBuf);
		// if rtn==0 a file with this name already exists
		changeFilePointer = fopen( backUpname, "w+" );
		if(0)fprintf( stderr, "new file %s (%d) rtn=%d\n",
			backUpname, (unsigned)changeFilePointer, rtn );
		if( changeFilePointer==NULL ){
			fprintf( stderr, "cannot open %s\n", backUpname );
			return self;
		}
	}
	//fprintf( stderr, "save %d byte\n", len );
	fwrite( "\n", sizeof(char), 1, changeFilePointer );
	fwrite( &buffer[ 0 ], sizeof(char), len, changeFilePointer );
	fwrite( "\n", sizeof(char), 1, changeFilePointer );
	fflush( changeFilePointer );
	return self;
}


- appendNewText:(int)len :(char *)buffer
{
	id newObject, rtnObject;

	NXSeek( memStream, 0, NX_FROMEND);	// append here
	NXPrintf( memStream, "\n\n" );
	NXWrite( memStream, buffer, len );
	NXFlush( memStream );

	[self saveChanges:len :buffer];

	// parse the new text
	NXSeek( memStream, -len-1, NX_FROMEND);	// go back

	rtnObject = newObject = [self parseNewObject];

	while( newObject ){
		[self addBibObject:newObject];
		[[NXApp delegate] update:newObject for:self isNew:YES shiftLit:NO];
		newObject = [self parseNewObject];
		if( newObject ) rtnObject = newObject;
	}
	isDirty = YES;
	return rtnObject;
}


// this method is called if the object's text did change
- replaceTextOf:(BibTeXObject*)obj with:(char *)buffer length:(int)len
{
	locType	newRange;
	int		oldEntryType = [obj entryType];
	locType keyRangeOld = [obj key];

	NXSeek( memStream, 0, NX_FROMEND);	// append here
	newRange.start = NXTell( memStream );	// new position
	NXWrite( memStream, buffer, len );
	NXFlush( memStream );

	[self saveChanges:len :buffer];

	newRange.length = len;
	[obj setRange:newRange];
	[obj parseSelf];	// parse again
	isDirty = YES;

	GET_BUFFER;
	if( [self compare:keyRangeOld with:[obj key]] != 0 ){
		[self changeKeyOfBibObject:obj];	// key did change
	}

	if( oldEntryType==[obj entryType] ) return self;

	[self changeTypeOfBibObject:obj from:oldEntryType to:[obj entryType]];

	return self;
}


@end



/***************		category for the List class	***********/

@implementation List(exch)

- exchange:(int)i :(int)j;
{
	id	h = [self objectAt:i];
	
	[self replaceObjectAt:i with:[self objectAt:j]];	
	[self replaceObjectAt:j with:h];
	
	return self;
}


@end


