/* BBAlchemyFilter.m				 
 *
 * This format filter does handle read/write of molecules in the
 * Alchemy Text (.alchMol or .mol) 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:    		15.03.1994 (Copyleft)
 * Last modified: 	30.10.1994
 */

#import "BBAlchemyFilter.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		"#"

#define SINGLEBONDTOKEN		"SINGLE"
#define DOUBLEBONDTOKEN		"DOUBLE"
#define AROMATICBONDTOKEN	"AROMATIC"

// 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 BBAlchemyFilter

- (float)canReadFile:(const char*)filename
{
	// Let's see if we can handle the file. We will check the extention.
	// "alchMol" will be regonized direktly where as the normal "mol" 
	// extention has to pass so file checking.
	
	id		pathString;
	id		aString;
	float	answer;
	
	pathString = [MiscString new];
	[pathString setPath:filename];
	aString = [pathString fileExtension];
	
	if( [aString casecmp:"alchMol"] == 0 )
		answer = (float)YES;

	else if( [aString casecmp:"mol"] == 0 )
	{
		// The "mol" extention is not save. We have to check the first line
		// for the three magic tokens to see we really have a alchemy file.
		
		inStream = fopen( filename, "r" );
		if( inStream == NULL ) return NO;
		
		theLine = [MiscString new];
		tokenList = nil;
		answer = (float)NO;
		
		if( fgets( lineBuffer, MYBUFFERSIZE, inStream ))
		{
			[theLine setStringValue:lineBuffer];
			tokenList = [theLine tokenize:" \t\n\r" into:nil];
		
			if( [[tokenList objectAt:1] compareStr:"ATOMS,"] == 0 &&
				[[tokenList objectAt:3] compareStr:"BONDS,"] == 0 &&
				[[tokenList objectAt:5] compareStr:"CHARGES,"] == 0 )
				answer = (float)YES;
		}
		[theLine free];
		fclose( inStream );
	}
	else
		answer = (float)NO;
	
	[pathString free];
	[aString free];
	
	return answer;
}

- readBeaker:aBeaker fromFile:(const char*)filename
{	
	// A simple parser for Alchemy 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 read the number of data.

	id		aString;
	int		atomCount;
	int		bondCount;
	
	inStream = fopen( filename, "r" );
	if( inStream == NULL ) return self;
		
	// We will read the first line and extract all the info in there.
	// After that we will read the atoms and bonds.
	
	mol = [BBMolecule new];
	
	theLine = [MiscString new];
	tokenList = nil;

	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];
	}
	
	[aBeaker addMolecule:mol];
	[theLine free];
	fclose( inStream );
	
	return self;
}

- (BOOL)readAtomData:(int)count
{
	// Reading the atom data
	// Right here we will continue to read lines until count atoms have been
	// read. In normal Alchemy files the atoms have to follow without a break.
		
	BOOL 	goAhead = YES;
	id		anAtom;
	id		basicAtom;
	id		atomLibrary;		
	id		token;
	
	atomLibrary = [[NXApp delegate] atomLibrary];

	do
	{
		// Stop after last atom or at files end..
		
		if( count == 0 ) break;

		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.

		token = [tokenList objectAt:0];
		
		if( [tokenList count] < 6 ||
			[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 Alchemy atomtype
		// settings. Therefore we strip of any unwanted character.
	
		token = [tokenList objectAt:1];
		[self checkSymbol:token];

		anAtom = [BBAtom new];
		basicAtom = [atomLibrary findBasicAtomWithSymbol:[token stringValue]];
		[anAtom setMotherAtom:basicAtom];
		[anAtom setXPos:[[tokenList objectAt:2] doubleValue]];
		[anAtom setYPos:[[tokenList objectAt:3] doubleValue]];
		[anAtom setZPos:[[tokenList objectAt:4] doubleValue]];
			
		[mol addAtom:anAtom];
		--count;
		
		// Lets free the list and make the line as read. We don't need it
		// anymore. With count = 0 we have reached the end.

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

- (BOOL)readBondData:(int)count
{
	// Reading the bond data.
	// Right here we will continue to read lines until count bonds are read.
		
	BOOL 	goAhead = YES;
	BBBond * aBond;
	id		atomList;
	id		fromAtom;
	id		toAtom;
	id		token;
	char	type;
	
	atomList = [mol atomList];
	
	do
	{
		// It there are no bonds...let's leave this place..
		
		if( count < 1 ) break;
		
		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] < 4 ||
			[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.
		
		aBond = [BBBond new];
		token = [tokenList objectAt:3];
		
		if( [token compareStr:SINGLEBONDTOKEN] == 0 )
			type = BOND_SINGLE;
		else if( [token compareStr:DOUBLEBONDTOKEN] == 0 )
			type = BOND_DOUBLE;
		else if( [token compareStr:AROMATICBONDTOKEN] == 0 )
			type = BOND_DOUBLE_DELOCATED;
		else
			type = BOND_SINGLE;
			
		fromAtom = [atomList objectAt:[[tokenList objectAt:1] intValue]-1];
		toAtom = [atomList objectAt:[[tokenList objectAt:2] intValue]-1];
		
		[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];
		--count;
		
		// 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
{
	// Here we try to simplify a given atom symbol. 
	// The original symbols have additional information that we might not need
	// or can not cope with right now.
	// This method will change the original string object.

	if( [symbol compareStr:"C2"] == 0 ||			
		[symbol compareStr:"C3"] == 0 ||
		[symbol compareStr:"CAR"] == 0 ||
		[symbol compareStr:"CC"] == 0 ||
		[symbol compareStr:"CK"] == 0 ||
		[symbol compareStr:"CM"] == 0 ||
		[symbol compareStr:"CN"] == 0 ||
		[symbol compareStr:"CQ"] == 0 ||
		[symbol compareStr:"CR"] == 0 ||
		[symbol compareStr:"CT"] == 0 ||
		[symbol compareStr:"CV"] == 0 ||
		[symbol compareStr:"CW"] == 0)
		[symbol setStringValue:"C"];
		
	else if( [symbol compareStr:"H2"] == 0 ||
			 [symbol compareStr:"H3"] == 0 ||
			 [symbol compareStr:"HO"] == 0 ||
			 [symbol compareStr:"HS"] == 0 )
		[symbol setStringValue:"H"];
			
	else if( [symbol compareStr:"NM"] == 0 ||
			 [symbol compareStr:"N2"] == 0 ||
			 [symbol compareStr:"N3"] == 0 ||
			 [symbol compareStr:"N3+"] == 0 ||
			 [symbol compareStr:"NPL3"] == 0 ||
			 [symbol compareStr:"NB"] == 0 ||
			 [symbol compareStr:"NC"] == 0 ||
			 [symbol compareStr:"NT"] == 0 )
		[symbol setStringValue:"N"];

	else if( [symbol compareStr:"O2"] == 0 ||
			 [symbol compareStr:"O3"] == 0 ||
			 [symbol compareStr:"OH"] == 0 )
		[symbol setStringValue:"O"];
			
	else if( [symbol compareStr:"SH"] == 0 )
		[symbol setStringValue:"S"];

	else if( [symbol compareStr:"P3D"] == 0 )
		[symbol setStringValue:"P"];
				
	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.
 *
 *			25.03.94 Added checkSymbol.
 *
 *			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)
 */
