package lu.tudor.santec.gecamed.esante.gui.webservice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.Collections;
import java.util.Set;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.util.ByteArrayDataSource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.soap.SOAPBinding;

import lu.tudor.santec.gecamed.esante.utils.Utility;

import org.apache.commons.io.IOUtils;
import org.w3c.dom.Node;

import com.sqli.sante.xdsb.ws.generated.ebxml.v3.rs.RegistryResponseType;
import com.sqli.sante.xdsb.ws.generated.ihe.iti.DocumentRepositoryPortType;
import com.sqli.sante.xdsb.ws.generated.ihe.iti.DocumentRepositoryService;
import com.sqli.sante.xdsb.ws.generated.ihe.iti.ProvideAndRegisterDocumentSetRequest;
import com.sqli.sante.xdsb.ws.generated.ihe.iti.ProvideAndRegisterDocumentSetRequest.Document;

/**
 * 
 * @author donak
 * 
 * @version <br>
 *          $Log: UploadDoc.java,v $
 *          Revision 1.8  2014-02-12 12:16:50  ferring
 *          log upload request as the other WSDL requests
 *
 *          Revision 1.7  2014-01-30 11:58:26  ferring
 *          replace all occurrences instead of only the first
 *
 *          Revision 1.6  2013-11-28 10:38:24  ferring
 *          GECAMedUtils split into utils and GUI utils classes
 *
 *          Revision 1.5  2013-11-19 17:36:00  ferring
 *          Exception changed
 *
 *          Revision 1.4  2013-10-07 16:12:12  donak
 *          Logging, message backup, and error protocol creation
 *
 * 
 */
public class UploadDoc implements SOAPHandler<SOAPMessageContext> {

	private Object message = null;

	// defines the web service security namespace
	private final String WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
	// stores the saml token
	private String samlAssertion = null;

