package au.edu.wehi.idsv;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

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

import org.junit.Test;

import com.google.common.collect.ImmutableList;

import htsjdk.samtools.SAMRecord;
import htsjdk.samtools.fastq.FastqRecord;

public class GreedyVariantAllocationCacheTest extends TestHelper {
	@Test
	public void should_return_first_best_variant() throws IOException {
		DirectedBreakpoint[] evidence = new DirectedBreakpoint[] {
			(DiscordantReadPair)NRRP(DP(0, 1, "1M", true, 1, 1, "1M", true)),
			SR(Read(0, 1, "10M5S"), Read(1, 100, "5M")),
			IE(Read(0, 1, "1M1D1M"))
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			for (DirectedBreakpoint e : evidence) {
				cache.addBreakpoint("variant1", 1, e);
				cache.addBreakpoint("variant2", 2, e);
				cache.addBreakpoint("variant3", 2, e);
			}
			for (DirectedBreakpoint e : evidence) {
				assertFalse(cache.isBestBreakpoint("variant1", e));
				assertTrue(cache.isBestBreakpoint("variant2", e));
				assertFalse(cache.isBestBreakpoint("variant3", e));
				assertFalse(cache.isBestBreakpoint("variant4", e));
			}
		}
	}
	@Test
	public void dp_must_be_best_call_for_read_pair() throws IOException {
		DiscordantReadPair[] evidence = new DiscordantReadPair[] {
				(DiscordantReadPair)NRRP(withReadName("read", DP(1, 1, "1M", true, 2, 2, "1M", true))),
				(DiscordantReadPair)NRRP(withReadName("read", DP(2, 2, "1M", true, 1, 1, "1M", true))),
				(DiscordantReadPair)NRRP(withReadName("read", DP(3, 3, "1M", true, 4, 4, "1M", true))),
				(DiscordantReadPair)NRRP(withReadName("read", DP(1, 1, "1M", true, 4, 4, "1M", true))),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant1", 1, evidence[1]);
			cache.addBreakpoint("variant2", 1, evidence[2]);
			cache.addBreakpoint("variant3", 1, evidence[3]);
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[0]));
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[1]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[1]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[1]));		
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[2]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[2]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[2]));
			// Although it has the same local mapping, the other side is incorrect
			// and this evidence is not part of variant1 
			assertFalse(cache.isBestBreakpoint("variant1", evidence[3]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[3]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[3]));
		}
	}
	@Test
	public void sr_must_be_best_split() throws IOException {
		DirectedBreakpoint[] evidence = new DirectedBreakpoint[] {
				SR(withReadName("read", Read(0, 1, "10M10S"))[0], withReadName("read", Read(0, 10, "10M"))[0]),
				SR(withReadName("read", Read(0, 1, "10M10S"))[0], withReadName("read", Read(0, 20, "10M"))[0]),
				SR(withReadName("read", Read(0, 1, "10S10M"))[0], withReadName("read", Read(0, 10, "10M"))[0]),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant2", 1, evidence[1]);
			cache.addBreakpoint("variant3", 1, evidence[2]);
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[0]));
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[1]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[1]));		
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[2]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[2]));
			assertFalse(cache.isBestBreakpoint("variant3", evidence[2]));
		}
	}
	@Test
	public void indel_must_be_best_alignment() throws IOException {
		DirectedBreakpoint[] evidence = new DirectedBreakpoint[] {
				IE(SES(), withReadName("read", Read(0, 1, "10M5D10M"))[0], 1),
				IE(SES(), withReadName("read", Read(0, 1, "10M6D10M"))[0], 1),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant2", 1, evidence[1]);
			
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant2", 1, evidence[1]);
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[1]));
		}
	}
	@Test
	public void indel_must_include_both_sides() throws IOException {
		IndelEvidence[] evidence = new IndelEvidence[] {
				IE(SES(), withReadName("read", Read(0, 1, "10M5D10M"))[0], 1),
				IE(SES(), withReadName("read", Read(0, 1, "10M6D10M"))[0], 1),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant2", 1, evidence[1]);
			cache.addBreakpoint("variant1", 2, evidence[0].asRemote());
			cache.addBreakpoint("variant2", 1, evidence[1].asRemote());
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[0].asRemote()));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0].asRemote()));
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1].asRemote()));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[1].asRemote()));
		}
	}
	@Test
	public void should_allow_multiple_indels_given_same_alignment() throws IOException {
		IndelEvidence[] evidence = new IndelEvidence[] {
				IE(SES(), withReadName("read", Read(0, 1, "10M5D10M5D10M"))[0], 1),
				IE(SES(), withReadName("read", Read(0, 1, "10M5D10M5D10M"))[0], 2),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 2, evidence[0]);
			cache.addBreakpoint("variant2", 1, evidence[1]);
			
			assertTrue(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			
			// no problem the read supporting both the indels at the same time
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1]));
			assertTrue(cache.isBestBreakpoint("variant2", evidence[1]));
		}
	}
	@Test
	public void should_allow_multiple_split_reads_given_same_alignment() throws IOException {
		SAMRecord mid = withReadName("read", Read(0, 100, "10S5M8S"))[0];
		List<FastqRecord> ralist = SplitReadIdentificationHelper.getSplitReadRealignments(mid, false, getContext().getEvidenceIDGenerator());
		SAMRecord pre = Read(0, 50, "10M");
		SAMRecord post = Read(0, 150, "8M");
		pre.setReadName(ralist.get(0).getReadHeader());
		post.setReadName(ralist.get(1).getReadHeader());
		SplitReadIdentificationHelper.convertToSplitRead(mid, ImmutableList.of(pre, post));
		List<SplitReadEvidence> sr = new ArrayList<>();
		sr.addAll(SplitReadEvidence.create(SES(), pre));
		sr.addAll(SplitReadEvidence.create(SES(), mid));
		sr.addAll(SplitReadEvidence.create(SES(), post));
		// and an alternative alignment
		sr.add(SR(withReadName("read", Read(0, 1, "10M10S"))[0], withReadName("read", Read(0, 20, "10M"))[0]));
				
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 1, sr.get(0));
			cache.addBreakpoint("variant1", 1, sr.get(1));
			cache.addBreakpoint("variant2", 2, sr.get(2));
			cache.addBreakpoint("variant2", 1, sr.get(3));
			cache.addBreakpoint("variant3", 1, sr.get(4));
			
			// both split reads are fine
			assertTrue(cache.isBestBreakpoint("variant1", sr.get(0)));
			assertTrue(cache.isBestBreakpoint("variant1", sr.get(1)));
			assertTrue(cache.isBestBreakpoint("variant2", sr.get(2)));
			assertTrue(cache.isBestBreakpoint("variant2", sr.get(3)));
		}
	}
	@Test
	public void should_call_best_sc_alignment() throws IOException {
		SoftClipEvidence[] evidence = new SoftClipEvidence[] {
				SCE(FWD, withReadName("read", Read(0, 1, "10M5S"))),
				SCE(FWD, withReadName("read", Read(1, 1, "10M5S"))),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 1, evidence[0]);
			cache.addBreakpoint("variant2", 2, evidence[1]);
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1]));
			assertTrue(cache.isBestBreakpoint("variant2", evidence[1]));
		}
	}
	@Test
	public void should_call_best_oea_alignment() throws IOException {
		NonReferenceReadPair[] evidence = new NonReferenceReadPair[] {
				NRRP(withReadName("read", OEA(0, 1, "1M", true))),
				NRRP(withReadName("read", OEA(0, 2, "1M", true))),
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 1, evidence[0]);
			cache.addBreakpoint("variant2", 2, evidence[1]);
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[0]));
			assertFalse(cache.isBestBreakpoint("variant2", evidence[0]));
			
			assertFalse(cache.isBestBreakpoint("variant1", evidence[1]));
			assertTrue(cache.isBestBreakpoint("variant2", evidence[1]));
		}
	}
	@Test
	public void should_not_persist_cache() throws IOException {
		SoftClipEvidence[] evidence = new SoftClipEvidence[] {
				SCE(FWD, withReadName("read", Read(0, 1, "10M5S")))
		};
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			cache.addBreakpoint("variant1", 1, evidence[0]);
		}
		// The cache should have been destroyed - we don't want any persistence
		try (GreedyVariantAllocationCache cache = new GreedyVariantAllocationCache(true, 1000, true, 1000, true, 1000)) {
			assertFalse(cache.isBestBreakpoint("variant1", evidence[0]));
			
		}
	}
}
