/* Copyright 2003  Alexander V. Diemand

    This file is part of MolTalk.

    MolTalk is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    MolTalk is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MolTalk; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

/* vim: set filetype=objc: */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "privateStructure.oh"
#include "Chain.oh"
#include "privateChain.oh"
#include "ChainFactory.oh"
#include "Residue.oh"
#include "Atom.oh"
#include "Stream.oh"
#include "String.oh"


static NSString *mkTextDate (NSCalendarDate *caldate);



@implementation Structure (Private)


-(void)writePDBHeaderTo:(Stream *)strout	//@nodoc
{
	char buffer[82];
	int i,counter;
	char end_of_line[] = { 10, 0 };
	NSRange range;

	/* HEADER */
	NSString *t_date = mkTextDate (date);
	snprintf(buffer,51,"HEADER    %s                                         ",[header cString]);
	snprintf(buffer+50,10,"%s          ",[t_date cString]);
	snprintf(buffer+59,11,"   %s      ",[pdbcode cString]);
	buffer[70]='\0';
	[strout writeCString: buffer];
	[strout writeCString: end_of_line];

	/* TITLE */
	i = [title length];
	if (i <= 60)
	{
		[strout writeString: @"TITLE     "];
		[strout writeString: title];
		i = 60-i;
		buffer[i+1]='\0';
		for (; i>=0; i--)
		{
			sprintf(buffer+i," ");
		}
		[strout writeCString: buffer];
		[strout writeCString: end_of_line];
	} else {
		counter = 2;
		range.location = 0;
		range.length = 60;
		[strout writeString: @"TITLE     "];
		[strout writeString: [title substringWithRange: range]];
		[strout writeCString: end_of_line];
		i -= 60;
		range.location += 60;
		while (i > 0)
		{
			[strout writeString: @"TITLE   "];
			if (i<60)
			{
				range.length = i;
			}
			snprintf(buffer,63,"% 2d %s                                                               ",counter,[[title substringWithRange:range]cString]);
			buffer[62]='\0';
			[strout writeCString: buffer];
			i -= range.length;
			[strout writeCString: end_of_line];
		}
	}


	/* COMPND */

	/* SOURCE */

	/* EXPDTA */

	/* REMARK 2: resolution */

	/* REMARK 4: acceptance of PDB file format V 2.2/1996 */
	[strout writeString: @"REMARK   4                                                            "];
	[strout writeCString: end_of_line];
	[strout writeString: [NSString stringWithFormat:@"REMARK   4 %@ COMPLIES WITH FORMAT V. 2.2, 16-DEC-1996              ",[self pdbcode]]];
	
	[strout writeCString: end_of_line];

	
	/* write MODRES entries for all modified residues */
	Chain *chain;
	Residue *residue;
	NSEnumerator *e_res;
	NSEnumerator *e_chain = [self allChains];
	while ((chain = [e_chain nextObject]))
	{
		e_res = [chain allResidues];
		while ((residue = [e_res nextObject]))
		{
			if ([residue isModified])
			{
				char tbuffer[128];
				memset(buffer,32,80);
				buffer[81]='\0';
				buffer[80]='\n';
				buffer[0]='M';buffer[1]='O';buffer[2]='D';buffer[3]='R';buffer[4]='E';buffer[5]='S';
				buffer[16] = [chain code]; /* chain identifier */
				[pdbcode getCString: tbuffer maxLength: 4];
				/* pdb code */
				buffer[7]=tbuffer[0];buffer[8]=tbuffer[1];buffer[9]=tbuffer[2];buffer[10]=tbuffer[3];
				/* residue name */
				[[residue name] getCString: tbuffer maxLength: 3];
				buffer[12]=tbuffer[0];buffer[13]=tbuffer[1];buffer[14]=tbuffer[2];
				/* residue number */
				snprintf(tbuffer, 10, "% 4u    ", [[residue number] intValue]);
				for (i=0;i<4;i++)
				{
					buffer[18+i]=tbuffer[i];
				}
				buffer[22]=[residue subcode];
				/* residue modname */
				[[residue modname] getCString: tbuffer maxLength: 3];
				buffer[24]=tbuffer[0];buffer[25]=tbuffer[1];buffer[26]=tbuffer[2];
				/* modification description */
				[[residue moddescription] getCString: tbuffer maxLength: 41];
				for (i=0; (i<[[residue moddescription]length] && i<41); i++)
				{
					buffer[29+i]=tbuffer[i];
				}
				[strout writeCString: buffer];
			}
		}
	}
}


