package org.biomoby.client.test;

import org.biomoby.client.AsyncClient;
import org.biomoby.client.CentralImpl;
import org.biomoby.client.MobyRequest;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;
import org.biomoby.w3c.addressing.EndpointReference;
import org.omg.lsae.notifications.AnalysisEvent;

import junit.framework.*;

import org.w3c.dom.*;

import java.net.URL;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class AsyncClientTestCase extends TestCase{
    private final static String TEST_INPUT_RESOURCE = "org/biomoby/client/test/TestAASeq.xml";
    private final static String TEST_SERVICE_NAME = "runFunCUT";
    private final static String TEST_SERVICE_AUTHORITY = "cnio.es";
    private final static int POLLING_WAITTIME_MS = 5000;  // ms between checks to see if the job is done
    private final static int MAX_WAITTIME_MS = 600000;  // allow 10 minutes for the service to run

    /**
     * @param name Test case name.
     */
    public AsyncClientTestCase(String name) {
        super(name);
    }

    public void testCall(){
	// Setup variable, etc.
	Central central = null;
	try{
	    central = new CentralImpl();
	    //central.setDebug(true);
	} catch(Exception e){
	    e.printStackTrace();
	    fail("Could not initialize Moby Central client: "+e);
	}

	MobyService serviceTemplate = new MobyService(TEST_SERVICE_NAME, TEST_SERVICE_AUTHORITY);
	// So we get synchronous and asynchronous services, since blank 
	// template values are wildcards
	serviceTemplate.setCategory("");  
	String serviceName = TEST_SERVICE_AUTHORITY + ":" + TEST_SERVICE_NAME;

	MobyService[] services = null;
	try{
	    services = central.findService(serviceTemplate);
	} catch(Exception e){
	    e.printStackTrace();
	    fail("Could not successfully lookup the test service from Moby Central: " + e);
	}
	assertTrue("Could not find the test asynchronous MOBY service (" + serviceName + ")", 
		   services != null && services.length > 0);
	MobyService service = services[0];

	assertTrue("The test asynchronous MOBY service (" + serviceName + 
		   ") is not registered as asynchronous",
		   service.isAsynchronous());

	MobyPrimaryData[] inputs = service.getPrimaryInputs();
	assertTrue("The test asynchronous MOBY service (" + serviceName + 
		   ") does not have any input defined, but a service with " +
		   "input must be used for testing purposes", 
		   inputs != null && inputs.length > 0);

	MobyPrimaryData[] outputs = service.getPrimaryOutputs();
	assertTrue("The test asynchronous MOBY service (" + serviceName + 
		   ") does not have any output defined, but a service with " +
		   "output must be used for testing purposes",
		   outputs != null && outputs.length > 0);

	URL dataURL = getClass().getClassLoader().getResource(TEST_INPUT_RESOURCE);
	assertNotNull("Could not find the test data resource (" + TEST_INPUT_RESOURCE + ")", dataURL);
	MobyContentInstance data = null;
	try{
	    data = MobyDataUtils.fromXMLDocument(dataURL.openStream());
	} catch(IOException ioe){
	    ioe.printStackTrace();
	    fail("I/O error while reading the test data (" + dataURL + "): " + ioe);
	    //	} catch(MobyException mobye){
	    //mobye.printStackTrace();
	    //fail("Moby logical error while parsing the test data (" + dataURL + "): " + mobye);
	} catch(Exception e){
	    e.printStackTrace();
	    fail("General exception while reading and parsing the test data (" + dataURL + "): " + e);
	}
	assertNotNull("Could not load the test data (" + dataURL + ")", data);

	for(MobyDataJob job: data.values()){
	    String jobKeys = "";
	    for(String key: job.keySet()){
		jobKeys += key + " ";
	    }

	    for(MobyPrimaryData input: inputs){
		if(!job.containsKey(input.getName())){
		    System.err.println("Test Input Data: " + job);
		    fail("The service requires an input ("+input.getName() + 
			 ") that was not provided in the test input data (" +
			 dataURL + ").  The provided input: "+jobKeys);
		}
		if(!(job.get(input.getName()) instanceof MobyPrimaryData)){
		    System.err.println("Test Input Data: " + job);
		    fail("The service requires a primary input ("+input.getName() + 
			 ") but the data with that article name was not primary " +
			 "data in the test input resource (" + dataURL + ")");
		}
		MobyPrimaryData inputInstance = (MobyPrimaryData) job.get(input.getName());
		if(!inputInstance.getDataType().inheritsFrom(input.getDataType())){
		    System.err.println("Test Input Data: " + job);
		    fail("The service input \""+input.getName() + 
			 "\" is not of the correct data type (expected \"" + 
			 input.getDataType().getName() +
			 "\", but got \"" + 
			 inputInstance.getDataType().getName() + 
			 "\") in the test input data (" +
			 dataURL + ")");
		}
	    }
	}

	// The real meat of the call
	EndpointReference epr = null;
	try{
	    epr = AsyncClient.sendRequest(service, data);
	} catch(Exception e){
	    e.printStackTrace();
	    fail("Exception while sending request to service: " + e);
	}

	long startTime = System.currentTimeMillis();
	// Essentially cloning, so removing ids doesn't change the 
	// MobyContentInstance "data" (which we will use again later on)
	Set<String> queryIDs = new HashSet(data.keySet());
	try {
	    while(!queryIDs.isEmpty()){
		if(System.currentTimeMillis()-startTime > MAX_WAITTIME_MS){
		    destroyEPR(epr);
		    fail("The service ("+serviceName + 
			 ") took longer than the maximum allowed time to execute ("+
			 MAX_WAITTIME_MS+"ms)");
		}

		Thread.sleep(POLLING_WAITTIME_MS);
		AnalysisEvent[] events = 
		    AsyncClient.poll(epr, queryIDs);
		for(AnalysisEvent event: events){
		    if(event != null){
			System.err.println("Event: " + event);
			if(event.isCompleted()){
			    queryIDs.remove(event.getQueryId());
			}
		    }
		}
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	    destroyEPR(epr);
	    fail("Exception occured while polling the service invocation: " + e);
	}

	String resultText = null;
	try {
	    resultText = AsyncClient.getResultText(epr, data.keySet());
	} catch (Exception e) {
	    e.printStackTrace();
	    destroyEPR(epr);
	    fail("Exception while retrieving results of the test asynchronous service invocation '" +
		 epr.getServiceInvocationId() + "'" +
		 "(even though AsyncClient said they were ready): " + e);
	}
	assertNotNull("AsyncClient returned a null string result for the test " +
		      "asynchronous service invocation ("+epr.getServiceInvocationId()+")", resultText);

	javax.xml.parsers.DocumentBuilder docBuilder = null;
	try{
	    javax.xml.parsers.DocumentBuilderFactory dbf = 
		javax.xml.parsers.DocumentBuilderFactory.newInstance();
	    dbf.setNamespaceAware(true);	
	    docBuilder = dbf.newDocumentBuilder();
	}
	catch(javax.xml.parsers.ParserConfigurationException pce){
	    pce.printStackTrace();
	    destroyEPR(epr);
	    fail("An XML parser could not be found or configured: " + pce);
	}
	    
	Element soapDOM = null;
	try{
	    soapDOM = docBuilder.parse(new org.xml.sax.InputSource(new java.io.StringReader(resultText))).getDocumentElement();
	} catch(org.xml.sax.SAXException saxe){
	    saxe.printStackTrace();
	    System.err.println("Response SOAP payload:\n" + resultText);
	    destroyEPR(epr);
	    fail("Exception while parsing the SOAP message (bad XML?): " + saxe);
	} catch(java.io.IOException ioe){
	    ioe.printStackTrace();
	    destroyEPR(epr);
	    fail("Exception while parsing the SOAP message (I/O): " + ioe);
	}

	Element mobyDOM = null;
	StringBuffer outputXmlBuffer = new StringBuffer();
	try{
	    boolean IS_ASYNC_SERVICE_CALL = true;
	    mobyDOM = (new MobyRequest(central)).decodeSOAPMessage(soapDOM,
								   //service.getName(),
								   outputXmlBuffer, 
								   resultText, 
								   IS_ASYNC_SERVICE_CALL);
	} catch(Exception e){
	    e.printStackTrace();
	    destroyEPR(epr);
	    System.err.println("Response SOAP payload:\n" + resultText);
	    fail("Exception while extracting the MOBY document from the test async service SOAP response: "+e);
	}
	if(mobyDOM == null){
	    System.err.println("Response SOAP payload:\n" + resultText);
	    fail("Moby DOM extracted from test async service SOAP response was null (should have "+
		 data.size()+" moby data blocks)");
	}

	// Verify the results
	MobyContentInstance results = null;
	try{
	    results = MobyDataUtils.fromXMLDocument(mobyDOM);
	}
	catch(Exception e){
	    e.printStackTrace();
	    System.err.println("Server response:\n" + resultText);
	    destroyEPR(epr);
	    fail("Exception while parsing the results of the test asynchronous service: " + e); 
	}
	assertNotNull("AsyncClient returned a string result that could not " +
		      "be parsed by MobyDataUtils (null returned)", results);

	for(MobyDataJob job: results.values()){
	    for(MobyPrimaryData output: outputs){
		if(!job.containsKey(output.getName())){
		    System.err.println("Results job contents:\n" + job);
		    fail("A service result does not contain one of the " +
			 "outputs specified by the service signature (" + 
			 output.getName() + ")");
		    if(!(job.get(output.getName()) instanceof MobyPrimaryData)){
			System.err.println("Results job contents: " + job);
			fail("The service was supposed to return a primary output ("+output.getName() + 
			     ") but the data with that article name was not primary " +
			     "data in the test service output (" + serviceName + ")");
		    }
		    MobyPrimaryData outputInstance = (MobyPrimaryData) job.get(output.getName());
		    if(!outputInstance.getDataType().inheritsFrom(output.getDataType())){
			System.err.println("Results job contents: " + job);
			fail("The service output \""+output.getName() + 
			     "\" is not of the correct data type (expected \"" + 
			     output.getDataType().getName() +
			     "\", but got \"" + 
			     outputInstance.getDataType().getName() + 
			     "\") in the test service output (" +
			     serviceName + ")");
		    }
		}
	    }
	}
	    
	if(data.size() != results.size()){
	    System.err.println("Server response:\n" + resultText);
	    fail("The number of results (" + results.size() + ") in the response from " +
		 "the test asynchronous service was not equal to the number of test inputs (" +
		 data.size()+") given, which is required by the MOBY spec (even in the " +
		 "case of service failure)");
	}

	System.out.println("Issuing a clean up call (Destroy)");
	destroyEPR(epr);
    }
    
    private void destroyEPR(EndpointReference epr){
	try{
	    AsyncClient.destroy(epr);
	} catch(Exception e){
	    System.err.println("Exception while destroying the service invocation " +
			       "endpoint reference (but this was is not a fatal error): " + e);
	    e.printStackTrace();
	}
    }

    /**
     * @return a test suite for all the test methods of this test case.
     */
    public static Test suite() {
	TestSuite suite = new TestSuite();
 	suite.addTest(new AsyncClientTestCase("testCall"));
        return suite;
    }

    /**
     * Runs the test suite when the class is invoked on the command line.
     * The name of the service can be defined through a Java property, 
     * moby.testService
     */
    public static void main(String args[]) throws Exception{
        junit.textui.TestRunner.run(suite());
    } 
}
