/**
 * Copyright Tandgerg Television
 */
package com.tandbergtv.workflow.webservice.filesubsystem;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import javax.xml.rpc.ServiceException;
import javax.xml.rpc.server.ServiceLifecycle;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;

import com.tandbergtv.workflow.comm.HTTPDevice;
import com.tandbergtv.workflow.comm.IDestination;
import com.tandbergtv.workflow.message.IMessageKey;
import com.tandbergtv.workflow.message.IMessageUID;
import com.tandbergtv.workflow.message.MessageUIDImpl;
import com.tandbergtv.workflow.message.WorkflowMessage;
import com.tandbergtv.workflow.message.WorkflowMessage.MessageType;
import com.tandbergtv.workflow.message.util.MarshalException;
import com.tandbergtv.workflow.message.util.Marshaller;
import com.tandbergtv.workflow.webservice.filesubsystem.messagehandler.MessageHandler;
import com.tandbergtv.workflow.webservice.filesubsystem.messagehandler.MessageHandlerConfigurationException;
import com.tandbergtv.workflow.webservice.filesubsystem.messagehandler.MessageHandlerFactory;
import com.tandbergtv.workflow.webservice.filesubsystem.messagehandler.MessageParameters;

/**
 * File subsystem webservice which is responsible for File Management and SAN Drive Management. The
 * File Management functions do not support any operations with directories. Code would be required
 * to change in order to support directory operations.
 * 
 * Webservice expects the SOAP Request to contain a Workflow Message element marshalled by the
 * com.tandbergtv.workflow.message.util.Marshaller class.
 * 
 * @author kmehta
 * 
 */
public class FileSubsystemWebservice implements ServiceLifecycle
{
	private static final Logger logger = Logger.getLogger(FileSubsystemWebservice.class);

	private static final String UNKNOWN_UID = "Unknown UID";

	private RequestHandler requestHandler = null;

	private Marshaller marshaller = null;

	private MessageHandlerFactory mhFactory = null;

	/**
	 * Default Constructor.
	 */
	public FileSubsystemWebservice()
	{
	}

	/**
	 * Method to process the SOAP request.
	 * 
	 * @param request
	 *            The SOAP Envelope containing the request
	 * @param response
	 *            The SOAP Envelope that contains the response
	 */
	public void process(SOAPEnvelope request, SOAPEnvelope response)
	{
		WorkflowMessage message = null;
		WorkflowMessage result = null;
		String messageUID = null;

		try
		{
			logger.info("Received soap request: " + request);
			message = this.requestHandler.parseRequest(request);

			messageUID = message.getMessageUID().getUID();
			logger.info("Received valid Workflow Message with UID: " + messageUID);

			MessageHandler handler = this.mhFactory.getMessageHandler(messageUID);
			if (mhFactory.isMessageAsynchronous(messageUID))
			{
				String responseUID = mhFactory.getResponseUID(messageUID);
				result = handleAsyncMessage(handler, message, responseUID);
			}
			else
				result = handler.handleMessage(message);
		}
		catch (OperationException oe)
		{
			logger.error("Failed to Parse the request and read the Workflow Message.", oe);
			result = this.generateNackMessage(message, oe);
		}
		catch (MessageHandlerConfigurationException mhce)
		{
			logger.error("Failed to load the Message Handler for the UID: " + messageUID, mhce);
			result = this.generateNackMessage(message, mhce);
		}
		catch (Exception ex)
		{
			logger.error("Unexpected error when processing SOAP request for File Subsystem.", ex);
			result = this.generateNackMessage(message, ex);
		}

		try
		{
			SOAPBody body = response.getBody();
			body.addDocument(marshaller.marshal(result));
		}
		catch (SOAPException ex)
		{
			logger.error("Failed to add the Workflow Message response to the SOAP response.", ex);
			this.addSOAPFault(response, ex);
		}
		catch (MarshalException ex)
		{
			logger.error("Failed to marshal the Workflow Message response.", ex);
			this.addSOAPFault(response, ex);
		}
		catch (Exception ex)
		{
			logger.error("Unexpected error when writing the Workflow Message response.", ex);
			this.addSOAPFault(response, ex);
		}
	}

	/*
	 * Handling asynchronous method calls to the file subsystem
	 */
	private WorkflowMessage handleAsyncMessage(final MessageHandler handler,
			final WorkflowMessage message, final String responseUID)
	{
		// Change this to perform the operation in async manner. Need to generate basic ACK
		logger.debug("Handling Asynchronous Message: " + message.getMessageUID().getUID());

		// creating thread for the handling the message
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
		
		service.submit(new Runnable()
		{
			public void run()
			{
				WorkflowMessage response = handler.handleMessage(message);

				// Need to replace UID with response UID, and change Message Type if required
				response.setMessageUID(new MessageUIDImpl(responseUID));
				if (response.getType() != MessageType.nack)
					response.setType(MessageType.notification);

				logger.info("Response of the operation: " + response);
				handleCallBackMessage(message, response);
			}
		});
		
		service.shutdown();
		
		logger.debug("Finished Calling the runnable thread for handling the callback");
		return generateAckReceivedMessage(message);
	}

