/*
+----------------------------------------------------------------------+
|                    Class: CLauncherImpl                              |
|                                                                      |
| Developper:  Eric Gavaldo (eric.gavaldo@xqual.com)                   |
| Version:     1.3                                                     |
| License:     LGPL (http://www.gnu.org/licenses/lgpl.html)            |
+----------------------------------------------------------------------+
*/
package com.xqual.xlauncher.proxy;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.JOptionPane;

import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.util.ClientFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.xqual.xagent.launcher.CExecutionStep;
import com.xqual.xagent.launcher.CLauncher;
import com.xqual.xagent.launcher.CReturnStatus;
import com.xqual.xcommon.CAttribute;
import com.xqual.xcommon.CXmlDocumentFactory;
import com.xqual.xcommon.IConstantsResults;
import com.xqual.xcommon.thread.CSwingWorker;
import com.xqual.xcommon.type.CParam;
import com.xqual.xcommon.type.CParamParsingException;
import com.xqual.xcommon.utils.CUtils;

/**
 * The <code>CLauncherImpl</code> implementation of <code>ILauncher</code> for Proxying to several SubAgents.
 * @author egavaldo
 */
public class CLauncherImpl extends CLauncher implements IConstantsResults {

	// +==============================================================+
	// | Attributes                                                   |
	// +==============================================================+

	static final String TRACE_HEADER = "{proxy         }  ";

	private final Vector<XmlRpcClientConfigImpl> xmlRpcClientConfigVector;
	private final Vector<XmlRpcClient>           xmlRpcClientVector;
	private final Vector<ClientFactory>          factoryVector;
	private final Vector<IRemoteSubAgent>        remoteSubAgentVector;

	private Vector<String> xmlRpcServerUrlVector;
	private Vector<String> jarNameVector;
	private Vector<String> confVector;
	private Vector<String> postFixVector;

	// +==============================================================+
	// | Constructors                                                 |
	// +==============================================================+

	public CLauncherImpl() {
		super(TRACE_HEADER);

		xmlRpcClientConfigVector = new Vector<XmlRpcClientConfigImpl>();
		xmlRpcClientVector       = new Vector<XmlRpcClient>();
		factoryVector            = new Vector<ClientFactory>();
		remoteSubAgentVector     = new Vector<IRemoteSubAgent>();
	}

	// +==============================================================+
	// | Methods                                                      |
	// +==============================================================+

