/**	
 * This is the simplest Java MOBY client you can have.  A fully documented version
 * is available in the TestRequest class, with all the proper error checking. 
 */

import org.biomoby.client.*;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;
import com.hp.hpl.jena.rdf.model.*;
import org.biomoby.service.MobyServlet;

import org.w3c.dom.*;

import javax.xml.parsers.*;
import java.util.*;
import java.io.FileInputStream;

public class ServletTester{
    public static void main(String[] args) throws Exception{

        if(args.length != 2 && args.length != 3 || 
	   args.length == 3 && !("register".equals(args[2]) || "register_permanent".equals(args[2]))){
	    System.err.println("Usage: java -jar FooService.war " +
			       "<fully-qualified servlet url> <exampleMobyData.xml (register|register_permanent) | unregister>");
	    System.exit(1);
        }
	if(args[0].indexOf("http://localhost") == 0){
	    System.err.println("The servlet URL must be fully qualified " + 
			       "(\"localhost\" is not acceptable)");
	    System.exit(1);
	}

	ServletTester tester = new ServletTester();

        // Open the web.xml file to determine the service name and inputs/outputs.
	java.net.URL webXmlURL = tester.getClass().getClassLoader().getResource("WEB-INF/web.xml");
	if(webXmlURL == null){
	    System.err.println("Could not find \"WEB-INF/web.xml\", aborting!");
	    System.exit(1);
	}
	
        String centralURL = null;
        String testServiceName = null;
        String testServiceClassName = null;
	String inParams = null;
	String outParams = null;
	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	dbf.setNamespaceAware(false);	
	DocumentBuilder docBuilder = dbf.newDocumentBuilder();

	Element docRoot = null;
        try{
	    docRoot = docBuilder.parse(webXmlURL.openStream()).getDocumentElement();
	    if(docRoot == null || !docRoot.getNodeName().equals("web-app")){
		System.err.println("Could not find /web-app tag in " + 
				   webXmlURL  + ", aborting!");
		System.exit(1);
	    }
	} catch(Exception e){
	    System.err.println("There appears to be a problem with the \"WEB-INF/web.xml\" file (XML syntax?)." +
			       "While parsing it I encountered the error: " + e);
	    e.printStackTrace();
	    System.exit(1);
	}
	Element servlet = getChildElement(docRoot, "servlet");
	if(servlet == null){
	    System.err.println("Could not find /web-app/servlet/ tag in " + 
			       webXmlURL  + ", aborting!");
	    System.exit(1);
	}
	Element servletName = getChildElement(servlet, "servlet-name");
	if(servletName == null){
	    System.err.println("Could not find /web-app/servlet/servlet-name tag in " + 
			       webXmlURL  + ", aborting!");
	    System.exit(1);
	}
	testServiceName = servletName.getTextContent();

        MobyService testService = new MobyService(testServiceName);
        testService.setURL(args[0]);
	// When we GET this URL, the RDF is returned
	if(args.length == 3 && "register_permanent".equals(args[2])){
	    testService.setSignatureURL(args[0]); //signature set == MOBY agent, please don't delete!
	}
	testService.setAuthoritative(false); //unless the web.xml tells us otherwise

	Vector<MobyPrimaryData> inputTypes = new Vector<MobyPrimaryData>();
	Vector<MobyPrimaryData> outputTypes = new Vector<MobyPrimaryData>();
	NodeList params = docRoot.getElementsByTagName("context-param");
	for(int i = 0; i < params.getLength(); i++){
	    Element paramName = getChildElement((Element) params.item(i), 
						"param-name");
	    if(paramName == null){
		System.err.println("Could not find /web-app/context-param/param-name tag in " + 
				   webXmlURL  + " (context-param #" + (i+1) +"), aborting!");
		System.exit(1);
	    }
	    Element paramValue = getChildElement((Element) params.item(i), 
						 "param-value");
	    if(paramName == null){
		System.err.println("Could not find /web-app/context-param/param-value tag in " + 
				   webXmlURL  + " (context-param #" + (i+1) +"), aborting!");
		System.exit(1);
	    }
	    
	    Map<String,MobyDataTypeTemplate> map = new HashMap<String,MobyDataTypeTemplate>();
	    if(MobyServlet.MOBY_INPUT_PARAM.equals(paramName.getTextContent())){
		String values = paramValue.getTextContent();
		if(values != null && values.length() != 0){
		    StringTokenizer st = new StringTokenizer(values, ",");
		    // non-void param
		    while(st.hasMoreTokens()){
			inputTypes.add(MobyServlet.stringToPrimaryDataTemplate(st.nextToken(), map));
		    }
		}
	    }
	    else if(MobyServlet.MOBY_OUTPUT_PARAM.equals(paramName.getTextContent())){
		String values = paramValue.getTextContent();
		if(values != null && values.length() != 0){
		    StringTokenizer st = new StringTokenizer(values, ",");
		    // non-void param
		    while(st.hasMoreTokens()){
			outputTypes.add(MobyServlet.stringToPrimaryDataTemplate(st.nextToken(), map));
		    }
		}
	    }
	    else if(MobyServlet.MOBY_CENTRAL_URL_PARAM.equals(paramName.getTextContent())){
		String value = paramValue.getTextContent();
		if(value != null && value.length() != 0){
		    centralURL = value;
		}
	    }
	    else if(MobyServlet.MOBY_SERVICETYPE_PARAM.equals(paramName.getTextContent())){
		String param = paramValue.getTextContent();
		if(param.length() == 0){
		    throw new Exception("Required " + MobyServlet.MOBY_SERVICETYPE_PARAM + 
					" parameter in servlet context cannot be blank");
		}
		MobyServiceType serviceType = MobyServiceType.getServiceType(param);
		if(serviceType == null){
		    throw new Exception("The service type specified (" + param + 
					") was not found in the service type ontology");
		}
		testService.setServiceType(serviceType);
	    }
	    else if(MobyServlet.MOBY_SERVICE_DESC_PARAM.equals(paramName.getTextContent())){
		String param = paramValue.getTextContent();
		if(param.length() == 0){
		    throw new Exception("Required " + MobyServlet.MOBY_SERVICE_DESC_PARAM + 
					" parameter in servlet context cannot be blank");
		}
		testService.setDescription(param);
	    }
	    else if(MobyServlet.MOBY_PROVIDER_URI_PARAM.equals(paramName.getTextContent())){
		String param = paramValue.getTextContent();
		if(param.length() == 0){
		    throw new Exception("Required " + MobyServlet.MOBY_PROVIDER_URI_PARAM + 
					" parameter in servlet context cannot be blank");
		}
		testService.setAuthority(param);

		// Cannot have : from hh:mm:ss in LSID
		String time = MobyDataDateTime.getString(new GregorianCalendar(TimeZone.getTimeZone("Zulu"))).replaceAll(":", "-");
		// Now we have all the info we need to create the LSID for the service
		testService.setLSID("urn:lsid:biomoby.org:serviceinstance:"+param+","+
				    testServiceName+":"+ time);
	    }
	    // Other fields (authoritative and contact info) are highly recommended, but optional
	    else if(MobyServlet.MOBY_AUTHORITATIVE_PARAM.equals(paramName.getTextContent())){
		String param = paramValue.getTextContent();
		if(param != null){
		    if("YES".equals(param.toUpperCase()) || "Y".equals(param.toUpperCase()) || "1".equals(param)){
			testService.setAuthoritative(true);
		    }
		    else{
			testService.setAuthoritative(false);
		    }
		}
	    }
	    else if(MobyServlet.MOBY_CONTACT_PARAM.equals(paramName.getTextContent())){
		String param = paramValue.getTextContent();
		if(param != null){
		    testService.setEmailContact(param);
		}
	    }
	    // else, ignore it, the param is for some other purpose
	}

	// Setup communication with MOBY Central
        Central worker = null;
	if(centralURL != null){
	    worker = new CentralImpl(centralURL);
	}
	else{  // Use the default
	    worker = new CentralImpl();
	}

	if("unregister".equals(args[1])){
	    unregisterService(testService, worker);
	    System.exit(0);
	}

	// Make sure the example data provided is okay, if the service takes any input at all.
	MobyContentInstance testData = null;
	if(inputTypes.size() > 0){
	    FileInputStream dataIn = null;
	    try{
		dataIn = new FileInputStream(args[1]);
	    } catch(Exception e){
		e.printStackTrace();
	    }
	    if(dataIn == null){
		System.err.println("Could not read example data file \"" + args[1] + "\", aborting!");
		System.exit(1);
	    }
	    
	    // Create the data and service instances.
	    testData = MobyDataUtils.fromXMLDocument(dataIn);
	}
	else{
	    // Create a blank request if the input is void
	    testData = new MobyContentInstance();
	    testData.put(new MobyDataJob());
	}

        testService.setInputs(inputTypes.toArray(new MobyData[inputTypes.size()]));

	// Check the example data against the input spec
	Iterator<String> jobIDs = testData.keySet().iterator();
	while(jobIDs.hasNext()){
	    MobyDataJob job = testData.get(jobIDs.next());
	    MobyServlet.validateArguments(job, testService, "Input value to be sent to the service");
	}

	MobyRequest mr = new MobyRequest(worker);
	mr.setService(testService);
	mr.setInput(testData);
        long startTime = System.currentTimeMillis();
	MobyContentInstance resultData = mr.invokeService();
        long endTime = System.currentTimeMillis();
	System.out.println(resultData.toString());

	// Check output types, using the same method that checks input types
	testService.setOutputs(outputTypes.toArray(new MobyData[outputTypes.size()]));

	MobyService testServiceOut = new MobyService(testServiceName);
	testServiceOut.setInputs(outputTypes.toArray(new MobyData[outputTypes.size()]));
	jobIDs = resultData.keySet().iterator();
	while(jobIDs.hasNext()){
	    MobyDataJob job = resultData.get(jobIDs.next());
	    MobyServlet.validateArguments(job, testServiceOut, "Returned value from service");
	}

        System.out.println("Time to execute service: " + (endTime-startTime) + " ms");

	if(args.length != 3){
	    System.exit(0);
	}
	else{
	    registerService(testService, worker);
	}

    }

