/* BBMvtxFilter.m				 
 *
 * This format filter does handle read/write of molecules in the
 * MoleViewer Text (.mvt) 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: 	14.05.1994
 */

#import "BBMvtxFilter.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 ELLIPSEDATATOKEN	"ELLIPSE"

#define SINGLEBONDTOKEN		"S"
#define DOUBLEBONDTOKEN		"D"
#define AROMATICBONDTOKEN	"A"

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

- (float)canReadFile:(const char*)filename
{
	// Let's see if we can handle the file. Well this is a stupid check.
	// Right now we only check the file extention. If its "mvt" we
	// will do it.
	// Normally we should return (0, 0.5, 1) according to the percentage of
	// data we understand. More a fuzzy logic with MAYBE.
	
	id		pathString;
	id		aString;
	float	answer;
	
	pathString = [MiscString new];
	[pathString setPath:filename];
	aString = [pathString fileExtension];
	
	if( [aString casecmp:"mvt"] == 0 )
			answer = (float)YES;
	else	answer = (float)NO;
	
	[pathString free];
	[aString free];
	
	return answer;
}

- readBeaker:aBeaker fromFile:(const char*)filename
{	
	// A simple parser for MolViewer Textformat 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. 

	BOOL 	goAhead = YES;
	id		pathString;
	id		aString;
	
	inStream = fopen( filename, "r" );
	if( inStream == NULL ) return self;
		
	// So lets parse the data get one line and do it. We will
	// not take care of all the settings! We'll say that the current line
	// is already parsed because we want another one to get read.
	
	mol = [BBMolecule new];

	pathString = [MiscString new];
	[pathString setPath:filename];
	aString = [pathString fileBasename];
	[(BBMolecule *)mol setName:[aString stringValue]];
	[aString free];
	[pathString free];

	theLine = [MiscString new];
	tokenList = nil;

	do
	{
		// Read a new line if no already done. Let the MOString create the
		// tokenList. We will only free it.
		
		if( !tokenList )
		{
			if( !fgets( lineBuffer, MYBUFFERSIZE, inStream )) break;
			[theLine setStringValue:lineBuffer];
			tokenList = [theLine tokenize:" \t\n\r" into:nil];
		}
		// Now lets read till we have to stop. 
		
		goAhead = [self readNewLine];
	}
	while( goAhead );
	
	[aBeaker addMolecule:mol];
	[theLine free];
	fclose( inStream );
	
	return self;
}

- (BOOL)readNewLine
{
	// Lets check the tokens..skip empty lines and comments
	// theLine and tokenList are already set according to our lineBuffer
	// If there is nothing to parse we want to see the next line..
	// 
	// This algorythm is stupid. It only checks for the two-token-line and
	// says that this is the line with the atom/bond counts. This will work
	// But is not really nice!
	// Although we check for ELLIPSE...we don't read the data.

	BOOL	goAhead = YES;
	id		token;
	int		atomCount;
	int		bondCount;
		
	token = [tokenList objectAt:0];
	
	if( [tokenList count] == 0 )
	{
		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	else if( [tokenList count] == 2 )
	{
		// First we'll take the atom count and then we take the bond count.
		// If reading atoms was successful we will read the bonds.
		// We won't continue after that!
		
		atomCount = [[tokenList objectAt:0] intValue];
		bondCount = [[tokenList objectAt:1] intValue];
		[[tokenList freeObjects] free];
		tokenList = nil;
		
		if( [self readAtomData:atomCount] )
			[self readBondData:bondCount];
				
		goAhead = NO;
	}
	else if( [token compareStr:ELLIPSEDATATOKEN] == 0 )
	{
		[[tokenList freeObjects] free];		
		tokenList = nil;
		
		// We should read the ellipse info here.
	}

	// Currently we do not evaluate every possible token.
	// We will simple ingore everything else and just free the tokenList
	// if this has not already happened.
	
	else
	{
		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	return goAhead;
}

- (BOOL)readAtomData:(int)count
{
	// Reading the atom data
	// Right here we will continue to read lines until count atoms have been
	// read. In normal MVTX 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
	{
		// Leave this place at files end or when there are no more atoms
		// to be read.
		
		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 4 elements per line.

		token = [tokenList objectAt:0];
		
		if( [tokenList count] < 4 ||
			[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 MolViewer atomtype
		// settings.
		
		if( [token compareStr:"C*"] == 0 ||
			[token compareStr:"CA"] == 0 ||
			[token compareStr:"CB"] == 0 ||
			[token compareStr:"CC"] == 0 ||
			[token compareStr:"CK"] == 0 ||
			[token compareStr:"CM"] == 0 ||
			[token compareStr:"CN"] == 0 ||
			[token compareStr:"CQ"] == 0 ||
			[token compareStr:"CR"] == 0 ||
			[token compareStr:"CT"] == 0 ||
			[token compareStr:"CV"] == 0 ||
			[token compareStr:"CW"] == 0)
			[token setStringValue:"C"];
		
		else if( [token compareStr:"H2"] == 0 ||
				 [token compareStr:"H3"] == 0 ||
				 [token compareStr:"HO"] == 0 ||
				 [token compareStr:"HS"] == 0 )
			[token setStringValue:"H"];
			
		else if( [token compareStr:"NM"] == 0 ||
				 [token compareStr:"N2"] == 0 ||
				 [token compareStr:"N3"] == 0 ||
				 [token compareStr:"N*"] == 0 ||
				 [token compareStr:"NA"] == 0 ||
				 [token compareStr:"NB"] == 0 ||
				 [token compareStr:"NC"] == 0 ||
				 [token compareStr:"NT"] == 0 )
			[token setStringValue:"N"];

		else if( [token compareStr:"O2"] == 0 ||
				 [token compareStr:"OS"] == 0 ||
				 [token compareStr:"OH"] == 0 )
			[token setStringValue:"O"];
			
		else if( [token compareStr:"SH"] == 0 )
			[token setStringValue:"S"];
		
		anAtom = [BBAtom new];
		basicAtom = [atomLibrary findBasicAtomWithSymbol:[token stringValue]];
		[anAtom setMotherAtom:basicAtom];
		[anAtom setXPos:[[tokenList objectAt:1] doubleValue]];
		[anAtom setYPos:[[tokenList objectAt:2] doubleValue]];
		[anAtom setZPos:[[tokenList objectAt:3] doubleValue]];
			
		[mol addAtom:anAtom];
		--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;
}

- (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
	{
		// We will leave this place when there are no more bonds to be read.
		// After that left read the new line and quite at files end.
		
		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] < 3 ||
			[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:2];
		
		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:0] intValue]];
		toAtom = [atomList objectAt:[[tokenList objectAt:1] intValue]];
		
		[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;
}

- 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 could check the MVTX Magic word instead of relying on the file-
 *		   extension.
 *
 *		 - Does not handle Eliptic, charge and labeling info (15.03.94)
 */
