package eu.dnetlib.data.utility.objectpackaging;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.dom4j.DocumentException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnit44Runner;
import org.mockito.stubbing.Answer;

import eu.dnetlib.data.utility.objectpackaging.rmi.ObjectPackagingException;

@RunWith(MockitoJUnit44Runner.class)
public class ObjectPackagesSplitterTest {

	@Mock
	private PackageQueue queue;

	private ObjectPackagesSplitterFactory objectPackagesSplitterFactory;

	@Before
	public void setUp() throws Exception {
		objectPackagesSplitterFactory = new ObjectPackagesSplitterFactory();
	}

	@Test
	public void testConstructorWithQueue() throws ObjectPackagingException {
		ObjectPackagesSplitter splitter = objectPackagesSplitterFactory.createSplitter(queue);
		assertNotNull(splitter);
		assertEquals(0, splitter.getNumberOfComputedItems());

	}

	@Test(expected = ObjectPackagingException.class)
	public void testConstructorWithBadID() throws ObjectPackagingException {
		objectPackagesSplitterFactory.createSplitter("INVALID.ID");
	}

	@Test
	public void testConstructorWithGoodID() throws ObjectPackagingException {
		ObjectPackagesSplitter splitter1 = objectPackagesSplitterFactory.createSplitter(queue);
		ObjectPackagesSplitter splitter2 = objectPackagesSplitterFactory.createSplitter(splitter1.getID());
		assertNotNull(splitter2);
		assertEquals(splitter1.getID(), splitter2.getID());
		assertEquals(0, splitter2.getNumberOfComputedItems());
	}

	@Test
	public void testGetID() throws ObjectPackagingException {
		ObjectPackagesSplitter splitter = objectPackagesSplitterFactory.createSplitter(queue);
		assertTrue(splitter.getID().startsWith(ObjectPackagesSplitter.SPLIT_PREFIX));
		assertTrue(splitter.getID().length() > 0);
	}

	private class PackageQueueAnswers implements Answer<Object> {

		List<List<String>> queue;
		int size;
		int count;

		public PackageQueueAnswers() {
			List<String> l1 = new ArrayList<String>();
			List<String> l2 = new ArrayList<String>();
			List<String> l3 = new ArrayList<String>();

			l1.addAll(Arrays.asList("a|b|c".split("\\|")));
			l2.addAll(Arrays.asList("d|e|f|g".split("\\|")));
			l3.addAll(Arrays.asList("h|i".split("\\|")));

			this.queue = new ArrayList<List<String>>();
			queue.add(l1);
			queue.add(l2);
			queue.add(l3);
			this.size = this.queue.size();
			this.count = 0;
		}

		@Override
		public Object answer(InvocationOnMock invocation) throws Throwable {
			String method = invocation.getMethod().getName();

			if (method.equals("isEmpty")) {
				return Boolean.valueOf(count >= size);
			} else if (method.equals("fetchNextListObject")) {
				if (count >= size) {
					return null;
				}
				List<String> res = queue.get(count);
				count += 1;
				return res;
			} else if (method.equals("getGivenItems")) {
				return count;
			} else if (method.equals("obtainOldListObjects")) {
				int from = Integer.parseInt(invocation.getArguments()[0].toString()) - 1;
				int to = Integer.parseInt(invocation.getArguments()[1].toString());
				List<List<String>> tmp = new ArrayList<List<String>>();
				List<String> l1 = new ArrayList<String>();
				List<String> l2 = new ArrayList<String>();
				List<String> l3 = new ArrayList<String>();
				l1.addAll(Arrays.asList("a|b|c".split("\\|")));
				l2.addAll(Arrays.asList("d|e|f|g".split("\\|")));
				l3.addAll(Arrays.asList("h|i".split("\\|")));
				tmp.add(l1);
				tmp.add(l2);
				tmp.add(l3);
				return tmp.subList(from, to);
			} else {
				return null;
			}
		}

		public int getSize() {
			return size;
		}

	}