	@Override
	public CReturnStatus initialize(int sutId, String sutName, String sutVersion) {
		traceln(LOG_PRIORITY_INFO, "initialize sutId=" + sutId + " sutName=" + sutName + " sutVersion=" + sutVersion + "...");
		setSutDetails(sutId, sutName, sutVersion);

		// check the configuration sent by the manager
		printConfiguration();

		Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();

		xmlRpcServerUrlVector = new Vector<String>();
		jarNameVector         = new Vector<String>();
		confVector            = new Vector<String>();
		postFixVector         = new Vector<String>();
		try {
			int nbSubAgents = getNbFormsWithName("SubAgent");
			traceln(LOG_PRIORITY_INFO, "Number of sub-agents: " + nbSubAgents);
			for (int i=0; i<nbSubAgents; i++) {
				traceln(LOG_PRIORITY_INFO, "----------------------------------");
				traceln(LOG_PRIORITY_INFO, "SubAgent #" + i);
				traceln(LOG_PRIORITY_INFO, "----------------------------------");
				String xmlRpcServerUrl = getStringParamValue("SubAgent", "XML-RPC URL", i);
				String jarName         = getStringParamValue("SubAgent", "Launcher", i);
				String conf            = getStringParamValue("SubAgent", "Configuration", i);
				String postFix         = getStringParamValue("SubAgent", "Script postfix", i);
				traceln(LOG_PRIORITY_INFO, "- xmlrpc server url  = " + xmlRpcServerUrl);
				traceln(LOG_PRIORITY_INFO, "- jar name           = " + jarName);
				traceln(LOG_PRIORITY_INFO, "- configuration name = " + conf);
				traceln(LOG_PRIORITY_INFO, "- script postFix     = " + postFix);

				xmlRpcServerUrlVector.add(xmlRpcServerUrl);
				jarNameVector.add(jarName);
				confVector.add(conf);
				postFixVector.add(postFix);
			}
		} catch (CParamParsingException e) {
			traceln(LOG_PRIORITY_SEVERE, "parsing error during initialization");
			executionSteps.add(new CExecutionStep(RESULT_FAILURE, "Exception during initialize: " + e.getMessage()));
			return new CReturnStatus(RESULT_FAILURE, executionSteps);
		}


		// +----------------------------------------+
		// | Instanciate N subAgents/launchers      |
		// +----------------------------------------+
		for (int i=0; i<xmlRpcServerUrlVector.size(); i++) {
			try {
				xmlRpcClientConfigVector.add(new XmlRpcClientConfigImpl());
				xmlRpcClientConfigVector.get(i).setServerURL(new URL(xmlRpcServerUrlVector.get(i)));
				xmlRpcClientConfigVector.get(i).setEncoding("UTF-8");
				xmlRpcClientConfigVector.get(i).setEnabledForExtensions(true);
				// xmlRpcClientConfigVector.get(i).setBasicUserName(username);
				// xmlRpcClientConfigVector.get(i).setBasicPassword(password);
				// xmlRpcClientConfigVector.get(i).setConnectionTimeout(60 * 1000);
				// xmlRpcClientConfigVector.get(i).setReplyTimeout(60 * 1000);

				xmlRpcClientVector.add(new XmlRpcClient());
				xmlRpcClientVector.get(i).setConfig(xmlRpcClientConfigVector.get(i));

				factoryVector.add(new ClientFactory(xmlRpcClientVector.get(i)));
				remoteSubAgentVector.add((IRemoteSubAgent) factoryVector.get(i).newInstance(IRemoteSubAgent.class));

				try {
					// request the subAgent to instantiate the right launcher with the right configuration
					remoteSubAgentVector.get(i).instanciateLauncher(jarNameVector.get(i), confVector.get(i));

					// forward the initialize() request to the launchers
					remoteSubAgentVector.get(i).initialize(sutId, sutName, sutVersion);
				} catch (Exception e) {
					e.printStackTrace();
			 		CUtils.showErrorMessage(
								null,
								"Error: Operation impossible",
								"The SubAgent " + xmlRpcServerUrlVector.get(i) + " cannot be reached",
								"Please, check that the SubAgent is up and running and listening on the right port",
								JOptionPane.ERROR_MESSAGE);
					executionSteps.add(new CExecutionStep(RESULT_FAILURE, "cannot contact subagent: " + e.getMessage()));
					return new CReturnStatus(RESULT_FAILURE, executionSteps);
				}
			} catch (MalformedURLException e) {
				e.printStackTrace();
				CUtils.showErrorMessage(
							null,
							"Error: Operation impossible",
							"The SubAgent " + xmlRpcServerUrlVector.get(i) + " cannot be reached",
							"Please, check that the SubAgent is up and running and listening on the right port",
							JOptionPane.ERROR_MESSAGE);
				executionSteps.add(new CExecutionStep(RESULT_FAILURE, "subagent wrong url: " + e.getMessage()));
				return new CReturnStatus(RESULT_FAILURE, executionSteps);
			}
		}
		return new CReturnStatus(RESULT_SUCCESS, executionSteps);
	}

