/* BBLook3DFilter.m				 
 *
 * This format filter does handle read/write of molecules in the
 * LOOK3D (.lookMol) 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:    		09.03.1994 (Copyleft)
 * Last modified: 	30.10.1994
 */

#import "BBLook3DFilter.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 MOLNAMETOKEN	"@molname"
#define MOLCOLORTOKEN	"@molcolor"
#define ATOMDATATOKEN	"@atomdata"
#define BONDDATATOKEN	"@binddata"

#define SINGLEBONDTOKEN				"E"
#define SINGLEFIXEDBONDTOKEN		"Ef"
#define DOUBLEBONDTOKEN				"D"
#define TRIPLEBONDTOKEN				"T"
#define DOUBLEDELOCATEDBONDTOKEN	"Dd"

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

- (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 "lookMol" we
	// will do it.
	// Normally we should return (0, 0.5, 1) according to the percentage of
	// data we understand.
	
	id		pathString;
	id		aString;
	float	answer;
	
	pathString = [MiscString new];
	[pathString setPath:filename];
	aString = [pathString fileExtension];
	
	if( [aString casecmp:"lookMol"] == 0 )
			answer = (float)YES;
	else	answer = (float)NO;
	
	[pathString free];
	[aString free];
	
	return answer;
}

- readBeaker:aBeaker fromFile:(const char*)filename
{	
	// A simple parser for LOOK 3D Molecules.
	// The tokenList is only defined as long as its data is not allready
	// 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;
	
	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 wan another one to get read.
	
	mol = [BBMolecule new];
	
	theLine = [MiscString new];
	tokenList = nil;
	
	do
	{
		// Read a new line if no already done. Let the MiscString 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 acording to our lineBuffer
	// If there is nothing to parse we want to see the next line..

	BOOL	goAhead = YES;
	id		token;
	id		aString;
		
	token = [tokenList objectAt:0];
	
	if( [tokenList count] == 0 )
	{
		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	else if( [token compareStr:MOLNAMETOKEN] == 0 )
	{
		aString = [theLine substringFromToken:1 toToken:[tokenList count]-1
									   ofList:tokenList];
		[(BBMolecule *)mol setName:[aString stringValue]];
		[aString free];
		[[tokenList freeObjects] free];
		tokenList = nil;
	}
	else if( [token compareStr:MOLCOLORTOKEN] == 0 )
	{
		[[tokenList freeObjects] free];		
		tokenList = nil;
	}
	else if( [token compareStr:ATOMDATATOKEN] == 0 )
	{
		[[tokenList freeObjects] free];		
		tokenList = nil;
		goAhead = [self readAtomData];
	}
	else if( [token compareStr:BONDDATATOKEN] == 0 ) 
	{
		[[tokenList freeObjects] free];		
		tokenList = nil;
		goAhead = [self readBondData];
	}
	
	// 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
{
	// Reading the atom data
	// Right here we will continue to read lines until a new token comes.
	// As a token we will take everything that does not match a atom definition
		
	BOOL 	goAhead = YES;
	id		anAtom;
	id		basicAtom;
	id		atomLibrary;		
	id		token;
	
	atomLibrary = [[NXApp delegate] atomLibrary];

	do
	{
		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 it has to be a token.
		// Well kind of stupid but it should work for every LOOK mol

		token = [tokenList objectAt:0];
		
		if( [tokenList count] == 0 ||
			[token compareStr:COMMENTTOKEN] == 0 )
		{
			[[tokenList freeObjects] free];
			tokenList = nil;
			continue;
		}
		else if( [tokenList count] != 4 ) break;

		// otherwise lets init a new atom from this line:
		// We have to find the basicAtom too.
		
		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];
		
		// 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.
	// Right here we will continue to read lines until a new token comes.
	// As a token we will take everything that does not match a bond definition
		
	BOOL 	goAhead = YES;
	BBBond * aBond;
	id		atomList;
	id		fromAtom;
	id		toAtom;
	id		token;
	char	type;
	
	atomList = [mol atomList];
	
	do
	{
		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 it has to be a token.
		// Well kind of stupid but it should work for every LOOK mol

		token = [tokenList objectAt:0];

		if( [tokenList count] == 0 ||
			[token compareStr:COMMENTTOKEN] == 0 )
		{
			[[tokenList freeObjects] free];
			tokenList = nil;
			continue;
		}
		else if( [tokenList count] != 3 ) break;

		// otherwise lets init a new bond from this line:
		// We will set the name to some usefull value.
		
		aBond = [BBBond new];
		
		if( [token compareStr:SINGLEBONDTOKEN] == 0 )
			type = BOND_SINGLE;
		else if( [token compareStr:SINGLEFIXEDBONDTOKEN] == 0 )
			type = BOND_SINGLE;
		else if( [token compareStr:DOUBLEBONDTOKEN] == 0 )
			type = BOND_DOUBLE;
		else if( [token compareStr:TRIPLEBONDTOKEN] == 0 )
			type = BOND_TRIPLE;
		else if( [token compareStr:DOUBLEDELOCATEDBONDTOKEN] == 0 )
			type = BOND_DOUBLE_DELOCATED;
		else
			type = BOND_SINGLE;
			
		fromAtom = [atomList objectAt:[[tokenList objectAt:1] intValue]];
		toAtom = [atomList objectAt:[[tokenList objectAt:2] 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];
		
		// 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.
 *
 *			14.03.94 First 'real' filter of this project. Extracted form the
 *					 old FileFormatManager
 *
 *			14.01.94 Made the parsing somewhat nicer and cleaner.
 *
 *			13.01.94 Did completely rewrite the parsing section. Now uses
 *					 MOStrings to parse self.
 *
 *			28.12.93 Created a initBeaker method.
 *
 *
 * Bugs: - Blank lines inside a atom/bond data block will stop the parsing
 *		   and comments will do the same. This might be implemented some day.
 */