package component;

import java.util.HashMap;

import javax.annotation.Resource;

import javax.jbi.messaging.DeliveryChannel;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.InOut;

import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
import javax.naming.InitialContext;

import org.apache.log4j.Logger;
import org.apache.servicemix.MessageExchangeListener;
import org.apache.servicemix.components.util.ComponentSupport;

import org.apache.servicemix.jbi.jaxp.StringSource;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;

import org.apache.servicemix.client.ClientFactory;
import org.apache.servicemix.client.Destination;
import org.apache.servicemix.client.ServiceMixClient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import java.io.File;


import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

//import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.parsers.ParserConfigurationException;

public abstract class Orchestrator extends ComponentSupport implements 
MessageExchangeListener {

	private Logger logger = null;
	
	@Resource
	DeliveryChannel channel;
	
	String[] key = null;
	
	private String parameters = null;
	
	private String targetStatus = null;
	
	private String myWSStatus = null;
	
	private String operationName = null;
	
	private NodeList nodeList = null;
	
	private CompositionManager compositionManager = null;
	
	private	ServiceMixClient client = null;

	private int successor;
	
	private InOut inOutWithTarget = null;
	
	public Orchestrator() {
		logger = Logger.getLogger(this.getClass());
		
		//logger.info("Orchestrator constructor");
		key = new String[4];
		compositionManager = new CompositionManager();
		
		
		client = createClient();

		successor = 0;
	}
		
	public abstract int getOrchestratorId();
	
	public abstract String getBindingType();

	public abstract String getServiceName();

    public void onMessageExchange(MessageExchange exchange) throws MessagingException {

        NormalizedMessage message = getInMessage(exchange);
		DOMSource domSource = null;
	
		try{
			
			Source contentSource = getInMessage(exchange).getContent();
			
			String stringMsg = new SourceTransformer().toString(contentSource);
			
			domSource = new SourceTransformer().toDOMSource(contentSource);
	
			exchange.setStatus(ExchangeStatus.DONE);
	    	channel.send(exchange);
		   // logger.info("status done");

			Node node = domSource.getNode();
			Node n = node.getFirstChild();
			
			//Il messaggio proviene dal target
			if(n.getNodeName().equals("myMessage")){
			//logger.info("Messagge received : " + stringMsg);
			
			//operationName	
			Node n1 = n.getFirstChild();
			operationName = n1.getTextContent(); 		
			logger.info("Requested Operation : " + operationName);
  
  			//targetStatus
  			Node n2 = n1.getNextSibling();
  			targetStatus = n2.getTextContent(); 
  			logger.info("Target Status : " + targetStatus);
  			
  			//parameters
  			Node n3 = n2.getNextSibling();
  			String parameters = n3.getNodeName(); 
  			//logger.info("parameters : " + parameters);
  			
  			
  			nodeList =  n3.getChildNodes();
  			//logger.info("nodelist length : " + nodeList.getLength());

				myWSStatus = getServiceStatus(client); 
			
			logger.info("sends to Orchestrators its Web Service status : " + myWSStatus);
			
			myWSStatus =   "<orchestratorMessage>" +
						   		"<orchestratorNumber>" + getOrchestratorId() + "</orchestratorNumber>" +
						   		"<serviceStatus>" + myWSStatus + "</serviceStatus>" +
						   "</orchestratorMessage>";
			
			//logger.info("sends to Orchestrators : " + myWSStatus);

			  	 	sendToDestination(client, myWSStatus, "simpleMulticast"); 	
		}
		//il messaggio proviene da un altro orchestrator
     	else if(n.getNodeName().equals("orchestratorMessage")){
     	
     		//orchestratorNumber
			Node n1 = n.getFirstChild();
			String orchestratorNumber = n1.getTextContent(); 		
		
     		//logger.info("OrchestratorNumber " + orchestratorNumber);	
        	Integer keyIndex = Integer.parseInt(orchestratorNumber);
        	keyIndex--;
        	
  			Node n2 = n1.getNextSibling();
  			String serviceStatus = n2.getTextContent();
  			
       		logger.info("receives from Orchestrator" + orchestratorNumber + " status = "+ serviceStatus );
        	key[keyIndex] = serviceStatus;
        	
        	if(communicationFinished()){
        		
        		String toTheHash = operationName + targetStatus + key[0] + key[1] + key[2] + key[3];
        		key[0] = null;
        		key[1] = null;
        		key[2] = null;
        		key[3] = null;
        		
        		logger.info("composition requested : " + toTheHash );
        		boolean se = false;
        		try {
        			se = compositionManager.containsCompositionKey(toTheHash);
        			}
        		catch (NoCompositionException e){
        				logger.info("...composition doesn't exist for the current request...");
        				}
        		if(se){
        			
        			try{
        				
        				    String result;
        					int serviceID = compositionManager.getServiceToInvoke(toTheHash);
        					logger.info("selected Web Service : "+ serviceID);
        					if(serviceID == getOrchestratorId())
        						{
        								result = invokeOperation(client, operationName, nodeList);
        								logger.info("Web Service response : " + result);
        								
        								successor = compositionManager.getSuccessorStatusForTheTarget(operationName + targetStatus);
        								logger.info("Target successor status : " + successor);
        								
        								result = "<responseForTarget>" +	
        											"<return>" + result + "</return>" + 
        											"<successor>" + successor + "</successor>" +
        										 "</responseForTarget>";
        								
        								logger.info("I'm sending the following message to CommunicationManager : " + result );
        								sendToDestination(client, result, "communicationManager");
        						//		}
        						}
        					else{
        							//logger.info("I'm NOT the selected Web Service : " + getOrchestratorId());	
        						}
        				
        				}catch (NoCompositionException e){
        				logger.info("...composition doesn't exist for the current request...");
        				}

        		}
        	
        	}
		}
	} catch(Exception e) {
        	logger.info("error while reading payload", e);
        }
       
}  
	
	
	private void sendToDestination(ServiceMixClient client, String stringMsg, String stringDestination){
		try{
			Destination destination = client.createDestination(
			"service:http://myproject.com/components/"+ stringDestination);
			InOnly inOnlyExchange = destination.createInOnlyExchange();
			NormalizedMessage inMessage = inOnlyExchange.getInMessage();
			inMessage.setContent(new StringSource(stringMsg));
			//logger.info("sends !");
			
			client.send(inOnlyExchange);
			}catch(MessagingException e){
				logger.info("error while sending message", e);
				}
		}

	private String getServiceStatus(ServiceMixClient client2){
       
       	SourceTransformer sourceTransformer = new SourceTransformer();
       	String temp1 = null;
		String temp2 = null;
		try {

			InOut io = client2.createInOutExchange();
			io.setService(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", getServiceName()));
			io.setInterfaceName(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", getBindingType()));
			io.setOperation(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", "getStatus"));
	
			io.getInMessage().setContent(new StringSource(
			"<message xmlns='http://java.sun.com/xml/ns/jbi/wsdl-11-wrapper'>"
			+ "  <part>"
			+ "    <getStatus xmlns='http://myproject.com/NDWS" + getOrchestratorId() + "/types'>"
			+ "    </getStatus>"
			+ "  </part>"
			+ "</message>"));
			client2.sendSync(io);
	
			String stringStatus = new SourceTransformer().toString(io.getOutMessage().getContent());
			
				
			io.setStatus(ExchangeStatus.DONE);
        	client2.send(io);	
        //	logger.info("done status");
			  
			temp1 = stringStatus.split("<return>")[1];
			temp2 = temp1.split("</return>")[0];
			//logger.info("my service status :" + temp2);
			
		
			
			
			}catch(Exception e) {
				logger.info("error while requesting service status", e);
			}
		return temp2;
	}
        	
	private String invokeOperation(ServiceMixClient client, String opName, NodeList nodeList){
		

       	String temp1 = null;
		String temp2 = null;
		String stringReturnMessage = null;			
	//	logger.info("serviceName : "+ getServiceName());
	//	logger.info("bindingType : "+ getBindingType());
	
		
		int lunghezza = nodeList.getLength();
	//	logger.info("nodeList length : " + lunghezza);
		String arguments = "";      
		
		
		for( int i=0 ; i<nodeList.getLength(); i++ ){
	    	Node n = nodeList.item(i);
	    	String parameter = n.getTextContent();
	    	arguments = arguments + "<arg" + i + ">" + parameter + "</arg" + i + ">";
	    	}
	    	
	 //   logger.info("arguments: " + arguments);
			
		try {
			
			InOut io = client.createInOutExchange();
			io.setService(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", getServiceName()));
			io.setInterfaceName(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", getBindingType()));
			io.setOperation(new QName("http://myproject.com/NDWS" + getOrchestratorId() + "/", opName));
	
			String message =  "<message xmlns='http://java.sun.com/xml/ns/jbi/wsdl-11-wrapper'>"
			+ "  <part>"
			+ "    <" + opName + " xmlns='http://myproject.com/NDWS" + getOrchestratorId() + "/types'>"
			+ 		arguments
			+ "    </" + opName + ">"
			+ "  </part>"
			+ "</message>";
			
			logger.info(message);
			
			io.getInMessage().setContent(new StringSource(message));
			
			
			client.sendSync(io);
		
			Source contentSource = io.getOutMessage().getContent();
			
			stringReturnMessage = new SourceTransformer().toString(contentSource);
			logger.info("Messagge received from WS : " + stringReturnMessage);

			io.setStatus(ExchangeStatus.DONE);
       		client.send(io);	
        	//logger.info("done status");   
			
			stringReturnMessage = stringReturnMessage.substring(38);

			//logger.info("Message transformed : " + stringReturnMessage);
			
			//StringSource stringSource = new StringSource(stringReturnMessage);
			
			Node node = new SourceTransformer().toDOMNode(new StringSource(stringReturnMessage));

			Node n = node.getFirstChild();
			
			//Il messaggio proviene dal target
			if(n.getNodeName().equals("jbi:message")){
			//	logger.info("jbi:message"); 	
				}
				
			Node n1 = n.getFirstChild();
			if(n1.getNodeName().equals("jbi:part")){
			//	logger.info("jbi:part"); 	
				}
				
			Node n2 = n1.getFirstChild();
		//	logger.info("namespace:nomeoperazione"); 	
			
			
			Node n3 = n2.getFirstChild();
 			if(n3.getNodeName().equals("return")){
			//	logger.info("return"); 
				temp2 = n3.getTextContent();	
				}
			
			}catch(Exception e) {
			logger.info("error while invoking operation", e);
			}
		return temp2;
	}

    private ServiceMixClient createClient() {
		try {
			ClientFactory factory = (ClientFactory) new InitialContext().lookup(ClientFactory.DEFAULT_JNDI_NAME);
			return factory.createClient();
			
			} catch(Exception e) {
			e.printStackTrace();
			return null;
			}
	}
	
	private boolean communicationFinished() {
		for (String string : key) {
			if (string == null)
			return false;
			}
		return true;
	}
	


	
}
