/*
 *                    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/
 *
 * created at Apr 26, 2008
 */
package org.biojava.nbio.structure.test;

import org.biojava.nbio.structure.*;
import org.biojava.nbio.structure.io.FileParsingParameters;
import org.biojava.nbio.structure.io.PDBFileParser;
import org.biojava.nbio.structure.io.mmcif.MMcifParser;
import org.biojava.nbio.structure.io.mmcif.SimpleMMcifConsumer;
import org.biojava.nbio.structure.io.mmcif.SimpleMMcifParser;
import org.junit.Test;
import static org.junit.Assert.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

public class MMcifTest {

	private static boolean headerOnly;

	@Test
	public void testLoad() throws IOException {

		headerOnly = false;

		doTestLoad();

	}

	@Test
	public void testLoadHeaderOnly() throws IOException {

		headerOnly = true;

		doTestLoad();

	}

	private void doTestLoad() throws IOException {
		// a structure with microheterogeneity
		//comparePDB2cif("2CI1","A");

		// test a simple protein
		comparePDB2cif("5pti","A");

		// test a protein with modified residues
		comparePDB2cif("1a4w","L");
		comparePDB2cif("1a4w","H");
		comparePDB2cif("1a4w","I");
		//non-standard encoded amino acid
		comparePDB2cif("1fdo","A");

		// test a DNA binding protein
		comparePDB2cif("1j59","A");
		//comparePDB2cif("1j59","B");
		//comparePDB2cif("1j59","C");
		//comparePDB2cif("1j59","D");
		comparePDB2cif("1j59","E");
		//comparePDB2cif("1j59","F");

		// test a NMR protein
		comparePDB2cif("2kc9","A");
	}

	private void comparePDB2cif(String id, String chainId) throws IOException {
		String fileName = "/"+id+".cif";
		InputStream inStream = this.getClass().getResourceAsStream(fileName);
		assertNotNull("Could not find file " + fileName + ". Config problem?" , inStream);

		MMcifParser parser = new SimpleMMcifParser();

		SimpleMMcifConsumer consumer = new SimpleMMcifConsumer();
		FileParsingParameters params = new FileParsingParameters();
		params.setHeaderOnly(headerOnly);
		consumer.setFileParsingParameters(params);


		parser.addMMcifConsumer(consumer);
		parser.parse(new BufferedReader(new InputStreamReader(inStream)));
		// remove to avoid memory leaks
		parser.clearConsumers();
		Structure cifStructure = consumer.getStructure();
		assertNotNull(cifStructure);


		// load the PDB file via the PDB parser
		Structure pdbStructure = null;
		InputStream pinStream = this.getClass().getResourceAsStream("/"+id+".pdb");
		assertNotNull(inStream);

		PDBFileParser pdbpars = new PDBFileParser();
		pdbpars.setFileParsingParameters(params);


		pdbStructure = pdbpars.parsePDBFile(pinStream) ;


		assertNotNull(pdbStructure);

		// now compare the results


		// chech NMR data
		assertEquals(id + ": the isNMR flag is not the same!", pdbStructure.isNmr(), cifStructure.isNmr());

		if ( pdbStructure.isNmr()){
			assertEquals(id + ": the nr of NMR models is not the same!", pdbStructure.nrModels(), pdbStructure.nrModels());
			checkNMR(pdbStructure);
			checkNMR(cifStructure);
		}

		//System.out.println(pdbStructure);
		//System.out.println(cifStructure);

		// compare amino acids in chain 1:
		Chain a_pdb = pdbStructure.getPolyChainByPDB(chainId);
		Chain a_cif = cifStructure.getPolyChainByPDB(chainId);
		//System.out.println(a_pdb.getAtomGroups());

		//System.out.println(id + "_" + chainName + " pdb atom groups: " + a_pdb.getAtomGroups(GroupType.AMINOACID).size());
		//System.out.println(id + "_" + chainName + " cif atom groups: " + a_cif.getAtomGroups(GroupType.AMINOACID).size());

		//for (Group g: a_cif.getAtomGroups()){
		//	System.out.println(g);
		//}
		//System.out.println("--");
		String pdb_SEQseq = a_pdb.getSeqResSequence();

		String cif_SEQseq = a_cif.getSeqResSequence();

		//                        System.out.println(id + "_" + chainName + " pdbSEQ: " + pdb_SEQseq);
		//                        System.out.println(id + "_" + chainName + " cifSEQ: " + cif_SEQseq);

		assertEquals(id + ": the SEQRES sequences don't match!", pdb_SEQseq,cif_SEQseq);

		assertEquals(id + ":  The nr of ATOM groups does not match!",a_pdb.getAtomGroups(GroupType.AMINOACID).size(),a_cif.getAtomGroups(GroupType.AMINOACID).size()  );

		// actually this check not necessarily works, since there can be waters in PDB that we don;t deal with yet in cif...
		//assertEquals("the nr of ATOM record groups is not the same!" , a_pdb.getAtomLength(),a_cif.getAtomLength());
		for (int i = 0 ; i < a_pdb.getAtomGroups(GroupType.AMINOACID).size(); i++){
			Group gp = a_pdb.getAtomGroups(GroupType.AMINOACID).get(i);

			List<Group> cifGroups = a_cif.getAtomGroups(GroupType.AMINOACID);
			Group gc = cifGroups.get(i);
			checkGroups(gp, gc);
		}



		String pdb_seq = a_pdb.getAtomSequence();
		String cif_seq = a_cif.getAtomSequence();

		//System.out.println(pdb_seq);
		//System.out.println(cif_seq);

		assertEquals("the sequences obtained from PDB and mmCif don't match!",pdb_seq, cif_seq);

		List<DBRef> pdb_dbrefs= pdbStructure.getDBRefs();
		List<DBRef> cif_dbrefs= cifStructure.getDBRefs();

		assertEquals("nr of DBrefs found does not match!", pdb_dbrefs.size(),cif_dbrefs.size());

		DBRef p = pdb_dbrefs.get(0);
		DBRef c = cif_dbrefs.get(0);

		//System.out.println(p.toPDB());
		//System.out.println(c.toPDB());
		String pdb_dbref = p.toPDB();
		String cif_dbref = c.toPDB();
		assertEquals("DBRef is not equal",pdb_dbref,cif_dbref);

		PDBHeader h1 = pdbStructure.getPDBHeader();
		PDBHeader h2 = cifStructure.getPDBHeader();

		//compareString(h1.toPDB() ,h2.toPDB());
		//System.out.println(h1.toPDB());
		//System.out.println(h2.toPDB());
		if ( ! h1.toPDB().toUpperCase().equals(h2.toPDB().toUpperCase()) ){
			System.err.println(h1.toPDB());
			System.err.println(h2.toPDB());
			compareString(h1.toPDB(), h2.toPDB());
		}
		assertEquals("the PDBHeader.toPDB representation is not equivalent", h1.toPDB().toUpperCase(),h2.toPDB().toUpperCase());

		// and the ultimate test!
		// but we are not there yet...
		// TODO: still need to parse SSBOND equivalent info from cif files...
		//assertEquals("the Structure.toPDB representation is not equivalent", pdbStructure.toPDB(),cifStructure.toPDB());



	}

