/*
 *                    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-11-20
 *
 */

package org.biojava.nbio.structure;

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static org.junit.Assert.*;

/**
 * A unit test for {@link ResidueRange}.
 *
 * @author dmyerstu
 *
 */
public class ResidueRangeTest {

	private AtomCache cache;

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

	@Test
	public void testWholeChainBasic() {
		String range = "B:";
		ResidueRange rr = ResidueRange.parse(range);
		assertEquals("Wrong chain Id", "B", rr.getChainId());
		assertNull("Start residue should be null", rr.getStart());
		assertNull("End residue should be null", rr.getEnd());
	}

	@Test
	public void testPartialChainWithMap() throws IOException,
	StructureException {
		String pdbId = "1cph";
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		String range = "B:1-30";
		ResidueRange rr = ResidueRangeAndLength.parse(range, map);
		ResidueNumber start = new ResidueNumber("B", 1, null);
		ResidueNumber end = new ResidueNumber("B", 30, null);
		assertEquals("Wrong chain Id", "B", rr.getChainId());
		assertEquals("Wrong start", start, rr.getStart());
		assertEquals("Wrong end", end, rr.getEnd());
	}

	@Test
	public void testWholeChainWithMap() throws IOException, StructureException {
		String pdbId = "1cph";
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		String range = "B:";
		ResidueRange rr = ResidueRangeAndLength.parse(range, map);
		assertEquals("Wrong chain Id", "B", rr.getChainId());
		assertEquals("Wrong start", new ResidueNumber("B",1,null),rr.getStart());
		assertEquals("Wrong end", new ResidueNumber("B",30,null),rr.getEnd());
	}

	/**
	 * Tests creating ResidueRanges and calculating their lengths.
	 */
	@Test
	public void testWithLengths() throws IOException, StructureException {
		String[] ids = new String[] { "1w0p", "3qq3", "3chc", "2ei7" }; // more:
		// ,
		// "2qbr"
		String[] chains = new String[] { "A", "B", "A", "L" };
		ResidueNumber[] starts = new ResidueNumber[] {
				new ResidueNumber("A", 5, ' '),
				new ResidueNumber("B", 10, 's'),
				new ResidueNumber("A", 15, 'm'),
				new ResidueNumber("L", 44, ' ') };
		ResidueNumber[] ends = new ResidueNumber[] {
				new ResidueNumber("A", 117, ' '),
				new ResidueNumber("B", 200, 's'),
				new ResidueNumber("A", 464, 'q'),
				new ResidueNumber("L", 254, 't') };
		int[] lengths = new int[] { 117 - 5, 200 - 10, 111, 55 };
		int totalLength = 0;
		List<ResidueRangeAndLength> ranges = new ArrayList<ResidueRangeAndLength>(
				ids.length);
		for (int i = 0; i < ids.length; i++) {
			ResidueRangeAndLength rr = new ResidueRangeAndLength(chains[i],
					starts[i], ends[i], lengths[i]);
			assertEquals("The chain is incorrect", chains[i], rr.getChainId());
			assertEquals("The start is incorrect", starts[i], rr.getStart());
			assertEquals("The end is incorrect", ends[i], rr.getEnd());
			assertEquals("The length is incorrect", lengths[i], rr.getLength());
			ranges.add(rr);
			totalLength += lengths[i];
			assertEquals("Total length is wrong", totalLength,
					ResidueRangeAndLength.calcLength(ranges));
		}
	}

	@Test
	public void testLengths() throws IOException, StructureException {
		String pdbId = "1w0p";
		String rangeStr = "A:25-26";
		Atom[] atoms = cache.getAtoms(pdbId);
		AtomPositionMap map = new AtomPositionMap(atoms);
		ResidueRangeAndLength range = ResidueRangeAndLength
				.parse(rangeStr, map);
		assertEquals(2, range.getLength());
	}

	@Test
	public void testIterator() throws IOException, StructureException {
		String pdbId = "2eke";
		String[] expected = new String[] { "C_1023", "C_1024", "C_1025",
				"C_1026", "C_1027", "C_1028", "C_1029", "C_1030", "C_1031",
				"C_1032", "C_1033", "C_1034", "C_1035", "C_1036", "C_1037",
				"C_1038", "C_1039", "C_1040", "C_1041", "C_1042", "C_1043",
				"C_1044", "C_1045", "C_1046", "C_1047", "C_1048", "C_1049",
				"C_1050", "C_1051", "C_1052", "C_1053", "C_1054", "C_1055",
				"C_1056", "C_1057", "C_1058", "C_1059", "C_1060", "C_1061",
				"C_1062", "C_1063" };
		ResidueRange rr = ResidueRange.parse("C_1023-1063");
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		Iterator<ResidueNumber> iter = rr.iterator(map);
		int i = 0;
		while (iter.hasNext()) {
			ResidueNumber rn = iter.next();
			assertTrue(i<expected.length);
			assertEquals(expected[i], rn.printFull());
			i++;
		}
		assertEquals(expected.length,i);
	}

