/* BBMacMoleculeFilter.m				 
 *
 * This format filter does handle read/write of molecules in the
 * MacMolecule Text (.macMol) fileformat.
 *
 * For interface-info see the header file. The comments in this file mostly
 * cover only the real implementation details.
 *
 * Written by: 		Thomas Engel
 * Created:    		25.03.1994 (Copyleft)
 * Last modified: 	30.10.1994
 */

#import "BBMacMoleculeFilter.h"
#import "BBFileFilterManager.h"
#import "../BBAppManager.h"
#import "../AtomLibrary.subproj/BBAtomLibraryManager.h"

#import "../BBBeaker.h"
#import "../BBMolecule.h"
#import "../BBAtom.h"
#import "../BBBond.h"

#import <misckit/MiscString.h>

#define MYBUFFERSIZE		500
#define COMMENTTOKEN		";"

// I hate statics but here they might be useful.
// The other variables have been added to the intercase to allow debugging.

static char lineBuffer[MYBUFFERSIZE];
static FILE	*inStream;

@implementation BBMacMoleculeFilter

- (float)canReadFile:(const char*)filename
{
	// Let's see if we can handle the file. We will check the extention.
	// "macMol" will be regonized.
	
	id		pathString;
	id		aString;
	float	answer;
	
	pathString = [MiscString new];
	[pathString setPath:filename];
	aString = [pathString fileExtension];
	
	if( [aString casecmp:"macMol"] == 0 )
			answer = (float)YES;
	else	answer = (float)NO;
	
	[pathString free];
	[aString free];
	
	return answer;
}

- readBeaker:aBeaker fromFile:(const char*)filename
{	
	// A simple parser for MacMolecule molecules.
	// The tokenList is only defined as long as its data is not already
	// parsed or it contains the parts that have to be parsed. Freeing
	// the list is not enough! We have to reset the id to nil to make the
	// check work properly.
	// Right here we only trigger reading the atoms and bonds. I hope that
	// they will always come in that order.

	id	aString;
	id	pathString;
	
	inStream = fopen( filename, "r" );
	if( inStream == NULL ) return self;
		
	// We won't take care of any tokens right here. Just set the molname from
	// the filename and init the list for tracking the atom symbols to allow
	// setting up the bonds.
	// The basic toke mechanism is still in here to allow easy token parsing..
	// if the format does define them.
	
	mol = [BBMolecule new];

	symbolList = [List new];
	theLine = [MiscString new];
	tokenList = nil;

	// The file has no info on the name. So we will take the filename.
	
	pathString = [[MiscString new] setPath:filename];
	aString = [pathString fileBasename];
	[(BBMolecule *)mol setName:[aString stringValue]];
	[aString free];
	[pathString free];

/*
	if( fgets( lineBuffer, MYBUFFERSIZE, inStream ))
	{
		[theLine setStringValue:lineBuffer];
		tokenList = [theLine tokenize:" \t\n\r" into:nil];
		atomCount = [[tokenList objectAt:0] intValue];
		bondCount = [[tokenList objectAt:2] intValue];

		aString = [theLine substringFromToken:6 toToken:[tokenList count]-1
									   ofList:tokenList];
		[(BBMolecule *)mol setName:[aString stringValue]];
		[aString free];

		[[tokenList freeObjects] free];
		tokenList = nil;

		if( [self readAtomData:atomCount] )
			[self readBondData:bondCount];
	}
*/
	if( [self readAtomData] )
		[self readBondData];
	
	[aBeaker addMolecule:mol];
	[theLine free];
	[[symbolList freeObjects] free];
	fclose( inStream );
	
	return self;
}

