

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


#import "MacroDB.h"
#import "Controller.h"
#import "BibliographicFile.h"
#import "Preferences.h"
#import "BibTexView.h"
#import	"TabMatrix.h"
#import	<dbkit/dbkit.h>

static id macroDB = nil;

#define	STRING_MODE	301
#define	MACRO_MODE	302
#define	STYLE_MODE	303

@implementation MacroDB

+ new
{
	if( macroDB==nil ){
		macroDB = [[super alloc] init];
	}

	return macroDB;
}


+ alloc
{
    return [self notImplemented:_cmd];
}


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


- makeTabMatrix
{
	NXRect	backgroundRect,matrixRect;
	TabSelectionCell *aCell;
	unsigned int entryFormMask = [tabCellView autosizing];	//€as set in IB

	/* Set tabCellView to be flipped ('cause Matrices are flipped) */
	[tabCellView setFlipped:YES];
	
	/* Get the tabCellView's dimensions */
	[tabCellView getBounds:&backgroundRect];

	/* Set up the matrix bounds */
	matrixRect = backgroundRect;
	matrixRect.origin.y = NX_MAXY(&matrixRect) - 17.0;

	/* Prepare a matrix to go inside the tabCellView */
	tabMatrix = [[Matrix allocFromZone:[self zone]] initFrame:&matrixRect 
						mode:NX_RADIOMODE
						cellClass:[TabSelectionCell class]
						numRows:1
						numCols:3];
	
	/* Set the background gray of tabMatrix to NX_DKGRAY */
	[tabMatrix setBackgroundGray:NX_DKGRAY];
	
	/* Set the autosizing and autoscrolling attributes of the matrix */
	[tabMatrix setAutosizing:entryFormMask];
	//[tabMatrix setAutoscroll:YES];
	[tabMatrix setAutosizeCells:YES];

	/* Stick the matrix in our scrollView */
	[tabCellView addSubview:tabMatrix];
	[tabCellView setAutoresizeSubviews:YES];
    [tabCellView setAutosizing:entryFormMask];

	/* Set our target and single-click actions */
	[tabMatrix setTarget:self];
	[tabMatrix setAction:@selector(tabMatrixAction:)];
	aCell = [tabMatrix cellAt:0 :0];
	[aCell initTextCell:"strings"];
	aCell = [tabMatrix cellAt:0 :1];
	[aCell initTextCell:"macros"];
	aCell = [tabMatrix cellAt:0 :2];
	[aCell initTextCell:"styles"];

	/* Size tabMatrix to its cells and display it */
	[tabMatrix sizeToCells];
	[tabMatrix display];

	return self;
}


- tabMatrixAction:sender
{
	[[mainBox window] disableDisplay];
	displayMode = [sender selectedCol] + STRING_MODE;
	switch( displayMode ){
		case STRING_MODE:
		case MACRO_MODE:
					[mainBox setContentView:stringView];
					[self show:self];
					break;
		case STYLE_MODE:
					[mainBox setContentView:styleBrowserBox];
					break;
		default : break;
	}
	[[mainBox window] reenableDisplay];
	[mainBox display];
	return self;
}


- awakeFromNib
{
	[[mainBox window] setFrameUsingName:"MacroWindow"];
	[[mainBox window] setFrameAutosaveName:"MacroWindow"];

	[stringView setRowHeadingVisible:NO];
	[stringView setColumnHeadingVisible:YES];
	[stringView allowVectorReordering:YES];
	[stringView allowVectorResizing:YES];
	[stringView setDataSource:self];
	[stringView setTarget:self];
	[stringView setDelegate:self];
	[stringView setAction:@selector(tableViewSingleClick:)];
	[stringView setDoubleAction:@selector(tableViewDoubleClick:)];
	[stringView setGridVisible:YES];

	preferences = [Preferences new];
	STRING = [preferences indexOfEntryname:"STRING"];
	NOTE = [preferences indexOfFieldname:"NOTE"];

	[mainBox setContentView:stringView];
	displayMode = STRING_MODE;

	// style browser
	[styleBrowser setDelegate:self];
	[styleBrowser setTarget:self];
	[styleBrowser setAction:@selector(styleSingleClick:)];
	[styleBrowser setDoubleAction:@selector(styleDoubleClick:)];
	[styleBrowser setMinColumnWidth:22];
	[styleBrowser setMaxVisibleColumns:2];
	[styleBrowser setTitle:"TEXINPUTS" ofColumn:0];

	styleFile = NXCopyStringBuffer( "no style file selected" );	

	[self makeTabMatrix];
	return self;
}