    public static void registerService(MobyService service, Central central) throws Exception{
	// Tell MOBY Central about the service (it serves its own RDF on GET requests)
	//System.err.println(((CentralImpl) central).getRegisterServiceXML(service));

	// Throws an exception if it fails to register
	central.registerService(service);

	// TO DO: check RDF discrepancies?
	System.err.println("Service Successfully Registered!");
    }
    
    public static void unregisterService(MobyService service, Central central) throws Exception{
	// Tell MOBY Central about the service (it serves its own RDF on GET requests)
	//System.err.println(((CentralImpl) central).getRegisterServiceXML(service));

	// Throws an exception if it fails to register
	central.unregisterService(service);

	// TO DO: check RDF discrepancies?
	System.err.println("Service Successfully Unregistered!");
    }
    
    public static Element getChildElement(Element parent, String elementName){
	if(elementName == null || parent == null){
	    return null;
	}

	NodeList children = parent.getChildNodes();
	for(int i = 0; children != null && i < children.getLength(); i++){
	    // Make sure it's an element, not a processing instruction, text, etc.
	    if(!(children.item(i) instanceof Element)){
		continue;
	    }
	    Element child = (Element) children.item(i);
	    // Make sure it has the right name, or wildcard
	    if(elementName.equals(child.getTagName())){
		return child;
	    }
	}
	return null;	
    }
}
