/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.nbio.structure.align.xml;

import org.biojava.nbio.structure.Atom;
import org.biojava.nbio.structure.StructureException;
import org.biojava.nbio.structure.align.model.AFPChain;
import org.biojava.nbio.structure.align.util.AlignmentTools;
import org.biojava.nbio.structure.jama.Matrix;
import org.biojava.nbio.core.util.PrettyXMLWriter;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Locale;



public class AFPChainXMLConverter {


	/** Convert an afpChain to a simple XML representation
	 *
	 * @param afpChain
	 * @return XML representation of the AFPCHain
	 */
	public synchronized static String toXML(AFPChain afpChain, Atom[] ca1, Atom[]ca2) throws IOException{
		StringWriter result = new StringWriter();
		toXML(afpChain,result,ca1,ca2);
		return result.toString();
	}

	/** Write the XML representation to a StringWriter
	 *
	 * @param afpChain
	 * @param swriter
	 * @throws IOException
	 */
	public synchronized static void toXML(AFPChain afpChain, StringWriter swriter,Atom[] ca1, Atom[]ca2) throws IOException{

		PrintWriter writer = new PrintWriter(swriter);
		PrettyXMLWriter xml = new PrettyXMLWriter(writer);

		xml.openTag("AFPChain");

		printXMLHeader(xml,afpChain);


		// that is the initial alignment...
		// we don't serialize that at the present.
		//int[] blockResSize = afpChain.getBlockResSize();
		//int[][][] blockResList = afpChain.getBlockResList();


		// get the alignment blocks
		int blockNum = afpChain.getBlockNum();
		//int[] optLen       = afpChain.getOptLen();
		//int[] blockSize    = afpChain.getBlockSize();
		for(int bk = 0; bk < blockNum; bk ++) {

			xml.openTag("block");

			printXMLBlockHeader(xml,afpChain, bk);

			if ( ca1 == null || ca2 == null) {
				try {
					printXMLEQRKnownPositions(xml,afpChain,bk);
				} catch (StructureException ex ){
					throw new IOException(ex.getMessage());
				}
			}
			else
				printXMLEQRInferPositions(xml, afpChain,bk,ca1,ca2);

			printXMLMatrixShift(xml, afpChain, bk);

			xml.closeTag("block");
		}

		xml.closeTag("AFPChain");

		writer.close();


	}



	private static void printXMLEQRKnownPositions(PrettyXMLWriter xml,
			AFPChain afpChain, int blockNr) throws IOException, StructureException{
		int[] optLen       = afpChain.getOptLen();


		String[][][] pdbAln = afpChain.getPdbAln();
		if ( pdbAln == null){
			throw new StructureException("Can't convert to XML without known the PDB coordinates. Please provide Ca atoms and call toXML(afpChain,ca1, ca2)");
		}

		for ( int eqrNr = 0 ; eqrNr < optLen[blockNr] ; eqrNr++ ){

			String pdbResnum1 = pdbAln[blockNr][0][eqrNr];
			String pdbResnum2 = pdbAln[blockNr][1][eqrNr];

			//System.out.println(eqrNr + " got resnum: " + pdbResnum1 + " " + pdbResnum2);

			String[] spl1 = pdbResnum1.split(":");
			String[] spl2 = pdbResnum2.split(":");

			String chain1 = spl1[0];
			String pdbres1 = spl1[1];

			String chain2 = spl2[0];
			String pdbres2 = spl2[1];

			xml.openTag("eqr");
			xml.attribute("eqrNr", String.valueOf(eqrNr));
			xml.attribute("pdbres1",pdbres1);
			xml.attribute("chain1", chain1);
			xml.attribute("pdbres2",pdbres2);
			xml.attribute("chain2", chain2);
			xml.closeTag("eqr");
		}
	}


	public static void printXMLEQRInferPositions(PrettyXMLWriter xml,
			AFPChain afpChain, int bk, Atom[] ca1, Atom[] ca2)  throws IOException{

		int[] optLen       = afpChain.getOptLen();

		if ( optLen == null)
			return;

		int[][][] optAln   = afpChain.getOptAln();


		for ( int pos=0;pos< optLen[bk];pos++){
			int pos1 = optAln[bk][0][pos];
			int pos2 = optAln[bk][1][pos];
			xml.openTag("eqr");
			xml.attribute("eqrNr", String.valueOf(pos));
			xml.attribute("pdbres1",ca1[pos1].getGroup().getResidueNumber().toString());
			xml.attribute("chain1", ca1[pos1].getGroup().getChain().getName());
			xml.attribute("pdbres2",ca2[pos2].getGroup().getResidueNumber().toString());
			xml.attribute("chain2", ca2[pos2].getGroup().getChain().getName());

			xml.closeTag("eqr");
			//System.out.println("aligned position: " + pos1  + ":" + pos2 +
			//" pdbresnum " + ca1[pos1].getGroup().getResidueNumber().toString() + " " +
			//ca1[pos1].getParent().getPDBName()+":" +
			//ca2[pos2].getGroup().getResidueNumber().toString() + " " + ca2[pos2].getParent().getPDBName());

		}

	}