- fileDidChangeTo:theFile
{
	if( dbWindow && [dbWindow isVisible] && (displayMode==STRING_MODE) ){
		[self show:self file:theFile];
	}
	return self;
}



/*======================================================================
 *	methods (delegate methods and others) for the style browser
 *======================================================================*/

//	browser delegate methods

#include <sys/dir.h>  /* POSIX applications #include <dirent.h> */


- (BOOL)isDirectory:(const char *)path
{
	struct	stat fBuf;
	int	rtn = stat(path, &fBuf);

	if( rtn<0 ){
		perror("stat error");
		return NO;
	}
	return (S_IFDIR & fBuf.st_mode) != 0;
}


- styleSingleClick:sender
{
	//NXRect	aRect;
	//NXStream	*localStream = NXOpenMemory( NULL, 0, NX_READWRITE );

	return self;

	//fprintf( stderr, "styleSingleClick\n" );
	//[stringButton printPSCode:self];	// TEST
	//[stringButton getFrame:&aRect];
	//[stringButton convertRect:&aRect fromView:nil];
	//[stringButton copyPSCodeInside:&aRect to:localStream];
	//NXSaveToFile(localStream, "/tmp/abcde.eps");
	//return self;
}


- (char *)nextFunctionName:(NXStream *)aStream
{
	char buf[1024];
	int	i = 0;
	while( (mc!=EOF) && NXIsAlpha(mc) ){
		buf[i++] = mc;
		mc = NXGetc( aStream );
	}
	buf[i] = '\0';
	//fprintf( stderr, ">%s<\n", buf );
	return buf;
}


- skipEmptyText:(NXStream *)aStream
{
	while( (mc!=EOF) && (NXIsSpace(mc) || (mc=='%')) ){
		if( mc=='%' ){
			mc = NXGetc( aStream );	// skip '%'
			while( (mc!=EOF) && (mc!='\n') ) mc = NXGetc( aStream );
			// mc == '\n'  || mc==EOF
		}
		else mc = NXGetc( aStream );
	}
	return self;
}


- skipString:(NXStream *)aStream
{
	if( mc!='"' ) return self;	// internal error
	mc = NXGetc( aStream );		// skip stringquote
	while( YES ){
		if( (mc=='"') || (mc==EOF) ) return self;
		mc = NXGetc( aStream );
	}
	return self;
}


- skipParameter:(NXStream *)aStream
{
	int	braceLevel = 1;
	//	mc == LBRACE
	while( braceLevel && (mc!=EOF) ){
		mc = NXGetc( aStream );
		if( mc=='%' ) [self skipEmptyText:aStream];
		if( mc=='"' ) [self skipString:aStream];
		if( mc==RBRACE ) braceLevel--;
		if( mc==LBRACE ) braceLevel++;
	}
	mc = NXGetc( aStream );	// skip RBRACE
	return self;
}


- readMacro:(NXStream *)aStream
{	
	// we need a stream for the definitions
	if( !texStream ){
		texStream = NXOpenMemory(NULL, 0, NX_READWRITE);
		NXSeek(texStream, 0, NX_FROMSTART);
		NXPrintf( texStream, "macros ");	// we may not start with index 0 !
	}

	[self skipEmptyText:aStream];
	//	mc must now be LBRACE
	mc = NXGetc( aStream );	// skip LBRACE
	[self skipEmptyText:aStream];
	// collect the macro name
	[macroList addObject:(id)NXTell( texStream )];	// here starts the macro

	while( NXIsAlpha(mc) ){
		NXPutc( texStream, mc);
		mc = NXGetc( aStream );
	}
	NXPutc( texStream, '\0');
	[self skipEmptyText:aStream];
	mc = NXGetc( aStream );	// skip RBRACE

	[self skipEmptyText:aStream];
	//	mc must be LBRACE
	mc = NXGetc( aStream );	// skip LBRACE
	// collect the macro text
	while( mc!=RBRACE ){
		NXPutc( texStream, mc);
		mc = NXGetc( aStream );
	}
	NXPutc( texStream, '\0');
	mc = NXGetc( aStream );	// skip RBRACE
	return self;
}