- (BOOL)readAtomData
{
	// Reading the atom data
	// Here we will set up the temp list for the bond stuff.
	// The recognizion is very stupid. Every line has to match a pattern.
			
	BOOL 	goAhead = YES;
	id		anAtom;
	id		basicAtom;
	id		atomLibrary;		
	id		token;
	
	atomLibrary = [[NXApp delegate] atomLibrary];

	do
	{
		if( !tokenList )
		{
			if( !fgets( lineBuffer, MYBUFFERSIZE, inStream ))
			{
				goAhead = NO;
				break;
			}
			[theLine setStringValue:lineBuffer];
			tokenList = [theLine tokenize:" \t\n\r" into:nil];
		}
		
		// Check for comments and blank lines.
		// We won't accept less than 6 elements per line.
		// If we allready did read a atom and we have 2 token in the list
		// We should stop. This will be the first bond.
		
		if( [tokenList count] == 2 &&
			[symbolList count] > 0 )	break;
			
		token = [tokenList objectAt:0];
		
		if( [tokenList count] < 5 ||
			[[tokenList objectAt:1] compareStr:":"] != 0 ||
			[token compareStr:COMMENTTOKEN] == 0 )
		{
			[[tokenList freeObjects] free];
			tokenList = nil;
			continue;
		}

		// Otherwise lets init a new atom from this line:
		// We have to find the basicAtom too.
		// To find the basic atom we need to convert the MacMolecule atom
		// symbol. Therefore we strip of any unwanted characters.
		// We will add the original symbol at the atom to the lists so we can
		// find them when reading the bonds.
		
		anAtom = [BBAtom new];
		[symbolList addObject:[token copy]];
		
		[self checkSymbol:token];
		
		basicAtom = [atomLibrary findBasicAtomWithSymbol:[token stringValue]];
		[anAtom setMotherAtom:basicAtom];
		[anAtom setXPos:[[tokenList objectAt:2] doubleValue] / 10];
		[anAtom setYPos:[[tokenList objectAt:3] doubleValue] / 10];
		[anAtom setZPos:[[tokenList objectAt:4] doubleValue] / 10];
			
		[mol addAtom:anAtom];
		
		// Lets free the list and make the line as read. We don't need it
		// anymore. 

		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	while( goAhead );
	
	return goAhead;
}

- (BOOL)readBondData
{
	// Reading the bond data.
	// We will use the tempLists to find the right atoms to be connected.
	// MacMolecule has no infos on the bondtype so everything will be a
	// single bond.
		
	BOOL 	goAhead = YES;
	BBBond * aBond;
	int		i;
	id		atomList;
	id		fromAtom;
	id		toAtom;
	id		token;
	char	type;
	
	atomList = [mol atomList];
	
	do
	{
		if( !tokenList )
		{
			if( !fgets( lineBuffer, MYBUFFERSIZE, inStream ))
			{
				goAhead = NO;
				break;
			}
			[theLine setStringValue:lineBuffer];
			tokenList = [theLine tokenize:" \t\n\r" into:nil];
		}
		
		// Check for comments, blank lines and new tokens.
		// If there are not enough elements we will skip it

		token = [tokenList objectAt:0];

		if( [tokenList count] < 2 ||
			[token compareStr:COMMENTTOKEN] == 0 )
		{
			[[tokenList freeObjects] free];
			tokenList = nil;
			continue;
		}

		// Otherwise lets init a new bond from this line:
		// We will set the name to some usefull value.
		// From the first atom symbol we have to strip the last ","
		// Then we will search the symbolList.
		// We always work with the premise that a molecule adds all the
		// atom objects in the same order to its internal atomList as we 
		// tell it to.
				
		aBond = [BBBond new];
		type = BOND_SINGLE;
	
		token = [[tokenList objectAt:0] substringFrom:0
							to:[[tokenList objectAt:0] length]-2 ];
		for( i=0; i<[symbolList count]; i++ )
			if( [[symbolList objectAt:i] compare:token] == 0 ) break;
			
		fromAtom = [atomList objectAt:i];

		token = [tokenList objectAt:1];
		for( i=0; i<[symbolList count]; i++ )
			if( [[symbolList objectAt:i] compare:token] == 0 ) break;

		toAtom = [atomList objectAt:i];
		
		[aBond connect:fromAtom to:toAtom with:type];
	
		// We will use the token string object to compose our Bond name.
		
		[token setStringValue:[fromAtom symbol]];		
		[token catStringValue:"±"];		
		[token catStringValue:[toAtom symbol]];		
		[aBond setName:[token stringValue]];
		
		[mol addBond:aBond];
		
		// Lets free the list and make the line as read. We don't need it
		// anymore
		
		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	while( goAhead );

	return goAhead;
}

- checkSymbol:symbol
{
	// MacMolecule symbol contain counts at their end.
	// We will remove them here.
	
	int	i;
	id	aString;
	
	for( i=0; i<[symbol length]; i++ )
		if( [symbol charAt:i] < 'A' ||
			[symbol charAt:i] > 'z' )
			break;
	
	aString = [symbol substringFrom:0 to:i-1];
	[symbol setStringValue:[aString stringValue]];
	[aString free];
	
	return self;
}

- writeBeaker:aBeaker toFile:(const char *)filename
{
	return self;
}

@end

/*
 * History: 30.10.94 Switched to the MiscString.
 *
 *			14.05.94 Changes to work with new BB... objects.
 *
 *			15.03.94 A basic implementation.
 *
 *
 * Bugs: - We will watch for '#' comment even they are not defined for that
 *		   data format..
 *
 *		 - Does not handle charges and strips extra data from the symbol
 *		   name. These symbolextentions may be automatically typed to our
 *		   symbol addition area..perhaps with some conversion ?? (15.03.94)
 */