-(void)writePDBConectTo:(Stream*)strout	//@nodoc
{

	Chain *chain;
	NSEnumerator *e_chain = [chains objectEnumerator];
	while ((chain = [e_chain nextObject]))
	{
		NSEnumerator *e_res = [chain allResidues];
		Residue *residue;
		while ((residue = [e_res nextObject]))
		{
			[self writePDBConectResidue: residue to: strout];
		}
		e_res = [chain allHeterogens];
		while ((residue = [e_res nextObject]))
		{
			[self writePDBConectResidue: residue to: strout];
		}
	}
}


-(void)writePDBConectResidue:(Residue*)residue to:(Stream*)strout	//@nodoc
{
	NSEnumerator *e_atm = [residue allAtoms];
	Atom *atom;
	while ((atom = [e_atm nextObject]))
	{
		unsigned int i=0;
		NSEnumerator *e_atm2 = [atom allBondedAtoms];
		Atom *atom2;
		char buffer[82];
		while ( (atom2 = [e_atm2 nextObject]) 
			&& !([[atom name] isEqualToString:@"N"] && [[atom2 name] isEqualToString:@"C"])
			&& ([[atom number] compare: [atom2 number]] == NSOrderedDescending) )
			/* we don't write covalent bonds N-C betweenn residues
			 * we don't write bonds twice (atom bonds to atom2, and
			 * atom2 bonds to atom)  */
		{
			if (i==0)
			{
				memset(buffer,32,82);
				buffer[81]='\0';
				buffer[80]='\n';
				sprintf(buffer,"CONECT% 5u",[[atom number] intValue]);
				buffer[11]=' ';
			}
			snprintf(buffer+(i*5)+11,6,"% 5u",[[atom2 number] intValue]);
			i++;
			buffer[(i*5)+11]=' ';
			if (i>=4)
			{
				i=0;
				[strout writeCString: buffer];
			}
		}
		if (i>0)
		{
			[strout writeCString: buffer];
		}
	}
}


-(void)writePDBChain:(Chain*)chain to:(Stream *)strout fromSerial:(unsigned int*)serial	//@nodoc
{
	Residue *residue;
	NSEnumerator *e_res = [chain allResidues];
	Residue *lastres = nil;
	while ((residue = [e_res nextObject]))
	{
		if ([residue isModified])
		{
			[self writePDBHeterogen: residue inChain: chain to: strout fromSerial: serial];
		} else {
			[self writePDBResidue: residue inChain: chain to: strout fromSerial: serial];
		}
		lastres=residue;
	} /* all residues */
	
	/* write out TER */
	if (lastres != nil)
	{ /* only if there is an amino acid in this chain */
		char buffer[82];
		char tbuffer[10];
		memset(buffer,32,80);
		buffer[81]='\0';
		buffer[80]='\n';
		buffer[0]='T';buffer[1]='E';buffer[2]='R';
		buffer[21] = [chain code]; /* chain identifier */
		[[lastres name] getCString: tbuffer maxLength: 3]; /* residue name */
		buffer[17] = tbuffer[0]; buffer[18] = tbuffer[1]; buffer[19] = tbuffer[2];
		snprintf(tbuffer, 10, "% 4u    ", [[lastres number] intValue]); /* residue number */
		buffer[22]=tbuffer[0]; buffer[23]=tbuffer[1]; buffer[24]=tbuffer[2]; buffer[25]=tbuffer[3];
		snprintf(buffer+6,6,"% 5u",*serial); buffer[11]=' '; *serial = *serial + 1;
		[strout writeCString: buffer];
	}
	
	e_res = [chain allHeterogens];
	while ((residue = [e_res nextObject]))
	{
		[self writePDBHeterogen: residue inChain: chain to: strout fromSerial: serial];
	} /* all heterogens */
	
	e_res = [chain allSolvent];
	while ((residue = [e_res nextObject]))
	{
		[self writePDBHeterogen: residue inChain: chain to: strout fromSerial: serial];
	} /* all solvent */
	
}