	/**
	 * handles the asynchronous call back by waiting for the task to complete and then once done
	 * makes a request to response destination as a asynchronous response
	 * 
	 * @param message
	 *            The received message that triggered the callback
	 * @param result
	 *            The response message
	 */
	public void handleCallBackMessage(WorkflowMessage message, WorkflowMessage result)
	{
		try
		{
			Document resultDoc = Marshaller.newMarshaller().marshal(result);

			IDestination destination = message.getResponseDestination();
			logger.info("Sending to Destination: " + ((HTTPDevice) destination));

			send(resultDoc, ((HTTPDevice) destination).getUrl());
		}
		catch (Exception ex)
		{
			logger.error("Failed sending the async response", ex);
		}
	}

	private void send(Document doc, String destinationUrl)
	{
		try
		{
			logger.debug("Making SOAP call to " + destinationUrl);

			SOAPMessage message = javax.xml.soap.MessageFactory.newInstance().createMessage();
			MimeHeaders headers = message.getMimeHeaders();
			headers.addHeader("SOAPAction", "");

			SOAPBody requestBody = message.getSOAPBody();
			requestBody.addDocument(doc);

			SOAPConnection connection = SOAPConnectionFactory.newInstance().createConnection();

			SOAPMessage reply = connection.call(message, destinationUrl);
			SOAPBody body = reply.getSOAPBody();

			if (body.getFault() != null)
			{
				SOAPFault fault = body.getFault();
				throw new SOAPException("Got a soap fault in the response: "
						+ fault.getFaultString());
			}

			connection.close();
		}
		catch (Exception ex)
		{
			logger.error("Could not send SOAP message.", ex);
		}
	}

	// ========================================================================
	// ====================== SERVICELIFECYCLE METHODS
	// ========================================================================

	/**
	 * Initialize the Drive Configuration.
	 * 
	 * @see javax.xml.rpc.server.ServiceLifecycle#init(java.lang.Object)
	 */
	public void init(Object context) throws ServiceException
	{
		logger.debug("Initializing the File Subsystem Service...");

		try
		{
			FileManager.getInstance();
			this.requestHandler = RequestHandler.getInstance();
			this.marshaller = Marshaller.newMarshaller();
			this.mhFactory = MessageHandlerFactory.getInstance();
		}
		catch (Exception ex)
		{
			String msg = "Failed to initialize the File Subsystem Service.";
			logger.error(msg, ex);
			throw new ServiceException(msg, ex);
		}

		logger.info("Successfully initialized the File Subsystem Service.");
	}

	/**
	 * @see javax.xml.rpc.server.ServiceLifecycle#destroy()
	 */
	public void destroy()
	{
		logger.info("Successfully shutdown the File Subsystem Service.");
	}

	// ========================================================================
	// ====================== UTILITY METHODS
	// ========================================================================

	/*
	 * Method to generate a default NACK response
	 */
	private WorkflowMessage generateNackMessage(WorkflowMessage message, Throwable ex)
	{
		IMessageUID uid = (message != null) ? message.getMessageUID() : new MessageUIDImpl(
				UNKNOWN_UID);
		IMessageKey key = (message != null) ? message.getKey() : null;
		WorkflowMessage response = new WorkflowMessage(uid, key, MessageType.nack);

		/* Set payload parameters */
		response.putValue(MessageParameters.ERROR_MESSAGE, ex.getMessage());

		StringWriter writer = new StringWriter();
		ex.printStackTrace(new PrintWriter(writer));
		response.putValue(MessageParameters.ERROR_STACK, writer.toString());

		return response;
	}

	/*
	 * generates a default ack response (mainly used now for asynchronous request informing that the
	 * request has been received and is being processed)
	 */
	private WorkflowMessage generateAckReceivedMessage(WorkflowMessage message)
	{
		IMessageUID uid = (message != null) ? message.getMessageUID() : new MessageUIDImpl(
				UNKNOWN_UID);
		IMessageKey key = (message != null) ? message.getKey() : null;
		WorkflowMessage response = new WorkflowMessage(uid, key, MessageType.ack);

		return response;
	}

	/*
	 * Method to write the exception to the SOAP Fault of the response.
	 */
	private void addSOAPFault(SOAPEnvelope response, Throwable ex)
	{
		try
		{
			SOAPBody body = response.getBody();
			SOAPFault fault = body.addFault();
			fault.setFaultString(ex.getMessage());
		}
		catch (SOAPException se)
		{
			logger.error("Failed to write the SOAP Fault in the response.", se);
		}
	}
}