	@Override
	public CReturnStatus preRun(int testId, String testPath, String testName, Vector<CAttribute> attributes, String additionalInfo) {
		traceln(LOG_PRIORITY_INFO, "preRun testId=" + testId + " testPath=" + testPath + " [" + testName + "]...");

		Vector<CPreRunSwingWorker> preRunSwingWorkerVector = new Vector<CPreRunSwingWorker>();
		for (int i=0; i<xmlRpcServerUrlVector.size(); i++) {
			CPreRunSwingWorker preRunSwingWorker = new CPreRunSwingWorker(testId,
																		  testPath,
																		  testName,
																		  attributeVectorToObjectArray(attributes),
																		  additionalInfo,
																		  remoteSubAgentVector.get(i),
																		  "[SubAgent#" + i + "] ", // logPrefix
																		  postFixVector.get(i)); // scriptPotfix
			preRunSwingWorkerVector.add(preRunSwingWorker);
			preRunSwingWorkerVector.get(i).execute();
		}

		return consolidateResults(preRunSwingWorkerVector, false);
	}

	@Override
	public CReturnStatus run(int testId, String testPath, String testName, int testcaseId, int testcaseIndex, String testcaseName, Vector<CParam> params, String additionalInfo) {
		traceln(LOG_PRIORITY_INFO, "run testId=" + testId + " testPath=" + testPath + " [" + testName + "] testcaseIndex=" + testcaseIndex + "...");

		Vector<CRunSwingWorker> runSwingWorkerVector = new Vector<CRunSwingWorker>();
		for (int i=0; i<xmlRpcServerUrlVector.size(); i++) {
			CRunSwingWorker runSwingWorker = new CRunSwingWorker(testId,
															     testPath,
															     testName,
															     testcaseId,
															     testcaseIndex,
															     testcaseName,
															     paramVectorToObjectArray(params),
															     additionalInfo,
															     remoteSubAgentVector.get(i),
															     "[SubAgent#" + i + "] ", // logPrefix
															     postFixVector.get(i)); // scriptPotfix
			runSwingWorkerVector.add(runSwingWorker);
			runSwingWorkerVector.get(i).execute();
		}

		return consolidateResults(runSwingWorkerVector, true);
	}

	@Override
	public CReturnStatus postRun(int testId, String testPath, String testName) {
		Vector<CPostRunSwingWorker> postRunSwingWorkerVector = new Vector<CPostRunSwingWorker>();
		for (int i=0; i<xmlRpcServerUrlVector.size(); i++) {
			CPostRunSwingWorker postRunSwingWorker = new CPostRunSwingWorker(testId,
																		    testPath,
																		    testName,
																		    remoteSubAgentVector.get(i),
																		    "[SubAgent#" + i + "] ", // logPrefix
																		    postFixVector.get(i)); // scriptPotfix
			postRunSwingWorkerVector.add(postRunSwingWorker);
			postRunSwingWorkerVector.get(i).execute();
		}

		return consolidateResults(postRunSwingWorkerVector, false);
	}

	@Override
	public CReturnStatus terminate() {
		Vector<CTerminateSwingWorker> terminateSwingWorkerVector = new Vector<CTerminateSwingWorker>();
		for (int i=0; i<xmlRpcServerUrlVector.size(); i++) {
			CTerminateSwingWorker terminateSwingWorker = new CTerminateSwingWorker(remoteSubAgentVector.get(i),
																		           "[SubAgent#" + i + "] "); // logPrefix
			terminateSwingWorkerVector.add(terminateSwingWorker);
			terminateSwingWorkerVector.get(i).execute();
		}

		return consolidateResults(terminateSwingWorkerVector, false);
	}

	// +-----------------------------+
	// |          Utilities          |
	// +-----------------------------+

	public static Object[] attributeVectorToObjectArray(Vector<CAttribute> input) {
		return input.toArray();
	}
	public static Object[] paramVectorToObjectArray(Vector<CParam> input) {
		return input.toArray();
	}