- initFromStream:(NXStream *)aStream
{
	char	*theName;
	styleStream = aStream;
	
	if( styleStream==NULL ){
		//[self alert:"cannot open %s" :fname];
		return self;
	}

	// we need a stream for the macro definitions
	if( !texStream ){
		texStream = NXOpenMemory(NULL, 0, NX_READWRITE);
	}
	NXSeek(texStream, 0, NX_FROMSTART);
	NXPrintf( texStream, "macros ");	// we may not start with index 0 !
	
	if( macroList==nil ) macroList = [[List alloc] initCount:50];
	[macroList empty];

	mc = NXGetc( styleStream );
	while( (mc!=EOF) ){
		[self skipEmptyText:styleStream];
		theName = [self nextFunctionName:styleStream];
		if( strcasecmp( theName, "macro" )==0 ){
			[self readMacro:styleStream];
		}
		[self skipEmptyText:styleStream];
		while( mc==LBRACE ){	// skip a parameter list
			[self skipParameter:styleStream];
		}
	}
	return self;
}


- (BOOL) initFromFile:(const char *)fname
{
	NXStream *fileStream = NXMapFile(fname, NX_READONLY);

	if( fileStream==NULL ){
		[self alert:"cannot open file %s" :fname];
		return NO;
	}
	if( styleFile ) free( styleFile );
	styleFile = NXCopyStringBuffer( fname );	
	[self initFromStream:fileStream];
	NXCloseMemory( fileStream, NX_FREEBUFFER );

	return YES;
}


- styleDoubleClick:sender
{
	char	path[1024];
	int	selCol  = [sender selectedColumn];

	//fprintf( stderr, "styleDoubleClick\n" );

	if( selCol==0 ) return self;
	[styleBrowser getPath:path toColumn:selCol+1];	// starts with //

	if( [self initFromFile:(path+1)]==NO ) return self;

	if(0)[[Application workspace] openFile:(path+1)
		withApplication:[preferences theEditor]];
	
	//[popUpButton setTitle:"macros"];
	[tabMatrix selectCellAt:0 :1];
	[dbWindow disableDisplay];
	[mainBox setContentView:stringView];
	displayMode = MACRO_MODE;
	[self show:self];

	return self;
}



- (int)fillColZero:locMatrix
{
	int		i=0, row=0;
	id		newCell;
	char	buffer[1024];
	List *args = [preferences shellArgsOf:"TEXINPUTS"
					toBuffer:buffer size:1024];

	if (args==nil ){
		//fprintf( stderr, "Erorr: no variable TEXINPUTS\n" );
		args = [[List alloc] init];
		[args addObject:(id)"."];
		[args addObject:(id)"/usr/lib/tex/inputs"];	// default
	}


	for( i=0; i<[args count]; i++ ){
		const char *para = (const char *)[args objectAt:i];
		if( para[0]=='\0' ) continue;	// (empty string)
		
		[locMatrix addRow];
		newCell = [locMatrix cellAt:row :0];
		row++;
		if( strcmp(para, ".")==0 ){
			[newCell setStringValue:[preferences tempDirectory]];
			//[newCell setStringValue:NXHomeDirectory()];
		}
		else{
			[newCell setStringValue:para];
		}
		[newCell setLeaf:NO];
		[newCell setLoaded:YES];
	}

	[args free];
	return row;
}


- (int)fillMatrix:locMatrix
{
	DIR		*dirp;
	struct	direct *dp;
	int		length;
	int		count = 0;
	id		newCell;
	char	path[1024];
	char	buffer[1024];

	[styleBrowser getPath:path
		toColumn:[styleBrowser selectedColumn]+1];

	// path starts with "//", do not use the first character	
	dirp = opendir( path+1 );
	if( dirp==NULL ){
		sprintf( buffer, "%s\n%s", path+1, strerror(errno) );
		[self alert:"  %s" :buffer];
		return 0;
	}
	for( dp = readdir(dirp); dp != NULL; dp = readdir(dirp) ) {
		length = strlen( dp->d_name );
		if( length<=4 ) continue;
	// show also directories
		sprintf( buffer, "%s/%s", path+1, dp->d_name );
		if( NO && [self isDirectory:buffer] ){
			[locMatrix addRow];
			newCell = [locMatrix cellAt:count :0];
			[newCell setStringValue:dp->d_name];
			[newCell setLeaf:NO];
			[newCell setLoaded:YES];
			count++;
			continue;
		}
		if( dp->d_name[length-4]=='.' &&
			dp->d_name[length-3]=='b' &&
			dp->d_name[length-2]=='s' &&
			dp->d_name[length-1]=='t'
			){
				[locMatrix addRow];
				newCell = [locMatrix cellAt:count :0];
				[newCell setStringValue:dp->d_name];
				[newCell setLeaf:YES];
				[newCell setLoaded:YES];
				count++;
			}
	}
	closedir(dirp);

	return count;
}