-(void)writePDBResidue:(Residue*)residue inChain:(Chain*)chain to:(Stream*)strout fromSerial:(unsigned int*)serial	//@nodoc
{
	NSString *t_str;
	int t_chrg;
	char buffer[82];
	char tbuffer[10];
	char tbuffer2[5];
	memset(buffer,32,80);
	buffer[81]='\0';
	buffer[80]='\n';
	buffer[0]='A';buffer[1]='T';buffer[2]='O';buffer[3]='M';
	buffer[21] = [chain code]; /* chain identifier */
	[[residue name] getCString: tbuffer maxLength: 3]; /* residue name */
	buffer[17] = tbuffer[0]; buffer[18] = tbuffer[1]; buffer[19] = tbuffer[2];
	snprintf(tbuffer, 10, "% 4u    ", [[residue number] intValue]); /* residue number */
	buffer[22]=tbuffer[0]; buffer[23]=tbuffer[1]; buffer[24]=tbuffer[2]; buffer[25]=tbuffer[3];
	buffer[56]='1';buffer[57]='.';buffer[58]='0';buffer[59]='0'; /* occupancy */

	NSEnumerator *e_atms = [residue allAtoms];
	Atom *atom;
	while ((atom = [e_atms nextObject]))
	{
		double temperature,x,y,z;
		temperature = [atom temperature];
		x = [atom x];
		y = [atom y];
		z = [atom z];
		[atom setNumber: *serial];
		snprintf(buffer+6,6,"% 5u",*serial); *serial = *serial + 1;
		buffer[11]=' ';
		tbuffer[0]=32; tbuffer[1]=32; tbuffer[2]=32; tbuffer[3]=32;
		[[atom name] getCString: tbuffer maxLength: 4];
		if (tbuffer[1]==0) tbuffer[1]=32;
		if (tbuffer[2]==0) tbuffer[2]=32;
		if (tbuffer[3]==0) tbuffer[3]=32;
		[[atom elementName] getCString: tbuffer2 maxLength: 2];
		/* elementName is prefix? */
		if (tbuffer[0]==tbuffer2[0])
		{
			if (tbuffer2[1]==0) 
			{  // length == 1
				buffer[12]=32; buffer[13]=tbuffer[0];
				buffer[14]=tbuffer[1]; buffer[15]=tbuffer[2];
			} else {
			   // length == 2
				buffer[12]=tbuffer[0]; buffer[13]=tbuffer[1];
				buffer[14]=tbuffer[2]; buffer[15]=tbuffer[3];
			}
		} else {
			buffer[12]=tbuffer[0]; buffer[13]=tbuffer[1];
			buffer[14]=tbuffer[2]; buffer[15]=tbuffer[3];
		}
		sprintf(buffer+30,"% 8.3f",x);
		sprintf(buffer+38,"% 8.3f",y);
		sprintf(buffer+46,"% 8.3f",z);
		buffer[54]=' ';
		sprintf(buffer+60,"%6.2f",temperature);
		buffer[66]=' ';
		t_str = [atom elementName];
		buffer[76] = ' ';
		buffer[77] = ' ';
		if ([t_str length]==1)
		{
			buffer[77] = (char)[t_str characterAtIndex:0];
		} else {
			buffer[76] = (char)[t_str characterAtIndex:0];
			buffer[77] = (char)[t_str characterAtIndex:1];
		}
		buffer[78] = ' ';
		buffer[79] = ' ';
		t_chrg = [atom charge];
		if (t_chrg != 0)
		{
			if (t_chrg < 0)
			{
				buffer[78] = (char)(0-t_chrg+48);
				buffer[79] = '-';
			} else {
				buffer[78] = (char)(t_chrg+48);
				buffer[79] = '+';
			}
		}
		[strout writeCString: buffer];
	}
}