	private void checkGroups(Group g1, Group g2){

		//System.out.print("comparing " +g1 + " " + g2);
		String pdbId1 = g1.getChain().getStructure().getPDBCode();
		String pdbId2 = g1.getChain().getStructure().getPDBCode();
		assertEquals(pdbId1, pdbId2);

		assertEquals(g1.getType(),g2.getType());
		assertEquals(g1.getResidueNumber().getSeqNum(),g2.getResidueNumber().getSeqNum());
		assertEquals(g1.getResidueNumber().getInsCode(),g2.getResidueNumber().getInsCode());
		assertEquals(g1.getPDBName(),g2.getPDBName());
		assertEquals(g1.has3D(),g2.has3D());

		assertEquals(g1.hasAltLoc(), g2.hasAltLoc());

		assertEquals(pdbId1 + ":" + g1 + " - " + pdbId2+":"+ g2,g1.getAltLocs().size(), g2.getAltLocs().size());

		assertEquals(pdbId1 + ":" + g1 + " - " + pdbId2+":"+ g2 , g1.getAtoms().size(), g2.getAtoms().size());
		if ( g1.has3D()){

			Atom a1 = g1.getAtom(0);
			Atom a2 = g2.getAtom(0);
			if ( a1 == null)
				fail("could not get atom for group " + g1);
			if (a2 == null)
				fail("could not get atom for group " + g2);
			assertEquals(a1.getX(),a2.getX(), 0.0001);
			assertEquals(a1.getOccupancy(),a2.getOccupancy(), 0.0001);
			assertEquals(a1.getTempFactor(),a2.getTempFactor(), 0.0001);
			assertEquals(a1.getName(),a2.getName());


		}
		//System.out.println(" ... done");

	}

	private void checkNMR(Structure s){
		assertTrue(s.isNmr());

		int models = s.nrModels();
		assertTrue(models > 0);

		List<Chain> model0 = s.getModel(0);

		// compare with all others
		for (int i = 1 ; i < models; i++){
			List<Chain> modelX = s.getModel(i);

			assertEquals(model0.size(),modelX.size());

			// compare lengths:
			for (int j=0 ; j< model0.size();j++){
				Chain c1 = model0.get(j);
				Chain cx = modelX.get(j);
				assertEquals(c1.getAtomLength(),cx.getAtomLength());
				// can;t compare seq res, since this is only done for 1st...
				//assertEquals("c1.getSeqResLength(),cx.getSeqResLength());
				assertEquals(c1.getAtomSequence(),cx.getAtomSequence());
				assertEquals(c1.getAtomGroups(GroupType.AMINOACID).size(),cx.getAtomGroups(GroupType.AMINOACID).size());
				assertEquals(c1.getAtomGroups(GroupType.NUCLEOTIDE).size(),cx.getAtomGroups(GroupType.NUCLEOTIDE).size());
				assertEquals(c1.getAtomGroups(GroupType.HETATM).size(),cx.getAtomGroups(GroupType.HETATM).size());
			}


		}
	}

	private void compareString(String t, String pdb){
		for (int i =0 ; i < t.length() ; i++){
			System.out.println(">"+t.charAt(i)+":"+ pdb.charAt(i)+"<");
			if ( Character.toUpperCase(t.charAt(i)) != Character.toUpperCase(pdb.charAt(i))){
				System.out.println("Mismatch!");
				break;
			}
		}
	}


}
