package eu.dnetlib.data.utility.featureextraction;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.util.StringUtils;

import eu.dnetlib.data.sts.das.IDataAccessService;
import eu.dnetlib.data.sts.ds.DepotServiceHelper;
import eu.dnetlib.data.sts.ds.IDepotService;
import eu.dnetlib.data.sts.ds.StorageConstants;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;

import pl.edu.icm.driver.factories.UniBiBeansFactory;
import pl.edu.icm.driver.index.DummyStaticResultSet;
import pl.edu.icm.driver.is.ISConstants;
import pl.edu.icm.driver.is.ISUtils;
import pl.edu.icm.driver.is.utils.ResourcesGenerator;
import pl.edu.icm.driver.tester.TestDescription;
import pl.edu.icm.driver.tester.runner.Parameterized4TestCase;
import pl.edu.icm.driver.tester.runner.TestJobContext;
import pl.edu.icm.driver.utils.PropertiesKeys;

/**
 * {@link IFeatureExtractionService} test class.
 * 
 * @author mhorst
 *
 */
@TestDescription(
		name="FeatureExtraction Service test suite",
		description="Performs all required functionality tests of FeatureExtraction service.\n" +
				"Expected properties:\n" +
				"unibi.fes.service.location - localisation of FeatureExtraction service to be tested\n" +
				"unibi.das.service.location - localisation of DataAccess service for reading data\n" +
				"unibi.depot.service.location - localisation of Depot service to store fulltexts\n" +
				"Optional properties:\n" +
				"is.rs.dataprov.ip - data provider service ip\n" +
				"is.rs.dataprov.port - data provider service port\n" +
				"is.rs.dataprov.location - data provider service location (default: 'IDataProvider?wsdl')\n"
)
public class FeatureExtractionServiceTest extends Parameterized4TestCase {

	protected static final String FEATURE_BIBREF_EXTR = "bibref";
	
	protected static final Logger log = Logger.getLogger(FeatureExtractionServiceTest.class);
	
	IFeatureExtractionService featureExtractionService;
	IDepotService depotService;
	IDataAccessService dataAccessService;

	String featureExtractionServiceLocation;
	String depotServiceLocation;
	String dataAccessServiceLocation;
	
	String dataProviderIP = null;
	String dataProviderPort = null;
	String dataProviderLocation = null;
	String dataProviderURL = null;
	
	String expectedFeaturesCSV = null;
	
	boolean failWhenUnknownFeature = false;
	
	long sleepTime = 1000;
	int triesCount = 5;
	
	/**
	 * DMF data provider.
	 */
	ResultSetService dataProvider;
	
	/**
	 * CXF bus containing HTTP server.
	 */
	Bus bus;
	