- (int)browser:sender fillMatrix:locMatrix inColumn:(int)column
{
	//id	matrix = [theBrowser matrixInColumn:column-1];
	//int	index = [matrix selectedRow];

	if( column==0 ){
		return [self fillColZero:locMatrix];
	}
	return [self fillMatrix:locMatrix];
}


 /*======================================================================*/


- tableViewSingleClick:sender
{
	int	col = [sender selectedColumn];
	int	row = [sender selectedRow];
	View *colHead = [sender columnHeading];
	NXRect	aRect;

	[colHead getVisibleRect:&aRect];
	if( row<0 ){
		[stringButton setEnabled:NO];
		[expndStrButton setEnabled:NO];
	}
	else{
		[stringButton setEnabled:YES];
		[expndStrButton setEnabled:YES];
	}
	if(0)fprintf( stderr, "selection is row(%d/%d) col(%d/%d)\n",
		[sender selectedRow], [sender rowCount], 
		col, [sender columnCount] );
	return self;
}


- tableViewDoubleClick:sender
{
	//fprintf( stderr, "tableViewDoubleClick %s\n", [sender name] );
	return self;
}


- insertString:sender
{
	id <DBTableVectors> vector;
	id	rowIdentifier;
	int	here;
	int	row = [stringView selectedRow];
	char buf[1024];

	if( row<0 ) return self;

	vector = [stringView rowAt:row];
	rowIdentifier = [vector identifier];

	switch( displayMode ){
		case STRING_MODE :
			[rowIdentifier parseSelf];
			[rowIdentifier copyRange:[rowIdentifier key] toBuffer:(buf+1)];
			break;
		case MACRO_MODE :
			here = (int)[macroList objectAt:(int)rowIdentifier];
			sprintf( (buf+1), &macroBuffer[here] );
			break;
		default : return self;
	}
	buf[0] = '@';
	[[BibTexView new] insertMacro:buf isRaw:NO];
	return self;
}


- insertExpandedString:sender
{
	int	here;
	id <DBTableVectors> vector;
	id	rowIdentifier;
	int	row = [stringView selectedRow];
	char buf[1024];

	if( row<0 ) return self;

	vector = [stringView rowAt:row];
	rowIdentifier = [vector identifier];

	switch( displayMode ){
		case STRING_MODE : 
			[rowIdentifier parseSelf];
			[rowIdentifier
				convertRawField:[rowIdentifier fieldFor:NOTE] to:buf];
			[[BibTexView new] insertMacro:buf isRaw:NO];
			break;
		case MACRO_MODE :
			here = (int)[macroList objectAt:(int)rowIdentifier];
			while( macroBuffer[here++] );	// skip the definition
			[[BibTexView new] insertMacro:&macroBuffer[here] isRaw:YES];
			break;
		default : return self;
	}

	return self;
}