	/*
	 * standard constructor
	 */
	public UploadDoc() {
		
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.xml.ws.handler.Handler#close(javax.xml.ws.handler.MessageContext)
	 */
	public void close(MessageContext arg0) {
	}
	
	/**
	 * provides the content of the eSanté upload message at its current state of creation 
	 * @return
	 * @throws Exception
	 */
	public String getMessage() throws Exception {
		return Utility.getObjectContent(this.message);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.xml.ws.handler.Handler#handleFault(javax.xml.ws.handler.MessageContext
	 * )
	 */
	public boolean handleFault(SOAPMessageContext smc) {
		return true;
	}

	/**
	 * Uploads a file to the DSP of a patient
	 * 
	 * @param request
	 *            The upload request meta data
	 *            (ITI-41-ProvideAndRegisterDocument)
	 * @param attachment
	 *            The cda document containing the actual document (in base64
	 *            encoded CDA format)
	 * @param samlToken
	 *            A valid SAML token for the patient
	 * @param url
	 *            The URL of the webservice
	 * @return A soap message containing the status of the request
	 * @throws Exception
	 *             Multiple reasons e.g. missing SAML token. (See Exception
	 *             message for further details)
	 */
	public RegistryResponseType sendToDsp(String request, String attachment, String samlToken, String url) throws Exception {
		// store the saml token
		if ((this.samlAssertion = samlToken) == null) {
			throw new Exception("SAML token is missing");
		}
		
		// log the request on the client
		SOAPUtilities.log(request, "REQUEST_RegDoc");
		SOAPUtilities.log(attachment, "REQUEST_RegDoc_CDA");
		
		// create context
		JAXBContext jaxbContext = JAXBContext.newInstance(ProvideAndRegisterDocumentSetRequest.class);
		// create unmarshaller for parsing xml into java class representation
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

		// Debug code for checking raw meta data of request
		// debugSaveContent(request, "trying to marshal:\n", "d:\\Temp", "RequestMetadata.xml");

		// unmarshall the request cda into the generated java representation
		ProvideAndRegisterDocumentSetRequest requestMessage = (ProvideAndRegisterDocumentSetRequest) unmarshaller.unmarshal(new StringReader(request));

		// if unmarshalling went right
		if ((requestMessage.getDocuments() != null) && (requestMessage.getDocuments().size() > 0)) {
			// obtain the java representation (be aware that this is not a
			// standard DOM document)
			Document document = requestMessage.getDocuments().get(0);

			// Create a datasource from the attachment by providing its mime
			// type to assure the right byte interpretation is used
			DataSource attachmentDs = new ByteArrayDataSource(attachment, "text/plain; charset=UTF-8");
			// finally create a uniform datahandler and add it to the document
			DataHandler attachmentDh = new DataHandler(attachmentDs);
			document.setValue(attachmentDh);

			// Debug check if attachment is here already b64 encoded - it
			// isn't...
			// debugSaveContent(IOUtils.toString(document.getValue().getInputStream()),
			// "Writing attachment (from file) out of document to ", "d:\\temp",
			// "AttachmentFromDocument.xml");
		}

		// define the repository service
		DocumentRepositoryService service = new DocumentRepositoryService(new URL(url));
		DocumentRepositoryPortType documentRepositoryPortSoap12 = service.getDocumentRepositoryPortSoap12();

		// Create provider for protocol bindings access
		BindingProvider provider = (BindingProvider) documentRepositoryPortSoap12;
		SOAPBinding sb = (SOAPBinding) provider.getBinding();

		sb.setHandlerChain(Collections.singletonList((Handler) this));
		// enable the message transfer optimization mechanism
		sb.setMTOMEnabled(true);

		// Send the request to the webservice and get the webservice response
		RegistryResponseType documentRepositoryProvideAndRegisterDocumentSetB = documentRepositoryPortSoap12
				.documentRepositoryProvideAndRegisterDocumentSetB(requestMessage);
		
		// save a copy of the soap message to the GECAMed server
		return documentRepositoryProvideAndRegisterDocumentSetB;
	}

	/**
	 * Add SOAP header and SAML assertion to message
	 * 
	 * @param smc
	 *            The soap message context
	 */
	public boolean handleMessage(SOAPMessageContext soapMessageContext) {

		// We only care of outbound messages
		Boolean outboundProperty = (Boolean) soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		if (!outboundProperty.booleanValue()) {
			return true;
		}

		try {
			// get the message
			SOAPMessage message = soapMessageContext.getMessage();
			// and now the soap part of the message
			SOAPPart soapPart = message.getSOAPPart();
			// and finally the envelope part
			SOAPEnvelope soapEnvelope = soapPart.getEnvelope();

			// Debug code for checking soap message that is sent to service
			// debugSaveContent(message,
			// "SOAP message content before saml has been saved to ",
			// "d:\\Temp", "SoapMessagebeforesaml.xml");

			// create a new web service security header element
			Name wsseHeaderName = soapEnvelope.createName("Security", "wsse", WSSE_NS);
			// add a header part if necessary
			if (soapEnvelope.getHeader() == null) {
				soapEnvelope.addHeader();
			}

			// and add the ws security element to the header
			SOAPHeaderElement securityElement = soapEnvelope.getHeader().addHeaderElement(wsseHeaderName);

			// has to be done here temporarily as otherwise the missing name
			// spaces prevent dom documentbuilder from parsing
			this.samlAssertion = this.samlAssertion.replace("<saml2:Assertion",
					"<saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"");
			ByteArrayInputStream SBIS = new ByteArrayInputStream(this.samlAssertion.getBytes("UTF-8"));

			org.w3c.dom.Document doc = buildDoc(SBIS);

			// create a new node
			Node cloneNode = doc.getFirstChild().cloneNode(true);

			// and place it at the right place (as a child of the security
			// header element)
			cloneNode = securityElement.getOwnerDocument().importNode(cloneNode, true);
			securityElement.appendChild(cloneNode);

			// store the generated soap message for logging
			this.message = message;
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return true;
	}

	/**
	 * Parse the saml token into a dom document
	 * 
	 * @param documentfile
	 *            The saml token as string
	 * @return the dom document containing the saml token
	 */
	public org.w3c.dom.Document buildDoc(InputStream documentfile) throws ParserConfigurationException, org.xml.sax.SAXException, IOException {

		org.w3c.dom.Document document = null;

		// documentfile = ByteArrayInputStream SBIS = new
		// ByteArrayInputStream(UploadDoc.assertionSAML.getBytes("UTF-8"));
		// uses the JAXP API to build a DOM Document. This parses the
		// SubmitObjectsRequest
		// containing registry metadata for addition to the soap body

		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		try {
			dbFactory.setNamespaceAware(true);
			DocumentBuilder builder = dbFactory.newDocumentBuilder();
			document = builder.parse(documentfile);

			// Debug code for checking raw meta data of request
			// debugSaveContent(document,
			// "SAML document content has been saved to", "d:\\Temp",
			// "ParsedSamlToken.xml");

		} catch (ParserConfigurationException pce) {
			this.message = IOUtils.toString(documentfile);
			throw pce;
		} catch (org.xml.sax.SAXException se) {
			this.message = IOUtils.toString(documentfile);
			throw se;
		} catch (IOException ex) {
			this.message = IOUtils.toString(documentfile);
			throw ex;
		}

		return document;
	}

	public Set<QName> getHeaders() {
		// TODO Auto-generated method stub
		return null;
	}

}
