package eu.dnetlib.data.information.collectionservice;

import static org.junit.Assert.assertTrue;

import java.io.StringReader;
import java.io.StringWriter;

import javax.annotation.Resource;

import org.apache.commons.io.IOUtils;
import org.dom4j.Document;
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.information.collectionservice.rmi.CollectionService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.test.AbstractIntegrationContainerTest;

/**
 * Test for Collection Service.
 *
 * @author michele
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
public class CollectionServiceNotificationTest extends AbstractIntegrationContainerTest {

	/**
	 * repeat retrieval condition test n times.
	 */
	private static final int RETRIEVAL_REPEAT = 100;

	/**
	 * milliseconds in a second.
	 */
	private static final int MILLIS = 1000;

	/**
	 * Service under test.
	 */
	private CollectionService service;

	/**
	 * Collection service locator.
	 */
	@Resource(name = "collectionServiceLocator")
	private transient ServiceLocator<CollectionService> serviceLocator;

	/**
	 * sax reader.
	 */
	private final SAXReader reader = new SAXReader();

	/**
	 * grandfather id.
	 */
	private String grandFatherCollID;
	/**
	 * father id.
	 */
	private String fatherCollID;
	/**
	 * collection 1.
	 */
	private String collID1;
	/**
	 * collection 2.
	 */
	private String collID2;
	/**
	 * collection 3.
	 */
	private String collID3;

	/**
	 * fake membership condition.
	 */
	private static final String NEW_MCOND = "NEWCOND";

	/**
	 * interval.
	 */
	private static final int INTERVAL = 3000;
	/**
	 * times to repeat.
	 */
	private static final int N_TIMES = 5;

	/**
	 * prepare for testing.
	 *
	 * @throws Exception
	 *             could happen
	 */
	@Before
	public void setUp() throws Exception {
		service = serviceLocator.getService();

		grandFatherCollID = registerSampleColl("A", null);
		fatherCollID = registerSampleColl("B", grandFatherCollID);
		collID1 = registerSampleColl("C", fatherCollID);
		collID2 = registerSampleColl("D", fatherCollID);
		collID3 = registerSampleColl("E", fatherCollID);
	}

	/**
	 * register a sample collection.
	 *
	 * @param cond
	 *            membership condition
	 * @param father
	 *            father collection id
	 * @return new collection id
	 * @throws Exception
	 *             could happen
	 */
	private String registerSampleColl(final String cond, final String father) throws Exception {
		final StringWriter writer = new StringWriter();
		IOUtils.copy(getClass().getResourceAsStream("collProfile2.xml"), writer);
		final String profile = writer.getBuffer().toString();

		final Document doc = reader.read(new StringReader(profile));
		((Element) doc.selectSingleNode("//MEMBERSHIP_CONDITION")).setText(cond);

		if ((father != null) && (father.length() > 0)) {
			((Element) doc.selectSingleNode("//FATHER")).addAttribute("id", father);
		}

		return service.createCollection(doc.asXML());
	}

	/**
	 * Test update father collection.
	 *
	 * @throws Exception
	 *             could happen
	 */
	@Test
	public void testUpdateFatherCollection() throws Exception {

		final Document fatherDom = reader.read(new StringReader(service.getCollection(fatherCollID)));
		fatherDom.selectSingleNode("//MEMBERSHIP_CONDITION").setText(NEW_MCOND);
		service.updateCollection(fatherDom.asXML());

		assertTrue(isRetrievalConditionChanged(collID1, "(C) AND ((" + NEW_MCOND + ") AND (A))"));
		assertTrue(isRetrievalConditionChanged(collID2, "(D) AND ((" + NEW_MCOND + ") AND (A))"));
		assertTrue(isRetrievalConditionChanged(collID3, "(E) AND ((" + NEW_MCOND + ") AND (A))"));
	}

	/**
	 * Test delete collection.
	 *
	 * @throws Exception
	 *             could happen
	 */
	@Test
	public void testDeleteFatherCollection() throws Exception {
		service.deleteCollection(fatherCollID);

		assertTrue(isRetrievalConditionChanged(collID1, "(C) AND (A)"));
		assertTrue(isRetrievalConditionChanged(collID2, "(D) AND (A)"));
		assertTrue(isRetrievalConditionChanged(collID3, "(E) AND (A)"));

		assertTrue(isFather(collID1, grandFatherCollID));
		assertTrue(isFather(collID2, grandFatherCollID));
		assertTrue(isFather(collID3, grandFatherCollID));
	}

	/**
	 * Test if a retrieval condition is matching an expected value.
	 *
	 * @param id
	 *            collection id
	 * @param expected
	 *            expect
	 * @return true if matching the expected value
	 * @throws Exception exception
	 */
	private boolean isRetrievalConditionChanged(final String id, final String expected) throws Exception {
		for (int i = 0; i < RETRIEVAL_REPEAT; i++) {
			Thread.sleep(MILLIS);
			final String rcond = reader.read(new StringReader(service.getCollection(id))).valueOf("//RETRIEVAL_CONDITION");
			
			if (rcond.equals(expected)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Test if a father id is matching an expected value.
	 *
	 * @param id
	 *            collection id
	 * @param fatherId
	 *            expected father id
	 * @return true if the father is the expected father
	 * @throws Exception ex
	 */
	private boolean isFather(final String id, final String fatherId) throws Exception {
		for (int i = 0; i < N_TIMES; i++) {
			Thread.sleep(INTERVAL);
			final String s = reader.read(new StringReader(service.getCollection(id))).valueOf("//FATHER/@id");
			if (s.equals(fatherId)) {
				return true;
			}
		}
		return false;
	}

}