- show:sender file:selFile
{
	int		i;
	char	buffer[128];
	id <DBTableVectors> vector;
	///id	selFile = [[NXApp delegate] selectedFile];
	id	anObj;
	id	typedList;

	if( !dbWindow ){	// first call
		[NXApp loadNibSection:"macroWindow.nib" owner:self];
	}

	if( sender!=self )[dbWindow orderFront:self];
	if( displayMode==STYLE_MODE ) return self;

	if(0)fprintf( stderr, "show obj %08x\n", (unsigned)selFile );
	if(0)fprintf( stderr, "show file %s\n", [selFile fullPath] );
	[stringButton setEnabled:NO];
	[expndStrButton setEnabled:NO];

	//if( selFile==nil ) return self;

	if( [dbWindow isDisplayEnabled] ) [dbWindow disableDisplay];
	for(i=[stringView rowCount]-1; i>=0; i-- ){
		id row = [stringView rowAt:i];
		[stringView removeRowAt:i];	// remove from display
		[row free];
	}
	
	for(i=[stringView columnCount]-1; i>=0; i-- ){
		id col = [stringView columnAt:i];
		[stringView removeColumnAt:i];	// remove from display
		[col free];
	}

	if( displayMode==MACRO_MODE ){	//show MACRO
		[stringButton setTitle:"insert macro"];
		[expndStrButton setTitle:"insert expanded macro"];
		[stringView addColumn:(id)0 withTitle:"Macro"];
		[stringView addColumn:(id)1 withTitle:styleFile ];
		if( macroList && texStream ){
			NXGetMemoryBuffer( texStream, &macroBuffer, &len, &maxLength);
			for( i=0; i<[macroList count]; i++ ){
				[stringView addRow:(id)i withTitle:
					&macroBuffer[(int)[macroList objectAt:i]] ];
			}
		}
	}
	else{	//show @STRING
		[stringButton setTitle:"insert string"];
		[expndStrButton setTitle:"insert expanded string"];
		[stringView addColumn:(id)0 withTitle:"String"];
		if( selFile==nil ){
			[stringView addColumn:(id)1 withTitle:"no or multiple selection" ];
		}
		else {
			[stringView addColumn:(id)1 withTitle:[selFile fullPath] ];
			typedList = [selFile listForType:STRING sorted:YES];
			for( i=0; i<[typedList count]; i++ ){
				anObj = [typedList objectAt:i];
				[anObj copyRange:[anObj key] toBuffer:buffer];
				[stringView addRow:anObj withTitle:buffer];
				//[stringView addRow:anObj at:i+1];
			}
		}
	}

	vector = [stringView columnAt:0];
	[vector setMaxSize:100.0];
	[stringView layoutChanged:self];
	if( sender!=self )[dbWindow orderFront:self];
	[dbWindow reenableDisplay];
	[dbWindow display];
	
	[stringView setEditable:YES];
	return self;
}


- show:sender
{
	return [self show:sender file:[[NXApp delegate] selectedFile]];
}



/*======================================================================
 *	DBTableView data source (pseudo-delegate) methods
 *======================================================================*/

- tableViewDidChangeSelection:aTableView
{
	[self tableViewSingleClick:aTableView];
	return self;
}


- (unsigned int)rowCount
{
	//fprintf( stderr, "rowCount\n" );
    return 0;
}

- (unsigned int)columnCount
{
	fprintf( stderr, "columnCount\n" );
    return 2;
}

/* DBTV sends this message when it needs to know what to display */
- getValueFor:identifier at:(unsigned int)aPosition into:aValue
{
	//fprintf( stderr, "getValueFor %d at %d\n", (int)identifier, aPosition );
	[aValue setIntValue:5*aPosition];
    return nil;
}

/* DBTV sends this message when it needs to know what to display */
- getValueFor:rowIdentifier :colIdentifier into:aValue
{
	int	here;

	if(0)fprintf( stderr, "getValueFor row(%d)/col(%d)\n",
		(unsigned)rowIdentifier, (unsigned)colIdentifier );
	
	if( displayMode==STRING_MODE ){ 
		//	show @STRING 
		// rowIdentifier is an entryObject 
		// colIdentifier is the column number
		[rowIdentifier parseSelf];
		if( (int)colIdentifier==0 ){	//String
			[rowIdentifier setStringOf: [rowIdentifier key] to:aValue];
		}

		if( (int)colIdentifier==1 ){	//Expansion
			[rowIdentifier setStringOf:
				[rowIdentifier fieldFor:NOTE] to:aValue];
		}
		return nil;
	}

	// displayMode==MACRO_MODE
	// rowIdentifier is the row number ( = index in macroList) 
	// colIdentifier is the column number
	here = (int)[macroList objectAt:(int)rowIdentifier];
	if( (int)colIdentifier==0 ){	// macro name
		[aValue setStringValue: &macroBuffer[here]];
		//fprintf( stderr, "0: %s\n", &macroBuffer[here] );
	}
	if( (int)colIdentifier==1 ){	//Expansion
		while( macroBuffer[here++] );	// skip the definition 
		//fprintf( stderr, "0: %s\n", &macroBuffer[here] );
		[aValue setStringValue: &macroBuffer[here]];
	}

    return nil;
}

/* DBTV sends this message when data in a cell has changed */
- setValueFor:identifier at:(unsigned int)aPosition from:aValue
{
    fprintf( stderr, "setValueFor at %d\n", aPosition );
    return nil;
}


- setValueFor:rowIdentifier :columnIdentifier from:aValue
{
	fprintf( stderr, "setValueFor row=%d  col=%d\n",
		(int)rowIdentifier, (int)columnIdentifier );
	return nil;
}


/*======================================================================
 *	end of DBTableView data 
 *======================================================================*/
@end
