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.Mockito.atLeast;
import static org.mockito.Mockito.times;
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 ObjectPackagesCollectionTest {

	@Mock
	private ObjectQueue mockedObjectQueue0;
	@Mock
	private ObjectQueue mockedObjectQueue1;
	@Mock
	private ObjectQueue mockedObjectQueue2;
	@Mock
	private ObjectQueue mockedObjectQueue3;
	@Mock
	private ObjectQueue mockedObjectQueue4;

	private List<ObjectQueue> listOQ;

	private ObjectPackagesCollectionFactory objectPackagesCollectionFactory;

	@Before
	public void setUp() throws Exception {
		objectPackagesCollectionFactory = new ObjectPackagesCollectionFactory();

		listOQ = new ArrayList<ObjectQueue>();

		listOQ.add(mockedObjectQueue0);
		listOQ.add(mockedObjectQueue1);
		listOQ.add(mockedObjectQueue2);
		listOQ.add(mockedObjectQueue3);
		listOQ.add(mockedObjectQueue4);
	}

	@Test
	public void testConstructorWithList() throws ObjectPackagingException {

		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);
		assertNotNull(opc);
		assertEquals(0, opc.getNumberOfComputedItems());

	}

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

	@Test
	public void testConstructorWithGoodID() throws ObjectPackagingException {
		ObjectPackagesCollection opc1 = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);
		ObjectPackagesCollection opc2 = objectPackagesCollectionFactory.createObjectPackagesCollection(opc1.getID());
		assertNotNull(opc2);
		assertEquals(opc1.getID(), opc2.getID());
		assertEquals(0, opc2.getNumberOfComputedItems());
		assertEquals(opc1.getListQueues().size(), opc2.getListQueues().size());
	}

	@Test
	public void testGetID() throws ObjectPackagingException {
		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);
		assertTrue(opc.getID().length() > 0);
		assertTrue(opc.getID().startsWith(ObjectPackagesCollection.OPC_PREFIX));
	}

	@Test
	public void testFindMinElementInQueues() throws ObjectPackagingException {
		when(mockedObjectQueue0.watchNextElementId()).thenReturn("dddd");
		when(mockedObjectQueue1.watchNextElementId()).thenReturn(null);
		when(mockedObjectQueue2.watchNextElementId()).thenReturn("ssss");
		when(mockedObjectQueue3.watchNextElementId()).thenReturn("aaaa");
		when(mockedObjectQueue4.watchNextElementId()).thenReturn("bbbb");

		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);
		int i = opc.findMinElementInQueues();

		verify(mockedObjectQueue0).watchNextElementId();
		verify(mockedObjectQueue1).watchNextElementId();
		verify(mockedObjectQueue2).watchNextElementId();
		verify(mockedObjectQueue3).watchNextElementId();
		verify(mockedObjectQueue4).watchNextElementId();

		assertEquals("aaaa", listOQ.get(i).watchNextElementId());
	}

	private class ObjectQueueAnswers implements Answer<Object> {
		List<String> queue;
		int size;
		int count;

		public ObjectQueueAnswers(String elems) {
			this.queue = new ArrayList<String>(Arrays.asList(elems.split("\\|")));
			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("fetchNextElement")) {
				if (count >= size) {
					return null;
				}
				String res = queue.get(count);
				count += 1;
				return res;
			} else if (method.equals("watchNextElementId")) {
				if (count >= size) {
					return null;
				}
				return queue.get(count);
			} else if (method.equals("getGivenItems")) {
				return count;
			} else if (method.equals("obtainOldItems")) {
				int from = Integer.parseInt(invocation.getArguments()[0].toString());
				int to = Integer.parseInt(invocation.getArguments()[1].toString());
				return queue.subList(from - 1, to);
			} else {
				return null;
			}
		}

		public int getSize() {
			return size;
		}

	}

	@Test
	public void testGetPackages() throws DocumentException, ObjectPackagingException {

		ObjectQueueAnswers answer0 = new ObjectQueueAnswers("<id>b</id>|<id>c</id>|<id>e</id>|<id>f</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer1 = new ObjectQueueAnswers("<id>a</id>|<id>c</id>|<id>d</id>|<id>e</id>|<id>f</id>|<id>i</id>");
		ObjectQueueAnswers answer2 = new ObjectQueueAnswers("<id>a</id>|<id>b</id>|<id>c</id>|<id>d</id>|<id>e</id>|<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer3 = new ObjectQueueAnswers("<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer4 = new ObjectQueueAnswers("<id>b</id>|<id>f</id>|<id>g</id>|<id>h</id>|<id>i</id>");

		when(mockedObjectQueue0.fetchNextElement()).thenAnswer(answer0);
		when(mockedObjectQueue0.watchNextElementId()).thenAnswer(answer0);
		when(mockedObjectQueue0.getGivenItems()).thenAnswer(answer0);
		when(mockedObjectQueue0.isEmpty()).thenAnswer(answer0);

		when(mockedObjectQueue1.fetchNextElement()).thenAnswer(answer1);
		when(mockedObjectQueue1.watchNextElementId()).thenAnswer(answer1);
		when(mockedObjectQueue1.getGivenItems()).thenAnswer(answer1);
		when(mockedObjectQueue1.isEmpty()).thenAnswer(answer1);

		when(mockedObjectQueue2.fetchNextElement()).thenAnswer(answer2);
		when(mockedObjectQueue2.watchNextElementId()).thenAnswer(answer2);
		when(mockedObjectQueue2.getGivenItems()).thenAnswer(answer2);
		when(mockedObjectQueue2.isEmpty()).thenAnswer(answer2);

		when(mockedObjectQueue3.fetchNextElement()).thenAnswer(answer3);
		when(mockedObjectQueue3.watchNextElementId()).thenAnswer(answer3);
		when(mockedObjectQueue3.getGivenItems()).thenAnswer(answer3);
		when(mockedObjectQueue3.isEmpty()).thenAnswer(answer3);

		when(mockedObjectQueue4.fetchNextElement()).thenAnswer(answer4);
		when(mockedObjectQueue4.watchNextElementId()).thenAnswer(answer4);
		when(mockedObjectQueue4.getGivenItems()).thenAnswer(answer4);
		when(mockedObjectQueue4.isEmpty()).thenAnswer(answer4);

		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);

		assertEquals(0, opc.getNumberOfComputedItems());

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

		verify(mockedObjectQueue0, atLeast(answer0.getSize())).watchNextElementId();
		verify(mockedObjectQueue1, atLeast(answer1.getSize())).watchNextElementId();
		verify(mockedObjectQueue2, atLeast(answer2.getSize())).watchNextElementId();
		verify(mockedObjectQueue3, atLeast(answer3.getSize())).watchNextElementId();
		verify(mockedObjectQueue4, atLeast(answer4.getSize())).watchNextElementId();
		verify(mockedObjectQueue0, times(answer0.getSize())).fetchNextElement();
		verify(mockedObjectQueue1, times(answer1.getSize())).fetchNextElement();
		verify(mockedObjectQueue2, times(answer2.getSize())).fetchNextElement();
		verify(mockedObjectQueue3, times(answer3.getSize())).fetchNextElement();
		verify(mockedObjectQueue4, times(answer4.getSize())).fetchNextElement();

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

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

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

	}

	@Test(expected = ObjectPackagingException.class)
	public void testGetPackagesFAIL() throws DocumentException, ObjectPackagingException {

		ObjectQueueAnswers answer0 = new ObjectQueueAnswers("<id>b</id>|<id>c</id>|<id>d</id>|<id>f</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer1 = new ObjectQueueAnswers("<id>a</id>|<id>c</id>|<id>aaaaaa</id>|<id>e</id>|<id>f</id>|<id>i</id>");
		ObjectQueueAnswers answer2 = new ObjectQueueAnswers("<id>a</id>|<id>b</id>|<id>c</id>|<id>d</id>|<id>e</id>|<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer3 = new ObjectQueueAnswers("<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer4 = new ObjectQueueAnswers("<id>b</id>|<id>f</id>|<id>g</id>|<id>h</id>|<id>i</id>");

		when(mockedObjectQueue0.fetchNextElement()).thenAnswer(answer0);
		when(mockedObjectQueue0.watchNextElementId()).thenAnswer(answer0);
		when(mockedObjectQueue0.getGivenItems()).thenAnswer(answer0);
		when(mockedObjectQueue0.isEmpty()).thenAnswer(answer0);

		when(mockedObjectQueue1.fetchNextElement()).thenAnswer(answer1);
		when(mockedObjectQueue1.watchNextElementId()).thenAnswer(answer1);
		when(mockedObjectQueue1.getGivenItems()).thenAnswer(answer1);
		when(mockedObjectQueue1.isEmpty()).thenAnswer(answer1);

		when(mockedObjectQueue2.fetchNextElement()).thenAnswer(answer2);
		when(mockedObjectQueue2.watchNextElementId()).thenAnswer(answer2);
		when(mockedObjectQueue2.getGivenItems()).thenAnswer(answer2);
		when(mockedObjectQueue2.isEmpty()).thenAnswer(answer2);

		when(mockedObjectQueue3.fetchNextElement()).thenAnswer(answer3);
		when(mockedObjectQueue3.watchNextElementId()).thenAnswer(answer3);
		when(mockedObjectQueue3.getGivenItems()).thenAnswer(answer3);
		when(mockedObjectQueue3.isEmpty()).thenAnswer(answer3);

		when(mockedObjectQueue4.fetchNextElement()).thenAnswer(answer4);
		when(mockedObjectQueue4.watchNextElementId()).thenAnswer(answer4);
		when(mockedObjectQueue4.getGivenItems()).thenAnswer(answer4);
		when(mockedObjectQueue4.isEmpty()).thenAnswer(answer4);

		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);

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

	@Test
	public void testGetOldPackages() throws ObjectPackagingException, DocumentException {
		ObjectQueueAnswers answer0 = new ObjectQueueAnswers("<id>b</id>|<id>c</id>|<id>e</id>|<id>f</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer1 = new ObjectQueueAnswers("<id>a</id>|<id>c</id>|<id>d</id>|<id>e</id>|<id>f</id>|<id>i</id>");
		ObjectQueueAnswers answer2 = new ObjectQueueAnswers("<id>a</id>|<id>b</id>|<id>c</id>|<id>d</id>|<id>e</id>|<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer3 = new ObjectQueueAnswers("<id>g</id>|<id>h</id>|<id>i</id>");
		ObjectQueueAnswers answer4 = new ObjectQueueAnswers("<id>b</id>|<id>f</id>|<id>g</id>|<id>h</id>|<id>i</id>");

		when(mockedObjectQueue0.fetchNextElement()).thenAnswer(answer0);
		when(mockedObjectQueue0.watchNextElementId()).thenAnswer(answer0);
		when(mockedObjectQueue0.getGivenItems()).thenAnswer(answer0);
		when(mockedObjectQueue0.isEmpty()).thenAnswer(answer0);
		when(mockedObjectQueue0.obtainOldItems(1, 4)).thenAnswer(answer0);

		when(mockedObjectQueue1.fetchNextElement()).thenAnswer(answer1);
		when(mockedObjectQueue1.watchNextElementId()).thenAnswer(answer1);
		when(mockedObjectQueue1.getGivenItems()).thenAnswer(answer1);
		when(mockedObjectQueue1.isEmpty()).thenAnswer(answer1);
		when(mockedObjectQueue1.obtainOldItems(2, 5)).thenAnswer(answer1);

		when(mockedObjectQueue2.fetchNextElement()).thenAnswer(answer2);
		when(mockedObjectQueue2.watchNextElementId()).thenAnswer(answer2);
		when(mockedObjectQueue2.getGivenItems()).thenAnswer(answer2);
		when(mockedObjectQueue2.isEmpty()).thenAnswer(answer2);
		when(mockedObjectQueue2.obtainOldItems(2, 5)).thenAnswer(answer2);

		when(mockedObjectQueue3.fetchNextElement()).thenAnswer(answer3);
		when(mockedObjectQueue3.watchNextElementId()).thenAnswer(answer3);
		when(mockedObjectQueue3.getGivenItems()).thenAnswer(answer3);
		when(mockedObjectQueue3.isEmpty()).thenAnswer(answer3);

		when(mockedObjectQueue4.fetchNextElement()).thenAnswer(answer4);
		when(mockedObjectQueue4.watchNextElementId()).thenAnswer(answer4);
		when(mockedObjectQueue4.getGivenItems()).thenAnswer(answer4);
		when(mockedObjectQueue4.isEmpty()).thenAnswer(answer4);
		when(mockedObjectQueue4.obtainOldItems(1, 2)).thenAnswer(answer4);

		ObjectPackagesCollection opc = objectPackagesCollectionFactory.createObjectPackagesCollection(listOQ);

		assertEquals(0, opc.getNumberOfComputedItems());

		List<String> list = new ArrayList<String>();
		while (true) {
			List<String> nl = opc.calculateMoreItems(3);
			list.addAll(nl);
			if (nl.size() != 3) {
				break;
			}
		}
		List<String> listOLD = opc.retrieveOldItems(2, 6);
		assertEquals(5, listOLD.size());
		assertEquals(list.get(1), listOLD.get(0));
		assertEquals(list.get(2), listOLD.get(1));
		assertEquals(list.get(3), listOLD.get(2));
		assertEquals(list.get(4), listOLD.get(3));
		assertEquals(list.get(5), listOLD.get(4));

	}

}
