/*
 *                    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 on 2012-12-01
 *
 */

package org.biojava.nbio.structure;

import org.biojava.nbio.structure.align.util.AtomCache;
import org.biojava.nbio.structure.io.LocalPDBDirectory;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.NavigableMap;

import static org.junit.Assert.*;
import static org.junit.Assume.*;

/**
 * A unit test for {@link org.biojava.nbio.structure.AtomPositionMap}.
 * @author dmyerstu
 */
public class AtomPositionMapTest {

	@Before
	public void setUp() throws Exception {
		cache = new AtomCache(); // TODO Should mock instead of depending on real data from AtomCache
		cache.setObsoleteBehavior(LocalPDBDirectory.ObsoleteBehavior.FETCH_OBSOLETE);
	}

	private AtomCache cache;

	/**
	 * Tests with no insertion codes.
	 * @throws StructureException
	 * @throws IOException
	 */
	@Test
	public void testEasy() throws IOException, StructureException { // no insertion codes
		// Straightforward case. Density for residues 25-777 (743 residues)
		String pdbId = "1w0p";
		int length = 93;
		ResidueNumber start = new ResidueNumber("A", 25, null);
		ResidueNumber end = new ResidueNumber("A", 117, null);
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		NavigableMap<ResidueNumber,Integer> navMap = map.getNavMap();
		for (ResidueNumber n : navMap.keySet()) {
			assertEquals("An element is missing", map.getPosition(n).intValue(), navMap.get(n).intValue());
		}
		int realLength = map.getLength(start, end);
		assertEquals("Real atom length is wrong", length, realLength);
	}

	@Test
	public void testLengths() throws IOException, StructureException {
		// Two identical chains, residues 1-68, no insertion codes or missing residues
		String pdbId = "3w0e";

		Atom[] atoms = cache.getAtoms(pdbId);
		AtomPositionMap map = new AtomPositionMap(atoms);

		// Double check that the chain length is correct
		int chainAlen = cache.getStructure(pdbId).getChainByPDB("A").getAtomGroups(GroupType.AMINOACID).size();
		assumeTrue(68==chainAlen);


		int len;
		int start,end;// 0-based

		// Single residue
		start = 0;
		end = 0;
		len = map.getLength(new ResidueNumber("A",start+1,null), new ResidueNumber("A",end+1,null));
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLength(start, end,"A");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",1, len);

		// Short range
		start = 2;
		end = 4;
		len = map.getLength(new ResidueNumber("A",start+1,null), new ResidueNumber("A",end+1,null));
		assertEquals("Bad length for ("+start+","+end+")",3, len);
		len = map.getLength(start, end,"A");
		assertEquals("Bad length for ("+start+","+end+")",3, len);
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",3, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",-3, len);