-(void)writePDBHeterogen:(Residue*)residue inChain:(Chain*)chain to:(Stream*)strout fromSerial:(unsigned int*)serial	//@nodoc
{
	NSString *t_str;
	int t_chrg;
	char buffer[82];
	char tbuffer[10];
	char tbuffer2[5];
	memset(buffer,32,82);
	buffer[81]='\0';
	buffer[80]='\n';
	buffer[0]='H';buffer[1]='E';buffer[2]='T';buffer[3]='A';buffer[4]='T';buffer[5]='M';
	buffer[21] = [chain code]; /* chain identifier */
	[[residue name] getCString: tbuffer maxLength: 3]; /* residue name */
	buffer[17] = tbuffer[0]; buffer[18] = tbuffer[1]; buffer[19] = tbuffer[2];
	snprintf(tbuffer, 10, "% 4u    ", [[residue number] intValue]); /* residue number */
	buffer[22]=tbuffer[0]; buffer[23]=tbuffer[1]; buffer[24]=tbuffer[2]; buffer[25]=tbuffer[3];
	buffer[56]='1';buffer[57]='.';buffer[58]='0';buffer[59]='0'; /* occupancy */

	NSEnumerator *e_atms = [residue allAtoms];
	Atom *atom;
	while ((atom = [e_atms nextObject]))
	{
		double temperature,x,y,z;
		temperature = [atom temperature];
		x = [atom x];
		y = [atom y];
		z = [atom z];
		[atom setNumber: *serial];
		snprintf(buffer+6,6,"% 5u",*serial); *serial = *serial + 1;
		buffer[11]=' ';
		tbuffer[0]=32; tbuffer[1]=32; tbuffer[2]=32; tbuffer[3]=32;
		[[atom name] getCString: tbuffer maxLength: 4];
		if (tbuffer[1]==0) tbuffer[1]=32;
		if (tbuffer[2]==0) tbuffer[2]=32;
		if (tbuffer[3]==0) tbuffer[3]=32;
		[[atom elementName] getCString: tbuffer2 maxLength: 2];
		/* elementName is prefix? */
		if (tbuffer[0]==tbuffer2[0])
		{
			if (tbuffer2[1]==0) 
			{  // length == 1
				buffer[12]=32; buffer[13]=tbuffer[0];
				buffer[14]=tbuffer[1]; buffer[15]=tbuffer[2];
			} else {
			   // length == 2
				buffer[12]=tbuffer[0]; buffer[13]=tbuffer[1];
				buffer[14]=tbuffer[2]; buffer[15]=tbuffer[3];
			}
		} else {
			buffer[12]=tbuffer[0]; buffer[13]=tbuffer[1];
			buffer[14]=tbuffer[2]; buffer[15]=tbuffer[3];
		}
		sprintf(buffer+30,"% 8.3f",x);
		sprintf(buffer+38,"% 8.3f",y);
		sprintf(buffer+46,"% 8.3f",z);
		buffer[54]=' ';
		sprintf(buffer+60,"%6.2f",temperature);
		buffer[66]=' ';
		buffer[76] = ' ';
		buffer[77] = ' ';
		t_str = [atom elementName];
		if ([t_str length]==1)
		{
			buffer[77] = (char)[t_str characterAtIndex:0];
		} else {
			buffer[76] = (char)[t_str characterAtIndex:0];
			buffer[77] = (char)[t_str characterAtIndex:1];
		}
		buffer[78] = ' ';
		buffer[79] = ' ';
		t_chrg = [atom charge];
		if (t_chrg != 0)
		{
			if (t_chrg < 0)
			{
				buffer[78] = (char)(0-t_chrg+48);
				buffer[79] = '-';
			} else {
				buffer[78] = (char)(t_chrg+48);
				buffer[79] = '+';
			}
		}
		[strout writeCString: buffer];
	}
}