	@SuppressWarnings("unchecked")
	private CReturnStatus consolidateResults(Object input, boolean runConsolidation) {
		boolean allSuccess         = true;
		boolean allNotExecuted     = true;
		boolean atLeastOneFailure  = false;

		Vector<CSwingWorker<Object, Void>> swingWorkerVector = (Vector<CSwingWorker<Object, Void>>)input;

		// wait all the threads are completed - get() is a blocking call
		// and get the return status of each thread
		Vector<CExecutionStep> consolidatedExecutionSteps = new Vector<CExecutionStep>();
		Enumeration<CSwingWorker<Object, Void>> enumerationSwingWorker = swingWorkerVector.elements();
		while (enumerationSwingWorker.hasMoreElements()) {
			CReturnStatus returnStatus;
			try {
				// wait for the swingWorker to finish his job and retrieve the returnStatus object
				returnStatus = (CReturnStatus)enumerationSwingWorker.nextElement().get();
			} catch (Exception e) {
				Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();
				executionSteps.add(new CExecutionStep(RESULT_FAILURE, "couldn't retrieve the result from the thread: " + e));
				returnStatus = new CReturnStatus(RESULT_FAILURE, executionSteps);
			}

			// prepare consolidation of the results
			short result = returnStatus.getResult();
			if (result != RESULT_SUCCESS)                          allSuccess        = false;
			if (runConsolidation && result != RESULT_NOT_EXECUTED) allNotExecuted    = false;
			if (result == RESULT_FAILURE)                          atLeastOneFailure = true;

			// consolidate the logs
			consolidatedExecutionSteps.addAll(returnStatus.getExecutionSteps());
		}

		// consolidate the results
		short consolidatedResult = runConsolidation ? RESULT_RELATIVE : RESULT_UNKNOWN;
		if (allSuccess)                              consolidatedResult = RESULT_SUCCESS;
		else if (runConsolidation && allNotExecuted) consolidatedResult = RESULT_NOT_EXECUTED;
		else if (atLeastOneFailure)                  consolidatedResult = RESULT_FAILURE;
		return new CReturnStatus(consolidatedResult, consolidatedExecutionSteps);
	}


	private Vector<CExecutionStep> nodeToExecutionStepVector(Object input, String logPrefix) {
		Vector<CExecutionStep> output = new Vector<CExecutionStep>();

		if (input instanceof Node) {
			Node rootNode = (Node)input;
			NodeList executionStepNodeList = CXmlDocumentFactory.getNodeObjListFromXPath(rootNode, "//executionStep");

			for (int i=0; i<executionStepNodeList.getLength(); i++) {
				Node executionStepNode = executionStepNodeList.item(i);
				String timeStamp = executionStepNode.getAttributes().getNamedItem("timeStamp").getNodeValue();
				short result     = Short.parseShort(executionStepNode.getAttributes().getNamedItem("result").getNodeValue());
				String message   = executionStepNode.getAttributes().getNamedItem("message").getNodeValue();

				CExecutionStep executionStep = new CExecutionStep(timeStamp, result, logPrefix + message);
				output.add(executionStep);
			}
		}
		return output;
	}

	// +---------------------------------------------------------------------------+
	// |                THREADS COMMUNICATING WITH THE SUBAGENTS                   |
	// +---------------------------------------------------------------------------+

    public class CPreRunSwingWorker extends CSwingWorker<Object, Void> {
        private final int             testId;
        private final String          testPath;
        private final String          testName;
        private final Object[]        attributes;
        private final String          additionalInfo;

    	private final IRemoteSubAgent remoteSubAgent;
    	private final String          logPrefix;
    	private final String          scriptPostfix;

        CPreRunSwingWorker(int testId, String testPath, String testName, Object[] attributes, String additionalInfo,
                           IRemoteSubAgent remoteSubAgent, String logPrefix, String scriptPostfix) {
        	super();
            this.testId         = testId;
            this.testPath       = testPath;
            this.testName       = testName;
            this.attributes     = attributes;
            this.additionalInfo = additionalInfo;

            this.remoteSubAgent = remoteSubAgent;
            this.logPrefix      = logPrefix;
            this.scriptPostfix  = scriptPostfix;
        }