	@Test
	public void testMultiIterator() throws IOException, StructureException {
		String pdbId = "1qdm";
		String[] expected = new String[] { "A_3S", "A_4S", "A_5S", "A_6S",
				"A_7S", "A_8S", "A_9S", "A_10S", "A_11S", "A_12S", "A_13S",
				"A_14S", "A_15S", "A_16S", "A_17S", "A_18S", "A_19S", "A_20S",
				"A_21S", "A_22S", "A_23S", "A_24S", "A_25S", "A_26S", "A_27S",
				"A_28S", "A_29S", "A_30S", "A_31S", "A_32S", "A_33S", "A_34S",
				"A_35S", "A_36S", "A_37S", "A_65S", "A_66S", "A_67S", "A_68S",
				"A_69S", "A_70S", "A_71S", "A_72S", "A_73S", "A_74S", "A_75S",
				"A_76S", "A_77S", "A_78S", "A_79S", "A_80S", "A_81S", "A_82S",
				"A_83S", "A_84S", "A_85S", "A_86S", "A_87S", "A_88S", "A_89S",
				"A_90S", "A_91S", "A_92S", "A_93S", "A_94S", "A_95S", "A_96S",
				"A_97S", "A_98S", "A_99S" };
		List<ResidueRange> rrs = ResidueRange
				.parseMultiple("A_3S-37S,A_65S-99S");
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms(pdbId));
		Iterator<ResidueNumber> iter = ResidueRange.multiIterator(map, rrs);
		int i = 0;
		while (iter.hasNext()) {
			ResidueNumber rn = iter.next();
			assertEquals(expected[i], rn.printFull());
			i++;
		}
	}

	/**
	 * Tests {@link ResidueRange#parseMultiple(String)}.
	 */
	@Test
	public void testParseAndEqual() {
		String rangeStr;
		List<ResidueRange> ranges;
		ResidueRange range;

		// String pdbId1 = "2eke";
		rangeStr = "C_1023-1063,C_1064-1084";
		ranges = ResidueRange.parseMultiple(rangeStr);
		assertEquals(new ResidueRange("C", new ResidueNumber("C", 1023, null),
				new ResidueNumber("C", 1063, null)), ranges.get(0));
		assertEquals(new ResidueRange("C", new ResidueNumber("C", 1064, null),
				new ResidueNumber("C", 1084, null)), ranges.get(1));

		// String pdbId = "1qdm";
		rangeStr = "A_3S-37S,A_65S-99S";
		ranges = ResidueRange.parseMultiple(rangeStr);
		assertEquals(new ResidueRange("A", new ResidueNumber("A", 3, 'S'),
				new ResidueNumber("A", 37, 'S')), ranges.get(0));
		assertEquals(new ResidueRange("A", new ResidueNumber("A", 65, 'S'),
				new ResidueNumber("A", 99, 'S')), ranges.get(1));

		// Multi-character chains
		rangeStr = "AB,A1,ABCD_1-55,NotAG00dID:-5-1R";
		ranges = ResidueRange.parseMultiple(rangeStr);
		range = ranges.get(0);
		assertEquals("Error parsing " + rangeStr, "AB", range.getChainId());
		assertNull("Error parsing " + rangeStr, range.getStart());
		assertNull("Error parsing " + rangeStr, range.getEnd());
		range = ranges.get(1);
		assertEquals("Error parsing " + rangeStr, "A1", range.getChainId());
		assertNull("Error parsing " + rangeStr, range.getStart());
		assertNull("Error parsing " + rangeStr, range.getEnd());
		range = ranges.get(2);
		assertEquals("Error parsing " + rangeStr, "ABCD", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("ABCD", 1,
				null), range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("ABCD", 55,
				null), range.getEnd());
		range = ranges.get(3);
		assertEquals("Error parsing " + rangeStr, "NotAG00dID",
				range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber(
				"NotAG00dID", -5, null), range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber(
				"NotAG00dID", 1, 'R'), range.getEnd());

		// Wildcard chains
		rangeStr = "_,__,_:1-5,_:+1-+5";
		ranges = ResidueRange.parseMultiple(rangeStr);
		range = ranges.get(0);
		assertEquals("Error parsing " + rangeStr, "_", range.getChainId());
		assertNull("Error parsing " + rangeStr, range.getStart());
		assertNull("Error parsing " + rangeStr, range.getEnd());
		range = ranges.get(1);
		assertEquals("Error parsing " + rangeStr, "_", range.getChainId());
		assertNull("Error parsing " + rangeStr, range.getStart());
		assertNull("Error parsing " + rangeStr, range.getEnd());
		range = ranges.get(2);
		assertEquals("Error parsing " + rangeStr, "_", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("_", 1,
				null), range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("_", 5,
				null), range.getEnd());
		range = ranges.get(3);
		assertEquals("Error parsing " + rangeStr, "_", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("_", 1,
				null), range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("_", 5,
				null), range.getEnd());

	}

	@Test(expected=IllegalArgumentException.class)
	public void testBadSyntax() throws IOException, StructureException {
		ResidueRange.parse("-");
	}

	@Test(expected=IllegalArgumentException.class)
	public void testPartialRange() throws IOException, StructureException {
		AtomPositionMap map = new AtomPositionMap(cache.getAtoms("2eke"));
		ResidueRangeAndLength.parse("C_1023-", map);
	}

	/**
	 * Tests
	 * {@link org.biojava.nbio.structure.ResidueRangeAndLength#parseMultiple(String, org.biojava.nbio.structure.AtomPositionMap)}
	 * .
	 *
	 * @throws StructureException
	 * @throws IOException
	 */
	@Test
	public void testParseAndEqualWithLengths() throws IOException,
	StructureException {
		String rangeStr;
		List<ResidueRangeAndLength> ranges;
		ResidueRangeAndLength range;

		AtomPositionMap map;

		String pdbId = "2eke";
		map = new AtomPositionMap(cache.getAtoms(pdbId));
		rangeStr = "C_1023-1063,C_1064-1084,C";//C is 105-112,1013-1095
		ranges = ResidueRangeAndLength.parseMultiple(rangeStr, map);
		assertEquals(new ResidueRangeAndLength("C", new ResidueNumber("C",
				1023, null), new ResidueNumber("C", 1063, null), 1063 - 1023+1),
				ranges.get(0));
		assertEquals(new ResidueRangeAndLength("C", new ResidueNumber("C",
				1064, null), new ResidueNumber("C", 1084, null), 1084 - 1064+1),
				ranges.get(1));
		assertEquals(new ResidueRangeAndLength("C", new ResidueNumber("C", 105,
				null), new ResidueNumber("C", 1095, null), 91), ranges.get(2));

		// Wildcard chains

		pdbId = "4r61"; // A:8-52,58-109,119-161
		map = new AtomPositionMap(cache.getAtoms(pdbId));
		rangeStr = "_,__,_:52-58";
		ranges = ResidueRangeAndLength.parseMultiple(rangeStr, map);
		range = ranges.get(0);
		assertEquals("Error parsing " + rangeStr, "A", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A",8,null),range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A",161,null),range.getEnd());
		range = ranges.get(1);
		assertEquals("Error parsing " + rangeStr, "A", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A",8,null),range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A",161,null),range.getEnd());
		range = ranges.get(2);
		assertEquals("Error parsing " + rangeStr, "A", range.getChainId());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A", 52,null), range.getStart());
		assertEquals("Error parsing " + rangeStr, new ResidueNumber("A", 58,null), range.getEnd());

		// wildcards not converted without the map
		ResidueRange range2 = ResidueRange.parse("_");
		assertEquals("Error parsing " + rangeStr, "_", range2.getChainId());
		assertNull("Error parsing " + rangeStr,range2.getStart());
		assertNull("Error parsing " + rangeStr,range2.getEnd());

	}

	@Test
	public void testRangeRegex() {
		// Valid ranges
		String[] yes = new String[] { "A_", "A:", "ABC:", "abc:", "A_5-100",
				"A_5-100S", "A_5S-100", "A_5S-100S", "A_-5-100", "A_-5--100",
				"A_-5S--100S", "ABC:-5--200S", "A", "ABCD", "A_1",
				"A1", // valid multi-char chain name
				"3A:1-100", // Weird chain name
				"_", "_:1-10", "__-2--1", "__", // catch-all chain
				"A:-3-+1","A:-3-+1","A:+1-6", // Positive numbers ok, although weird
		};
		for (String s : yes) {
			assertTrue(s + " was not considered a valid range format",
					ResidueRange.RANGE_REGEX.matcher(s).matches());
		}
		// invalid ranges
		String[] no = new String[] { "A_1-", "A_1S-", "A_1-100-",
				"A_-10-1000_", "", "-", "___", "__:",
				"A:1-","A:--5","A:-+5", // Partial ranges
		};
		for (String s : no) {
			assertFalse(s + " was considered a valid range format",
					ResidueRange.RANGE_REGEX.matcher(s).matches());
		}
	}

}
