package eu.dnetlib.data.utility.objectpackaging;

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

import javax.annotation.Resource;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import static org.junit.Assert.*; // NOPMD

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import eu.dnetlib.data.utility.objectpackaging.rmi.ObjectPackagingException;
import eu.dnetlib.data.utility.objectpackaging.rmi.ObjectPackagingService;
import eu.dnetlib.enabling.resultset.rmi.ResultSetException;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.enabling.tools.ServiceResolver;
import eu.dnetlib.test.AbstractIntegrationContainerTest;

/**
 * Test for ObjectPackagingService.
 *
 * @author michele
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
public class ObjectPackagingServicePackTest extends AbstractIntegrationContainerTest {
	/**
	 * retrieval page size.
	 */
	private static final int PAGE_SIZE = 10;

	/**
	 * we want IDs with constant width.
	 */
	private static final int ID_PADDING = 5;

	/**
	 * Service under test.
	 */
	private ObjectPackagingService objectPackagingService;

	/**
	 * result set service locator.
	 */
	@Resource(name = "resultSetLocator")
	private transient ServiceLocator<ResultSetService> resultSetLocator;

	/**
	 * Object Packaging service locator.
	 */
	@Resource(name = "objectPackagingLocator")
	private transient ServiceLocator<ObjectPackagingService> objectPackagingLocator;

	/**
	 * service resolver.
	 */
	@Resource
	private transient ServiceResolver serviceResolver;

	/**
	 * prepare for testing.
	 *
	 * @throws Exception
	 */
	@Before
	public void setUp() {
		objectPackagingService = objectPackagingLocator.getService();
	}

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(ObjectPackagingServicePackTest.class); // NOPMD by marko on 11/24/08 5:02 PM

	/**
	 * Test with input list = null.
	 *
	 * @throws ObjectPackagingException
	 *             expected
	 * @throws ResultSetException
	 *             not expected
	 */
	@Test(expected = ObjectPackagingException.class)
	public void testObjectPackagingNull() throws ObjectPackagingException, ResultSetException {
		objectPackagingService.generatePackages(null, "//id");
	}

	/**
	 * Test with input list with 1 EPR.
	 *
	 * @throws ObjectPackagingException
	 *             not expected
	 * @throws ResultSetException
	 *             not expected
	 * @throws DocumentException
	 *             not expected
	 */
	@Test
	public void testObjectPackagingSingle() throws ObjectPackagingException, ResultSetException, DocumentException {
		final List<W3CEndpointReference> eprs = new ArrayList<W3CEndpointReference>();

		final int size = 56;

		final List<String> list = new ArrayList<String>();
		for (int i = 0; i < size; i++) {
			list.add("<elem><id>" + generateID(i) + "</id><aaaaa /></elem>");
		}
		eprs.add(getEPR(list));

		final List<String> response = execute(eprs, "//id");
		assertEquals(size, response.size());

		final SAXReader reader = new SAXReader();
		for (int i = 0; i < size; i++) {
			final Document doc = reader.read(new StringReader(response.get(i)));
			assertEquals(generateID(i), doc.valueOf("/objectRecord//id"));
		}
	}

	/**
	 * Test with input list with 1 EPR and and invalid xpathId.
	 *
	 * @throws ObjectPackagingException
	 *             expected
	 * @throws ResultSetException
	 *             not expected
	 * @throws DocumentException
	 *             not expected
	 */
	@Test(expected = Exception.class)
	public void testObjectPackagingSingleInvalidXpath() throws ObjectPackagingException, ResultSetException, DocumentException {
		final List<W3CEndpointReference> eprs = new ArrayList<W3CEndpointReference>();

		final int size = 56;

		final List<String> list = new ArrayList<String>();
		for (int i = 0; i < size; i++) {
			list.add("<elem><id>" + generateID(i) + "</id><aaaaa /></elem>");
		}
		eprs.add(getEPR(list));

		execute(eprs, "//invalid");
	}

	/**
	 * Test with input list with more EPRs.
	 *
	 * @throws ObjectPackagingException
	 *             not expected
	 * @throws ResultSetException
	 *             not expected
	 * @throws DocumentException
	 *             not expected
	 */
	@Test
	public void testObjectPackagingMultiple() throws ObjectPackagingException, ResultSetException, DocumentException {
		final List<W3CEndpointReference> eprs = new ArrayList<W3CEndpointReference>();

		final int sizeA = 80;
		final int sizeB = 75;
		final int sizeC = 66;
		final int sizeD = 45;

		final List<String> list1 = new ArrayList<String>();
		final List<String> list2 = new ArrayList<String>();
		final List<String> list3 = new ArrayList<String>();
		final List<String> list4 = new ArrayList<String>();

		for (int i = 0; i < sizeA; i++)
			list1.add("<elem><id>" + generateID(i) + "</id><aaaaa /></elem>");

		for (int i = 0; i < sizeB; i++)
			list2.add("<elem><id>" + generateID(i + 2) + "</id><bbbb /></elem>");

		for (int i = 0; i < sizeC; i++)
			list3.add("<elem><id>" + generateID(i * 2) + "</id><cccc /></elem>");

		for (int i = 0; i < sizeD; i++)
			list4.add("<elem><id>" + generateID(i * (2 + 1)) + "</id><dddd /></elem>");

		eprs.add(getEPR(list1));
		eprs.add(getEPR(list2));
		eprs.add(getEPR(list3));
		eprs.add(getEPR(list4));

		final List<String> response = execute(eprs, "//id");

		assertTrue(response.size() > sizeA);

		final SAXReader reader = new SAXReader();
		int count = 0;
		for (final String elem : response) {

			log.info(elem);

			final Document doc = reader.read(new StringReader(elem));
			String test = null;

			@SuppressWarnings("unchecked")
			final List nodes = doc.selectNodes("/objectRecord//id");
			assertTrue(nodes.size() > 0);
			for (final Object o : nodes) {
				if (test == null) {
					test = ((Element) o).valueOf(".");
				} else {
					assertEquals(test, ((Element) o).valueOf("."));
				}
			}
			count += nodes.size();
		}
		assertEquals(sizeA + sizeB + sizeC + sizeD, count);
	}

	/**
	 * execute the object packaging service invocation.
	 *
	 * @param eprs
	 *            list of eprs
	 * @param xpath
	 *            xpath
	 * @return list of items
	 * @throws ObjectPackagingException
	 *             shouldn't happen
	 * @throws ResultSetException
	 *             shouldn't happen
	 */
	private List<String> execute(final List<W3CEndpointReference> eprs, final String xpath) throws ObjectPackagingException, ResultSetException {
		final W3CEndpointReference epr = objectPackagingService.generatePackages(eprs, xpath);

		final ResultSetService resultSet = serviceResolver.getService(ResultSetService.class, epr);
		final String rsId = serviceResolver.getResourceIdentifier(epr);

		final List<String> obtained = new ArrayList<String>();

		int total = 0;
		boolean allDone = false;

		while (true) {
			total = resultSet.getNumberOfElements(rsId);
			allDone = resultSet.getRSStatus(rsId).equals("closed");

			if ((obtained.size() < total) && (allDone)) {
				obtained.addAll(resultSet.getResult(rsId, obtained.size() + 1, total, "WAITING"));
				break;
			} else if (allDone) {
				break;
			} else {
				obtained.addAll(resultSet.getResult(rsId, obtained.size() + 1, obtained.size() + PAGE_SIZE, "WAITING"));
			}
		}
		return obtained;
	}

	/**
	 * helper method.
	 *
	 * @param n
	 *            some number
	 * @return some id
	 */
	private String generateID(final int n) {
		final String s = "0000000000" + n;
		return s.substring(s.length() - ID_PADDING);
	}

	/**
	 * get the epr for a push resultset containing 'elements'.
	 *
	 * @param elements
	 *            elements
	 * @return EPR to a resultset
	 * @throws ResultSetException
	 *             shouldn't happen
	 */
	private W3CEndpointReference getEPR(final List<String> elements) throws ResultSetException {
		final W3CEndpointReference epr = resultSetLocator.getService().createPushRS(0, 0);
		final ResultSetService resultSet = serviceResolver.getService(ResultSetService.class, epr);
		final String rsId = serviceResolver.getResourceIdentifier(epr);
		resultSet.populateRS(rsId, elements);
		resultSet.closeRS(rsId);

		assertEquals(elements.size(), resultSet.getNumberOfElements(rsId));

		return epr;
	}

	/**
	 * Test with input empty and null xpath.
	 *
	 * @throws Exception
	 *             not expected
	 */
	@Test
	public void testGeneratePackagesForNullEmptyXPath() throws Exception {
		final List<W3CEndpointReference> eprs = new ArrayList<W3CEndpointReference>();

		final int size = 56;

		final List<String> list = new ArrayList<String>();
		for (int i = 0; i < size; i++) {
			list.add("<elem><id>" + generateID(i) + "</id><aaaaa /></elem>");
		}
		eprs.add(getEPR(list));

		try {
			objectPackagingService.generatePackages(eprs, null);
			fail("exception should be thrown when generating packages for null xpath!");
		} catch (ObjectPackagingException e) {
//			ok
		}

		try {
			objectPackagingService.generatePackages(eprs, "");
			fail("exception should be thrown when generating packages for empty xpath!");
		} catch (ObjectPackagingException e) {
//			ok
		}

	}

	/**
	 * Test with input invalid xpath.
	 *
	 * @throws ObjectPackagingException expected
	 * @throws ResultSetException expected
	 */
	@Test(expected = Exception.class)
	public void testGetResultForInvalidXPath() throws ObjectPackagingException, ResultSetException {
		final List<W3CEndpointReference> eprs = new ArrayList<W3CEndpointReference>();

		final int size = 56;

		final List<String> list = new ArrayList<String>();
		for (int i = 0; i < size; i++) {
			list.add("<elem><id>" + generateID(i) + "</id><aaaaa /></elem>");
		}
		eprs.add(getEPR(list));

		execute(eprs, "//INVALID/XPATH");

	}

}