		//Full chain
		start = 0;
		end = chainAlen-1;
		len = map.getLength(new ResidueNumber("A",start+1,null), new ResidueNumber("A",end+1,null));
		assertEquals("Bad length for ("+start+","+end+")",chainAlen, len);
		len = map.getLength(start, end,"A");
		assertEquals("Bad length for ("+start+","+end+")",chainAlen, len);
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",chainAlen, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",-chainAlen, len);

		// Chain spanning
		start = chainAlen-1;
		end = chainAlen;
		try {
			len = map.getLength(new ResidueNumber("A",chainAlen,null), new ResidueNumber("B",1,null));
			fail("Not the same chain");
		} catch( IllegalArgumentException e) {}
		len = map.getLength(start, end,"A");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",-1, len);
		len = map.getLengthDirectional(start,end, "B");
		assertEquals("Bad length for ("+start+","+end+")",1, len);

		start = chainAlen-2; //2 residues of A
		end = chainAlen+2; // 3 residues of B
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",2, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",-2, len);
		len = map.getLengthDirectional(start, end, "B");
		assertEquals("Bad length for ("+start+","+end+")",3, len);
		len = map.getLengthDirectional(end, start, "B");
		assertEquals("Bad length for ("+start+","+end+")",-3, len);

		start = 0;
		end = chainAlen;
		try {
			len = map.getLength(new ResidueNumber("A",start+1,null), new ResidueNumber("A",end+1,null));
			fail("Residue found from the wrong chain");
		} catch( IllegalArgumentException e) {
			// end residue should be B1, not A142
		}
		// Chain Spanning
		len = map.getLength(start, end,"B");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(start, end, "B");
		assertEquals("Bad length for ("+start+","+end+")",1, len);
		len = map.getLengthDirectional(end, start, "B");
		assertEquals("Bad length for ("+start+","+end+")",-1, len);
		len = map.getLength(start, end,"A");
		assertEquals("Bad length for ("+start+","+end+")",chainAlen, len);
		len = map.getLengthDirectional(start, end, "A");
		assertEquals("Bad length for ("+start+","+end+")",chainAlen, len);
		len = map.getLengthDirectional(end, start, "A");
		assertEquals("Bad length for ("+start+","+end+")",-chainAlen, len);
	}

	/**
	 * Tests with insertion codes.
	 * @throws StructureException
	 * @throws IOException
	 */
	@Test
	public void testInsertionCodes() throws IOException, StructureException {
		String pdbId = "1qdm";
		// has 2 insertion code regions, lettered P and S, as well as disordered regions:
		// 6P-26P,2-163,169-247,1S-37S,65S-104S,248-338
		// Len:21,  162,     79,    37,      40,     91 = 430

		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		NavigableMap<ResidueNumber,Integer> navMap = map.getNavMap();

		for (ResidueNumber n : navMap.keySet()) {
			assertEquals("An element is missing", map.getPosition(n).intValue(), navMap.get(n).intValue());
		}

		int length1 = 60; // 2+37+21
		int length2 = 132;// 2+37+40+53
		ResidueNumber start = new ResidueNumber("A", 246, null);
		ResidueNumber mid = new ResidueNumber("A", 85, 'S');
		ResidueNumber end = new ResidueNumber("A", 300, null);
		int realLength1 = map.getLength(start, mid);
		assertEquals("Real atom length is wrong", length1, realLength1);
		int realLength2 = map.getLength(start, end);
		assertEquals("Real atom length is wrong", length2, realLength2);

		int realLength = map.getLength(new ResidueNumber("A",6,'P'),new ResidueNumber("A",338,null));
		assertEquals("Full length wrong",430,realLength);
	}

	@Test
	public void testTrim() throws IOException, StructureException {
		// Two identical chains, residues 1-68, no insertion codes or missing residues
		String pdbId = "3w0e";
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));

		ResidueRangeAndLength trimmed;
		ResidueRange untrimmed;

		untrimmed = new ResidueRange("A", "1", "68");
		trimmed = map.trimToValidResidues(untrimmed);
		assertEquals("Wrong start after trimming "+untrimmed,new ResidueNumber("A", 1, null), trimmed.getStart());
		assertEquals("Wrong end after trimming "+untrimmed,new ResidueNumber("A", 68, null), trimmed.getEnd());
		assertEquals("Wrong length after trimming "+untrimmed,68, trimmed.getLength());

		untrimmed = new ResidueRange("A", "1", "1");
		trimmed = map.trimToValidResidues(untrimmed);
		assertEquals("Wrong start after trimming "+untrimmed,new ResidueNumber("A", 1, null), trimmed.getStart());
		assertEquals("Wrong end after trimming "+untrimmed,new ResidueNumber("A", 1, null), trimmed.getEnd());
		assertEquals("Wrong length after trimming "+untrimmed,1, trimmed.getLength());

		untrimmed = new ResidueRange("A", "-1", "70");
		trimmed = map.trimToValidResidues(untrimmed);
		assertEquals("Wrong start after trimming "+untrimmed,new ResidueNumber("A", 1, null), trimmed.getStart());
		assertEquals("Wrong end after trimming "+untrimmed,new ResidueNumber("A", 68, null), trimmed.getEnd());
		assertEquals("Wrong length after trimming "+untrimmed,68, trimmed.getLength());

		// Fully out of range
		untrimmed = new ResidueRange("A", "-4", "-1");
		trimmed = map.trimToValidResidues(untrimmed);
		assertNull("Should be empty range "+untrimmed,trimmed);

		// Start before end. Arguably should be invalid, but currently works
		untrimmed = new ResidueRange("A", "4", "1");
		trimmed = map.trimToValidResidues(untrimmed);
		assertEquals("Wrong start after trimming "+untrimmed,new ResidueNumber("A", 4, null), trimmed.getStart());
		assertEquals("Wrong end after trimming "+untrimmed,new ResidueNumber("A", 1, null), trimmed.getEnd());
		assertEquals("Wrong length after trimming "+untrimmed,4, trimmed.getLength());

		// However, doesn't work if the endpoints are invalid, since searches wrong direction
		untrimmed = new ResidueRange("A", "4", "-1");
		trimmed = map.trimToValidResidues(untrimmed);
		assertNull("Should be empty range "+untrimmed,trimmed);

		untrimmed = new ResidueRange("A", "70", "50");
		trimmed = map.trimToValidResidues(untrimmed);
		assertNull("Should be empty range "+untrimmed,trimmed);

	}

	@Test
	public void testChains() throws IOException, StructureException {
		String pdbId = "1qdm";
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));

		ResidueNumber start,end;
		try {
			start = new ResidueNumber("A",6,'P');
			end = new ResidueNumber("B",338,null);
			map.getLength(start, end);
			fail("Chain missmatch");
		} catch(IllegalArgumentException e) {
			// Expected
		}
		try {
			start = new ResidueNumber("A",6,'P');
			end = new ResidueNumber("B",338,null);
			map.getLengthDirectional(start, end);
			fail("Chain missmatch");
		} catch(IllegalArgumentException e) {
			// Expected
		}

		// With integers, only count matching chain atoms
		start = new ResidueNumber("A",338,null);
		end = new ResidueNumber("B",6,'P');
		int len = map.getLength(map.getPosition(start),map.getPosition(end),"A");
		assertEquals(1, len);
	}
}