	private static void printXMLBlockHeader(PrettyXMLWriter xml,
			AFPChain afpChain,int blockNr) throws IOException{

		int bk = blockNr;
		int[] blockSize    = afpChain.getBlockSize();
		//if ( blockSize[bk] == 0) {
		//	return;
		//}
		int[] blockGap     = afpChain.getBlockGap();

		double[]blockScore = afpChain.getBlockScore();
		double[] blockRmsd = afpChain.getBlockRmsd();

		xml.attribute("blockNr", String.valueOf(bk));
		xml.attribute("blockSize", String.valueOf(blockSize[bk]));
		xml.attribute("blockScore", String.format(Locale.US, "%5.2f",blockScore[bk]).trim());
		xml.attribute("blockRmsd", String.format(Locale.US, "%5.2f",blockRmsd[bk]).trim());
		xml.attribute("blockGap", String.valueOf(blockGap[bk]));

	}


	private static void printXMLMatrixShift(PrettyXMLWriter xml,
			AFPChain afpChain, int blockNr)  throws IOException {

		Matrix[] ms     = afpChain.getBlockRotationMatrix();
		if ( ms == null || ms.length == 0)
			return;

		Matrix matrix = ms[blockNr];
		if ( matrix == null)
			return;
		xml.openTag("matrix");


		for (int x=0;x<3;x++){
			for (int y=0;y<3;y++){
				String key = "mat"+(x+1)+(y+1);
				xml.attribute(key,String.format(Locale.US, "%.6f",matrix.get(x,y)));
			}
		}
		xml.closeTag("matrix");

		Atom[]   shifts = afpChain.getBlockShiftVector();
		Atom shift = shifts[blockNr];
		xml.openTag("shift");
		xml.attribute("x", String.format(Locale.US, "%.3f",shift.getX()));
		xml.attribute("y", String.format(Locale.US, "%.3f",shift.getY()));
		xml.attribute("z", String.format(Locale.US, "%.3f",shift.getZ()));
		xml.closeTag("shift");

	}


	public static String toXML(AFPChain afpChain) throws IOException{

		return toXML(afpChain, null, null);
	}


	public static void printXMLHeader(PrettyXMLWriter xml, AFPChain afpChain) throws IOException{
		xml.attribute("name1", afpChain.getName1());
		xml.attribute("name2", afpChain.getName2());
		xml.attribute("method", afpChain.getAlgorithmName());
		xml.attribute("version" , afpChain.getVersion());
		xml.attribute("alnLength", afpChain.getAlnLength() + "");
		xml.attribute("blockNum", afpChain.getBlockNum() + "");
		xml.attribute("gapLen", afpChain.getGapLen() + "");
		xml.attribute("optLength", afpChain.getOptLength() + "");
		xml.attribute("totalLenIni", afpChain.getTotalLenIni() + "");

		xml.attribute("alignScore", String.format(Locale.US, "%5.2f", afpChain.getAlignScore() ).trim());
		xml.attribute("chainRmsd",  String.format(Locale.US, "%5.2f", afpChain.getChainRmsd() ).trim());
		xml.attribute("identity",String.format(Locale.US, "%5.4f", afpChain.getIdentity() ).trim());
		xml.attribute("normAlignScore", String.format(Locale.US, "%5.2f",afpChain.getNormAlignScore()).trim());
		xml.attribute("probability", String.format(Locale.US, "%.2e", afpChain.getProbability() ).trim());
		xml.attribute("similarity", String.format(Locale.US, "%5.4f", afpChain.getSimilarity() ).trim());

		xml.attribute("similarity1", afpChain.getCoverage1() + "");
		xml.attribute("similarity2", afpChain.getCoverage2() + "");
		xml.attribute("totalRmsdIni", String.format(Locale.US, "%5.2f",afpChain.getTotalRmsdIni() ).trim());
		xml.attribute("totalRmsdOpt", String.format(Locale.US, "%5.2f",afpChain.getTotalRmsdOpt() ).trim());
		xml.attribute("ca1Length", afpChain.getCa1Length()+"");
		xml.attribute("ca2Length", afpChain.getCa2Length()+"");
		xml.attribute("afpNum",afpChain.getAfpSet().size()+"");
		xml.attribute("alignScoreUpdate",String.format(Locale.US, "%5.2f",afpChain.getAlignScoreUpdate()).trim());
		xml.attribute("time", String.format("%d",afpChain.getCalculationTime()));
		if ( afpChain.getTMScore() != -1){
			xml.attribute("tmScore", String.format(Locale.US, "%.2f",afpChain.getTMScore()));
		}

		// test if alignment is CP:
		if ( ! AlignmentTools.isSequentialAlignment(afpChain,false)) {
			xml.attribute("cp","true");
		}
	}
}