	@Test
	public void testCalculateMoreItems() throws ObjectPackagingException, DocumentException {
		PackageQueueAnswers answer = new PackageQueueAnswers();

		when(queue.fetchNextElement()).thenAnswer(answer);
		when(queue.fetchNextListObject()).thenAnswer(answer);
		when(queue.getGivenItems()).thenAnswer(answer);
		when(queue.isEmpty()).thenAnswer(answer);

		ObjectPackagesSplitter splitter = objectPackagesSplitterFactory.createSplitter(queue);

		assertEquals(0, splitter.getNumberOfComputedItems());

		List<String> list = new ArrayList<String>();
		while (true) {
			List<String> nl = splitter.calculateMoreItems(4);
			list.addAll(nl);
			if (nl.size() != 4) {
				break;
			}
		}

		verify(queue, atLeast(answer.getSize())).fetchNextListObject();

		assertEquals(9, list.size());
		assertEquals(9, splitter.getNumberOfComputedItems());

		assertEquals("a", list.get(0));
		assertEquals("b", list.get(1));
		assertEquals("c", list.get(2));
		assertEquals("d", list.get(3));
		assertEquals("e", list.get(4));
		assertEquals("f", list.get(5));
		assertEquals("g", list.get(6));
		assertEquals("h", list.get(7));
		assertEquals("i", list.get(8));

		assertEquals(9, splitter.getHistoryList().size());
		assertEquals("1,0", splitter.getHistoryList().get(0));
		assertEquals("1,1", splitter.getHistoryList().get(1));
		assertEquals("1,2", splitter.getHistoryList().get(2));
		assertEquals("2,0", splitter.getHistoryList().get(3));
		assertEquals("2,1", splitter.getHistoryList().get(4));
		assertEquals("2,2", splitter.getHistoryList().get(5));
		assertEquals("2,3", splitter.getHistoryList().get(6));
		assertEquals("3,0", splitter.getHistoryList().get(7));
		assertEquals("3,1", splitter.getHistoryList().get(8));

	}

	@Test
	public void testRetrieveOldItems() throws ObjectPackagingException, DocumentException {
		PackageQueueAnswers answer = new PackageQueueAnswers();

		when(queue.fetchNextElement()).thenAnswer(answer);
		when(queue.fetchNextListObject()).thenAnswer(answer);
		when(queue.getGivenItems()).thenAnswer(answer);
		when(queue.isEmpty()).thenAnswer(answer);
		when(queue.obtainOldListObjects(anyInt(), anyInt())).thenAnswer(answer);

		ObjectPackagesSplitter splitter = objectPackagesSplitterFactory.createSplitter(queue);

		List<String> list1 = new ArrayList<String>();
		while (true) {
			List<String> nl = splitter.calculateMoreItems(4);
			list1.addAll(nl);
			if (nl.size() != 4) {
				break;
			}
		}

		assertEquals(9, list1.size());
		assertEquals("a", list1.get(0));
		assertEquals("b", list1.get(1));
		assertEquals("c", list1.get(2));
		assertEquals("d", list1.get(3));
		assertEquals("e", list1.get(4));
		assertEquals("f", list1.get(5));
		assertEquals("g", list1.get(6));
		assertEquals("h", list1.get(7));
		assertEquals("i", list1.get(8));

		List<String> list2 = splitter.retrieveOldItems(1, 9);
		assertEquals(9, list2.size());
		assertEquals("a", list2.get(0));
		assertEquals("b", list2.get(1));
		assertEquals("c", list2.get(2));
		assertEquals("d", list2.get(3));
		assertEquals("e", list2.get(4));
		assertEquals("f", list2.get(5));
		assertEquals("g", list2.get(6));
		assertEquals("h", list2.get(7));
		assertEquals("i", list2.get(8));

		List<String> list3 = splitter.retrieveOldItems(2, 3);
		assertEquals(2, list3.size());
		assertEquals("b", list3.get(0));
		assertEquals("c", list3.get(1));

		List<String> list4 = splitter.retrieveOldItems(2, 5);
		assertEquals(4, list4.size());
		assertEquals("b", list4.get(0));
		assertEquals("c", list4.get(1));
		assertEquals("d", list4.get(2));
		assertEquals("e", list4.get(3));

		List<String> list5 = splitter.retrieveOldItems(9, 9);
		assertEquals(1, list5.size());
		assertEquals("i", list5.get(0));
	}

}