		@Override
		public Object doInBackground() {
			int testResult = remoteSubAgent.preRun(testId, testPath, testName + scriptPostfix, attributes, additionalInfo);

			Object objExecutionSteps = remoteSubAgent.getExecutionSteps();
			Vector<CExecutionStep> executionSteps = nodeToExecutionStepVector(objExecutionSteps, logPrefix);

			return new CReturnStatus((short)testResult, executionSteps);
		}

		@Override
		public void done() {
		}
	}

    public class CRunSwingWorker extends CSwingWorker<Object, Void> {
        private final int             testId;
        private final String          testPath;
        private final String          testName;
        private final int             testcaseId;
        private final int             testcaseIndex;
        private final String          testcaseName;
        private final Object[]         params;
        private final String          additionalInfo;

    	private final IRemoteSubAgent remoteSubAgent;
    	private final String          logPrefix;
    	private final String          scriptPostfix;

        CRunSwingWorker(int testId, String testPath, String testName, int testcaseId, int testcaseIndex, String testcaseName, Object[] params, String additionalInfo,
                        IRemoteSubAgent remoteSubAgent, String logPrefix, String scriptPostfix) {
        	super();
            this.testId         = testId;
            this.testPath       = testPath;
            this.testName       = testName;
            this.testcaseId     = testcaseId;
            this.testcaseIndex  = testcaseIndex;
            this.testcaseName   = testcaseName;
            this.params         = params;
            this.additionalInfo = additionalInfo;

            this.remoteSubAgent = remoteSubAgent;
            this.logPrefix      = logPrefix;
            this.scriptPostfix  = scriptPostfix;
        }

		@Override
		public Object doInBackground() {
			int testResult = remoteSubAgent.run(testId, testPath, testName + scriptPostfix, testcaseId, testcaseIndex, testcaseName, params, additionalInfo);

			Object objExecutionSteps = remoteSubAgent.getExecutionSteps();
			Vector<CExecutionStep> executionSteps = nodeToExecutionStepVector(objExecutionSteps, logPrefix);

			return new CReturnStatus((short)testResult, executionSteps);
		}

		@Override
		public void done() {
		}
	}

    public class CPostRunSwingWorker extends CSwingWorker<Object, Void> {
        private final int             testId;
        private final String          testPath;
        private final String          testName;

    	private final IRemoteSubAgent remoteSubAgent;
    	private final String          logPrefix;
    	private final String          scriptPostfix;

        CPostRunSwingWorker(int testId, String testPath, String testName,
                           IRemoteSubAgent remoteSubAgent, String logPrefix, String scriptPostfix) {
        	super();
            this.testId         = testId;
            this.testPath       = testPath;
            this.testName       = testName;

            this.remoteSubAgent = remoteSubAgent;
            this.logPrefix      = logPrefix;
            this.scriptPostfix  = scriptPostfix;
        }

		@Override
		public Object doInBackground() {
			int testResult = remoteSubAgent.postRun(testId, testPath, testName + scriptPostfix);

			Object objExecutionSteps = remoteSubAgent.getExecutionSteps();
			Vector<CExecutionStep> executionSteps = nodeToExecutionStepVector(objExecutionSteps, logPrefix);

			return new CReturnStatus((short)testResult, executionSteps);
		}

		@Override
		public void done() {
		}
	}

    public class CTerminateSwingWorker extends CSwingWorker<Object, Void> {
    	private final IRemoteSubAgent remoteSubAgent;
    	private final String          logPrefix;

    	CTerminateSwingWorker(IRemoteSubAgent remoteSubAgent, String logPrefix) {
        	super();
            this.remoteSubAgent = remoteSubAgent;
            this.logPrefix      = logPrefix;
        }

		@Override
		public Object doInBackground() {
			int testResult = remoteSubAgent.terminate();

			Object objExecutionSteps = remoteSubAgent.getExecutionSteps();
			Vector<CExecutionStep> executionSteps = nodeToExecutionStepVector(objExecutionSteps, logPrefix);

			remoteSubAgent.freeLauncher();

			return new CReturnStatus((short)testResult, executionSteps);
		}

		@Override
		public void done() {
		}
	}

}