	/* (non-Javadoc)
	 * @see pl.edu.icm.driver.tester.ParameterizedTest#setParameters(java.util.Map)
	 */
	@Override
	public void setParameters(Map<Object, Object> parameters) {
		featureExtractionServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_FEATURE_EXTR);
		log.info("TestCase featureExtractionServiceLocation: " + featureExtractionServiceLocation);
		depotServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_DEPOT);
		log.info("TestCase depotServiceLocation: " + 
				depotServiceLocation);
		dataAccessServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_DAS);
		log.info("TestCase dataAccessServiceLocation: " + 
				dataAccessServiceLocation);
		
		expectedFeaturesCSV = (String) parameters.get(PropertiesKeys.FES_EXPECTED_FEATURES_CSV);
		log.info("TestCase expectedFeaturesCSV: " + expectedFeaturesCSV);
		
		dataProviderIP = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_IP);
		dataProviderPort = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_PORT);
		dataProviderLocation = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_LOCATION);
	}

	@Before
	public void setUp() throws Exception {
		ResourceBundle bundleIS = ResourceBundle.getBundle("pl.edu.icm.driver.is.auxiliary");
		if (dataProviderIP==null)
			dataProviderIP = bundleIS.getString(PropertiesKeys.RS_DATA_PROVIDER_IP);
		if (dataProviderPort==null)
			dataProviderPort = bundleIS.getString(PropertiesKeys.RS_DATA_PROVIDER_PORT);
		if (dataProviderLocation==null)
			dataProviderLocation = bundleIS.getString(PropertiesKeys.RS_DATA_PROVIDER_LOCATION);
		StringBuffer strBuff = new StringBuffer();
		strBuff.append("http://");
		strBuff.append(dataProviderIP);
		strBuff.append(':');
		strBuff.append(dataProviderPort);
		strBuff.append('/');
		strBuff.append(dataProviderLocation);
		dataProviderURL = strBuff.toString();
		log.info("dataProviderURL: "+dataProviderURL);
		
		ResourceBundle bundle = ResourceBundle.getBundle("eu.dnetlib.data.utility.auxiliary");
		if (expectedFeaturesCSV==null) {
			expectedFeaturesCSV = bundle.getString(PropertiesKeys.FES_EXPECTED_FEATURES_CSV);
		}

		UniBiBeansFactory ubBeansFactory=new UniBiBeansFactory();
		featureExtractionService=ubBeansFactory.getFeatureExtraction(
				featureExtractionServiceLocation);
		dataAccessService = ubBeansFactory.getDAS(dataAccessServiceLocation);
		depotService = ubBeansFactory.getDepot(depotServiceLocation);
	}
	
	@After
	public void tearDown() throws Exception {

	}
	
	@Test
	@TestDescription(
			planId="fes.functionality",
			description="Retrieves version number from identity call in FeatureExtraction service. " +
					"Stores it in the result with description ''"
			)
	public void testIdentify() throws Exception {
		String identify = featureExtractionService.identify();
		assertNotNull(identify);
		assertTrue(identify.length()>0);
		System.out.println("service version: "+identify);
		TestJobContext.storeResult(identify, "service version");
	}
	
	@Test
	@TestDescription(
			planId="fes.functionality",
			description="Tests getting list of features."
			)
	public void testGetFeatures() throws Exception {
		List<Feature> features = featureExtractionService.getFeatures();
		assertNotNull(features);
		if (expectedFeaturesCSV.trim().length()>0) {
//			testing only if features were specified
			String[] expected = StringUtils.tokenizeToStringArray(expectedFeaturesCSV, ",");
			if (expected.length>0) {
				for (String current : expected) {
					assertTrue(current + " feature not supported!", 
							isFeatureSupported(current.trim(), features));
				}
			}
		}
	}
	
	@Test
	@TestDescription(
			planId="fes.functionality",
			description="Checks whether method fails smoothly when " +
					"processing for unsupported feature."
			)
	public void testExtractForUnsupportedFeature() throws Exception {
		try {
			Feature feature = new Feature();
			feature.setName("unsupportedFeatureName");
			String[] rsData = new String[0];
			serverStartup(new Integer(dataProviderPort).intValue(), rsData);
			int expiryTime = 10000;
			String bdId = String.valueOf(System.currentTimeMillis());
			int initialPageSize = 10;
			try {
				featureExtractionService.extract(feature, 
						dataProvider.createPullRS(dataProviderURL, bdId, 
						initialPageSize, expiryTime, null, null, null));
				fail("exception should be thrown when extracting " +
						"data for unsupported feature!");
			} catch (FeatureExtractionException e) {
//				ok
			}
			
		} finally {
			serverShutdown();
		}
		
	}
	
	@Test
	@TestDescription(
			planId="fes.functionality",
			description="Checks whether method fails smoothly when " +
					"processing for invalid epr."
			)
	public void testExtractForInvalidEPR() throws Exception {
		List<Feature> features = featureExtractionService.getFeatures();
		assertNotNull(features);
		assertTrue("No features available!", features.size()>0);
		try {
			String dataProviderServiceAddress = "http://localhost:1234/nonExisting";
			featureExtractionService.extract(features.get(0), 
					ISUtils.buildEPR(dataProviderServiceAddress, 
							"unknownID", dataProviderServiceAddress+"?wsdl", "RSName"));
			fail("exception should be thrown when extracting " +
					"data for invalid epr!");
		} catch (FeatureExtractionException e) {
//			ok
		}
	}
	
	@Test
	@TestDescription(
			planId="fes.functionality",
			description="Tests all supported features."
			)
	public void testAllFeatures() throws Exception {
		List<Feature> features = featureExtractionService.getFeatures();
		assertNotNull(features);
		List<String> unknownFeatures = new ArrayList<String>();
		if (features.size()>0) {
			for (Feature currentFeature : features) {
				if (currentFeature.getName().equals(FEATURE_BIBREF_EXTR)) {
					log.debug("starting bibref extraction tests...");
					testBibrefExtraction(currentFeature);
					log.debug("bibref extraction tests finished");
				} else {
					String message = "unknown feature: " + currentFeature.getName() + 
					", cannot perform tests!";
					if (failWhenUnknownFeature) {
						fail(message);
					}
					log.warn(message);
					unknownFeatures.add(currentFeature.getName());
				}
			}
			if (unknownFeatures.size()>0) {
				String message = "features not tested: " + unknownFeatures.toString();
				System.out.println(unknownFeatures);
				TestJobContext.storeResult(message, "features not tested");
			}
		} else {
			String message = "no features to be tested!";
			System.out.println(message);
			TestJobContext.storeResult(message, "warning");
		}
	}
	
	/**
	 * Bibref extraction feature test.
	 * Notice: this test is strictly bound to the file content
	 * being passed to the FES. Otherwise it will be impossible to verify bibref value.
	 */
	protected void testBibrefExtraction(Feature feature) throws Exception {
//		this bibref is part of file exposed via servlet
		String bibref = "[1], I.M. Bassat, P. Nigam, D. Singh, R. Marchant, Bioresour. Technol., 58, 1996";
		String text = "this is some dummy text, this is some dummy text" +
			"this is some dummy text, this is some dummy text\n" +
			"this is some dummy text, this is some dummy text\n" +
			"this is some dummy text, this is some dummy text\n" +
			"this is some dummy text, this is some dummy text\n" +
			"this is some dummy text, this is some dummy text\n\n";

		String stId = null;
		try {
//			creating entry in depot
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String dataObjectId = "test_id_"+System.currentTimeMillis();
			data.add(DepotServiceHelper.buildStoreRecord(dataObjectId, 
					StorageConstants.STORE_DATATYPE_DATA, text + bibref));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, false);
				if (DepotServiceHelper.isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			extracting bibrefs
			W3CEndpointReference epr = featureExtractionService.extract(feature, 
					dataAccessService.storeLookUpSDORS(stId));
			assertNotNull(epr);
			ResultSetService resultSetService = epr.getPort(ResultSetService.class);
			assertNotNull(resultSetService);
			String rsId = ISUtils.extractResultSetId(epr);
			assertNotNull(rsId);
			
//			waiting until RS is closed
			int maxTriesCount = 5;
			int triesCount = 0;
			while(!ISConstants.RESULT_SET_STATUS_CLOSED.equals(
					resultSetService.getRSStatus(rsId))) {
				Thread.sleep(1000);
				triesCount++;
				if (triesCount>maxTriesCount) {
					fail("rs is not closed, maxTriesCount: " + maxTriesCount + 
							" was exceeded!");
				}
			}
			
			int expectedElementsCount = 1;
			assertEquals(expectedElementsCount, resultSetService.getNumberOfElements(rsId));
			List<String> results = resultSetService.getResult(rsId, 1, expectedElementsCount, 
					ISConstants.RESULT_SET_REQUEST_MODE_WAITING);
			assertNotNull(results);
			assertEquals(expectedElementsCount, results.size());
			System.out.println("bibref content: " + results.get(0));
//			the content is: <descriptionRecord><objIdentifier>test_id_1259689690490</objIdentifier><bibrefs/></descriptionRecord>
//			bibref is not extracted
			assertEquals(bibref.trim(), results.get(0).trim());
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}
	
	boolean isFeatureSupported(String expected, List<Feature> features) {
		if (features!=null && features.size()>0) {
			for (Feature current : features) {
				if (expected.equals(current.getName())) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Starts up cxf jetty server.
	 * @param dataProviderPort
	 * @throws Exception
	 */
	void serverStartup(int dataProviderPort, String[] data) throws Exception {
//		 Create CXF Service
		bus = BusFactory.getDefaultBus();
		dataProvider = new DummyStaticResultSet(data);
		StringBuffer strBuff = new StringBuffer();
		strBuff.append("http://");
		strBuff.append(dataProviderIP);
		strBuff.append(':');
		strBuff.append(dataProviderPort);
		strBuff.append('/');
		strBuff.append(dataProviderLocation);
		Endpoint.publish(strBuff.toString(), dataProvider);
		log.info("cxf service started...");
	}
	
	/**
	 * Shuts down cxf jetty server.
	 * @throws Exception
	 */
	void serverShutdown() throws Exception {
		if (bus!=null) {
			log.info("shutting down cxf service...");
			bus.shutdown(true);
		}
	}
}