-(Chain*)mkChain:(NSNumber*)p_chain	//@nodoc
{
	Chain *chain = [self getChain:p_chain];
	if (chain == nil)
	{
		chain = [ChainFactory newChainWithCode:[p_chain charValue]];
		[chains addObject:chain];
		[chain setStructure:self];
	}
	return chain;
}


-(void)expdata:(int)p_expdata	//@nodoc
{
	expdata = p_expdata;
}


-(void)resolution:(float)p_resolution	//@nodoc
{
	resolution = p_resolution;
}


-(void)header:(NSString*)p_header	//@nodoc
{
	RETAIN(p_header);
	if (header)
	{
		RELEASE(header);
	}
	header = p_header;
}


-(void)title:(NSString*)p_title	//@nodoc
{
	RETAIN(p_title);
	if (title)
	{
		RELEASE(title);
	}
	title = p_title;
}


-(void)keywords:(NSString*)p_kw	//@nodoc
{
	if (!p_kw)
	{
		return;
	}
	if (keywords)
	{
		RELEASE(keywords);
	}
	NSMutableArray *t_arr = [NSMutableArray new];
	
	char *t_str = (char*)[p_kw lossyCString];
	int slen = strlen(t_str);
	int lasti=0;
	int i;
	for (i=0; i<slen; i++)
	{
		if (t_str[i]=='\0')
		{
			break;
		}
		// ',' is the field delimiter
		if (t_str[i]==',')
		{
			t_str[i]='\0';
			[t_arr addObject:[[NSString stringWithCString:(t_str+lasti)]clipleft]];
			lasti = i+1;
		}
	}
	if (i>0 && lasti != i)
	{
		[t_arr addObject:[[NSString stringWithCString:(t_str+lasti)]clipleft]];
	}
	keywords = [NSArray arrayWithArray: t_arr];
	RETAIN(keywords);
}


-(void)date:(NSCalendarDate*)p_date	//@nodoc
{
	RETAIN(p_date);
	if (date)
	{
		RELEASE(date);
	}
	date = p_date;
}


-(void)revdate:(NSCalendarDate*)p_date	//@nodoc
{
	RETAIN(p_date);
	if (revdate)
	{
		RELEASE(revdate);
	}
	revdate = p_date;
}


-(void)pdbcode:(NSString*)p_pdbcode	//@nodoc
{
	RETAIN(p_pdbcode);
	if (pdbcode)
	{
		RELEASE(pdbcode);
	}
	pdbcode = p_pdbcode;
}


-(void)hetname:(id)name forKey:(NSString*)key	//@nodoc
{
	if (hetnames==nil)
	{
		hetnames = RETAIN([NSMutableDictionary new]);
	}
	[hetnames setObject:name forKey:key];
}


@end



/* convert NSCalendarDate to the format DD-MMM-YY, where MMM is a textual 
 * representation of a month
 */
NSString *mkTextDate (NSCalendarDate *caldate)
{
	NSString *t_m;
	int month=1;
	int year=2000;
	int day=1;

	if (caldate)
	{
		month = [caldate monthOfYear];
		day = [caldate dayOfMonth];
		year = [caldate yearOfCommonEra];
	}
	if (year >= 2000)
	{
		year -= 2000;
	} else {
		year -= 1900;
	}

	switch (month)
	{
	case 1:  t_m = @"JAN"; break;
	case 2:  t_m = @"FEB"; break;
	case 3:  t_m = @"MAR"; break;
	case 4:  t_m = @"APR"; break;
	case 5:  t_m = @"MAI"; break;
	case 6:  t_m = @"JUN"; break;
	case 7:  t_m = @"JUL"; break;
	case 8:  t_m = @"AUG"; break;
	case 9:  t_m = @"SEP"; break;
	case 10:  t_m = @"OCT"; break;
	case 11:  t_m = @"NOV"; break;
	case 12:  t_m = @"DEC"; break;
	default: t_m = @"XXX";
	}

	return [NSString stringWithFormat:@"%02d-%@-%02d",day,t_m,year];
}


