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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.Dsp;
import lu.tudor.santec.gecamed.esante.gui.data.Configuration;
import lu.tudor.santec.gecamed.esante.gui.dialogs.ConnectingDialog;
import lu.tudor.santec.gecamed.esante.gui.dialogs.LoginDialog;
import lu.tudor.santec.gecamed.esante.utils.ESanteUtils;
import lu.tudor.santec.gecamed.esante.utils.exceptions.HtmlErrorCodeException;
import lu.tudor.santec.gecamed.esante.utils.exceptions.SendingStoppedException;

import org.apache.log4j.Logger;
import org.jfree.util.Log;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: SoapSender.java,v $
 * <br>Revision 1.18  2014-02-06 14:31:12  ferring
 * <br>SendingStoppedException handling and logging changed
 * <br>
 * <br>Revision 1.17  2014-01-27 10:05:54  ferring
 * <br>query since prepared
 * <br>
 * <br>Revision 1.16  2014-01-22 14:39:04  ferring
 * <br>changed System.out to warning
 * <br>
 * <br>Revision 1.15  2014-01-17 08:47:57  ferring
 * <br>check, whether presence password is required
 * <br>
 * <br>Revision 1.14  2013-12-17 08:39:36  ferring
 * <br>Document not found exception caught and dialog shown
 * <br>
 * <br>Revision 1.13  2013-12-16 12:16:18  ferring
 * <br>socket timeout set
 * <br>
 * <br>Revision 1.12  2013-12-13 15:02:24  ferring
 * <br>Exception handling changed
 * <br>
 * <br>Revision 1.11  2013-12-13 13:17:24  donak
 * <br>Integratoin of LuxTrust authentication - so far it is disabled
 * <br>
 * <br>Revision 1.10  2013-12-13 12:31:43  ferring
 * <br>Exception handling changed
 * <br>
 * <br>Revision 1.9  2013-12-10 11:59:27  ferring
 * <br>webservice error handling improved
 * <br>
 * <br>Revision 1.8  2013-12-04 13:12:36  ferring
 * <br>Checking eSante server status
 * <br>
 * <br>Revision 1.7  2013-11-29 09:10:52  ferring
 * <br>first changes for eSanté test platform
 * <br>
 * <br>Revision 1.6  2013-11-27 07:57:49  ferring
 * <br>No Exception thrown, when connection is canceled by user
 * <br>
 * <br>Revision 1.5  2013-11-26 10:53:22  ferring
 * <br>socket exception avoided
 * <br>
 * <br>Revision 1.4  2013-11-19 17:34:21  ferring
 * <br>MIME response data extraction changed
 * <br>
 * <br>Revision 1.3  2013-11-18 15:37:00  ferring
 * <br>Restructured methods with SoapSender and DocumentWorker restructured.
 * <br>Checking the IPID, if it changes and refreshing the data.
 * <br>
 * <br>Revision 1.2  2013-11-12 15:37:53  ferring
 * <br>downloads can now be stopped
 * <br>
 * <br>Revision 1.1  2013-10-22 14:33:19  ferring
 * <br>Thread canceling prepared
 * <br>
 */

/**
 * TODO The inner secrets of this class describing you must my dear Padawan!!!
 * 
 * @author ferring, donak
 *
 */
public class SoapSender {
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */

	/** There is no status defined yet. This usually means the request has not yet been processed by the webservice */
	public static final int STATUS_UNDEFINED = -1;
	/** The webservice was able to successfully process the request */
	public static final int STATUS_SUCCESS = 1;
	/** The webservice indicated a warning when processing the request. The request was nevertheless normally successful fulfilled */
	public static final int STATUS_WARNING = 2;
	/** The webservice indicated an error when processing the request or an error occurred when processing the webservice response */
	public static final int STATUS_ERROR = 3;
	/** The default port that is used for TLS connections if no explicit port is specified in the webservice url */
	private static final int HTTPS_PORT = 443;

	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	// Before enabling this again, please move this code to or method, as it might throw an Exception if 
	// rights are not granted by OS or JVM. In that Case you will get an Exception in UploadToDSP Action:
	// java.lang.NoClassDefFoundError: Could not initialize class lu.tudor.santec.gecamed.esante.gui.webservice.SoapSender
	// will will cause GECAMEd to crash on start.	
//	private static SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();

	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(SoapSender.class.getName());

	private Dsp dsp;

	private Socket socket;

	private String hostname;

	private String path;

	/** the port to which the connection should be established */
	private int port;
	/** indicates if a secured connection should be used */
	private boolean useTLS = false;

	private StringBuilder request;

	private String responseMessage = null;
	private byte[] responseBytes;

	private String patientsPresencePassword;

	private ConnectingDialog dialog;

	private String logSuffix = null;

	private int status;

	private boolean stop = false;

	private boolean runInBackground;

	private Thread downloader;

	private Exception exception;

	private String errorCode;

	private String errorMessage;

	private String errorDetail;

	private boolean presencePasswordRequired = true;

	private Date querySince;

	/** This parameter is used for retrieving the metadata of a specific document from the registry */
	private String documentUniqueId;

	/** This is the cda-document that will be transferred to the dsp */
	private byte[] attachment = null;
	
	/** This is just needed for debug purposes. It is the unencoded payload of the CDA-document */
	private byte[] attachmentPayload = null;

	private String mediaType;

	private boolean sendAsMimeMultipart = false;

	/**
	 * Creates a new object that will hold all information necessary for communicating with
	 * 
	 * @param dsp
	 *            The electronic health record of a patient
	 * @param runInBackground
	 *            If this flag is set to true, the send process is run as a separate task in background
	 */
	public SoapSender(Dsp dsp, boolean runInBackground) {
		this.dsp = dsp;
		this.runInBackground = runInBackground;
	}

	/**
	 * Provides the attachment that is currently attached to this sender. It will either be set by the user, if it should be sent to a webservice or it will be
	 * set by the system, if it has been provided by a webservice.
	 * 
	 * @return The attachment or null, if none had been set.
	 */
	public byte[] getAttachment() {
		return attachment;
	}

	/**
	 * Sets the attachment
	 * 
	 * @param attachment
	 *            The attachment
	 */
	public void setAttachment(byte[] attachment) {
		this.attachment = attachment;
	}

	/**
	 * Sets the webservice response message
	 * 
	 * @param responseMessage
	 *            The response message without any http or mime headers
	 */
	private void setResponseMessage(String responseMessage) {
		this.responseMessage = responseMessage;
	}

	/**
	 * Provides the response message that has been sent by the webservice
	 * 
	 * @return The response message without any header or attachment. If none has been received, null is returned
	 * @throws SendingStoppedException
	 *             If user aborted the action
	 */
	public String getResponseMessage() throws SendingStoppedException {
		checkThreadStopped();
		if (this.responseMessage == null) {
			try {
				new Mime(responseBytes);
			} catch (RuntimeException e) {
				// retrieved response was no valid soap
				setException(e);
			}
		}
		// System.out.println("Response message is "+this.responseMessage);
		return this.responseMessage;
	}

	/**
	 * Sets the attachment that has been set for the next soap call
	 * 
	 * @param attachment
	 *            The attachment in utf8 format
	 */
	public void setAttachment(String attachment) {
		try {
			this.attachment = attachment.getBytes("UTF8");
		} catch (UnsupportedEncodingException e) {
			// Exception will never be thrown as utf8 is native java string format
			;
		}
	}

	/**
	 * Sets the unencoded payload of the cda-document that will be sent by the message to the dsp.<br/>
	 * <br/>
	 * <i>Just needed for debugging purposes</i>
	 * 
	 * @param attachmentPayload
	 *            The un-encoded payload of the cda-document (atm a pdf/a document)
	 */
	public void setAttachmentPayload(byte[] attachmentPayload) {

		this.attachmentPayload = attachmentPayload;
	}

	/**
	 * Provides the unencoded payload of the cda-document that will be sent by the message to the dsp.<br/>
	 * <br/>
	 * <i>Just needed for debugging purposes</i>
	 * 
	 * @return the unencoded payload of the cda-document
	 */
	public byte[] getAttachmentPayload() {

		return this.attachmentPayload;
	}

	
	/**
	 * Indicates if an attachment has been set for the next message that will be sent
	 * 
	 * @return True, if an attachment has been set, false otherwise
	 */
	public boolean hasAttachment() {
		return this.attachment != null;
	}

	/**
	 * Indicates if this message should be sent as mime multipart message (including usage of mtom)
	 * 
	 * @return true if message has to be sent as mime multipart, false otherwise
	 */
	public boolean sendAsMimeMultipart() {
		return sendAsMimeMultipart || hasAttachment();
	}

	/**
	 * Determines if message should be sent as mime multipart message. If an attachment for this message has been set, it will be automatically sent as mime
	 * multipart even if this flag has not been explicitly set.
	 * 
	 * @param sendAsMime
	 *            Flag that indicates if message should be sent as mime/multipart message
	 */
	public void setSendAsMimeMultipart(boolean sendAsMime) {
		this.sendAsMimeMultipart = sendAsMime;
	}

	/**
	 * Retrieve the electronic health record
	 * 
	 * @return The electronic health record or null if non-existent
	 */
	public Dsp getDsp() {
		return dsp;
	}

	/**
	 * Set the electronic health record
	 * 
	 * @param dsp
	 *            The electronic health record
	 */
	public void setDsp(Dsp dsp) {
		this.dsp = dsp;
	}

	/**
	 * Provides the media type of the attachment
	 * 
	 * @return Attachment media type
	 */
	public String getMediaType() {
		return mediaType;
	}

	/**
	 * Sets the media type of the attachment
	 * 
	 * @param mediaType
	 *            The media type
	 */
	public void setMediaType(String mediaType) {
		this.mediaType = mediaType;
	}

	/**
	 * Provides all request data that has been written so far for the upcoming webservice call.
	 * 
	 * @return The request data
	 * @throws SendingStoppedException
	 *             If user aborted the action
	 */
	public String getRequest() throws SendingStoppedException {
		checkThreadStopped();
		return request.toString();
	}

	/**
	 * Writes data to the soap request. This data is stored within the SoapSender object until the soap service is actually called.
	 * 
	 * @param append
	 *            The data that should be appended to the soap request
	 * @throws SendingStoppedException
	 *             If user aborted the action
	 */
	public void writeRequest(String append) throws SendingStoppedException {
		checkThreadStopped();
		this.request.append(append);
	}

	/**
	 * Stores the response of the webservice, splits it up if necessary (in case of multi-part mime messages), and analyses it for any errors that might have
	 * been returned from the webservice.
	 * 
	 * @param response
	 *            The response of the webservice
	 */
	public void setResponse(byte[] response) {
		this.responseBytes = response;
		if((response!=null)&&(response.length>0)){
			setSuccess(true);
		}
		// check the response and extract relevant information
		validateServiceResponse();
	}

	// public void writeResponseLine(String append) throws SendingStoppedException {
	// checkThreadStopped();
	// this.response.append(append).append("\n");
	// }

	/**
	 * Provides a reference to the login dialog
	 * 
	 * @return Reference to the login dialog
	 */
	public ConnectingDialog getDialog() {
		return dialog;
	}

	/**
	 * Provides the presence password, which might be added to a DSP authentication request for admitting temporary access to a personnel health record (DSP-12
	 * request)
	 * 
	 * @return The presence password in plain text
	 */
	public String getPresencePassword() {
		return this.patientsPresencePassword;
	}

	/**
	 * Sets the presence password, which might be necessary for a DSP authentication request for admitting temporary access to a personnel health record (DSP-12
	 * request)
	 * 
	 * @param presencePassword
	 *            The presence password in plain text
	 */
	public void setPresencePassword(String presencePassword) {
		this.patientsPresencePassword = presencePassword;
	}

	/**
	 * Indicates if the request could be successfully processed by the webservice
	 * 
	 * @return True, if the webservice was able to process the request (with or without warnings), false otherwise
	 */
	public boolean isSuccessful() {
		if (status == STATUS_UNDEFINED)
			throw new RuntimeException("SoapSender status UNDEFINED!");

		return status <= STATUS_WARNING;
	}

	/**
	 * Explicitely sets the status of the webservice request
	 * 
	 * @param success
	 *            True, if the webservice request should be set to {@link #STATUS_SUCCESS success}, false if it should be set to {@link #STATUS_ERROR error}
	 */
	public void setSuccess(boolean success) {
		this.status = success ? STATUS_SUCCESS : STATUS_ERROR;
	}

	/**
	 * Provides the status of the webservice request.
	 * 
	 * @return This function provides one of the following states:
	 *         <ul>
	 *         <li>{@link #STATUS_SUCCESS success}</li>
	 *         <li>{@link #STATUS_WARNING warning}</li>
	 *         <li>{@link #STATUS_ERROR error}</li>
	 *         <li>{@link #STATUS_UNDEFINED undefined}</li>
	 *         </ul>
	 */
	public int getStatus() {
		return status;
	}

	/**
	 * Indicates if the user has stopped the processing
	 * 
	 * @return True, if the user has stopped processing, false otherwise
	 */
	public boolean isStopped() {
		return stop;
	}

	/**
	 * checks if the user has stopped the processing
	 * 
	 * @throws SendingStoppedException
	 *             If the user has stopped the processing
	 */
	public void checkThreadStopped() throws SendingStoppedException {
		if (stop)
			throw new SendingStoppedException(true);
	}

	/**
	 * Sets a flag to stop the processing
	 */
	public void stop() {
		this.stop = true;
	}

	/**
	 * Stores the relevant elements of a webservice url
	 * 
	 * @param wsUrl
	 *            The url of the webservice. If it does not contain a port, the default port will be set depending on the protocol type
	 * @throws MalformedURLException
	 *             If the url is invalid
	 */
	public void setWebserviceUrl(String wsUrl) throws MalformedURLException {
		URL url = new URL(wsUrl);
		hostname = url.getHost(); // "www-test.infomed-vs.ch";
		path = url.getPath(); // "/dpp-xdsreg"
		// check if secured connection should be used
		useTLS = url.getProtocol().toLowerCase().startsWith("https");
		port = url.getPort();
		// if no port has explicitly been specified, use default port depending on the used protocol
		if (port == -1) {
			port = useTLS ? 443 : 80;
		}
	}

	/**
	 * Provides the name of the host at which the webservice will be requested
	 * 
	 * @return The host name
	 */
	public String getHostname() {
		return hostname;
	}

	/**
	 * Provides the path to the webservice that will be used for the requests
	 * 
	 * @return The path
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Provides the port of the webservice that will be used for the requests
	 * 
	 * @return The port
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Provides the suffix that will be added to the logfile name
	 * 
	 * @return The suffix or null if none has been set
	 */
	public String getLogSuffix() {
		return logSuffix;
	}

	/**
	 * Creates a buffered writer that allows to send data via the socket to the webservice (as utf8 string).
	 * 
	 * @return the buffered writer that can be used to send utf8 data
	 * @throws IOException
	 *             If socket or outputstream cannot be created
	 * @throws SendingStoppedException
	 *             If user interrupted the action
	 */
	public BufferedWriter createSocketWriter() throws IOException, SendingStoppedException {
		OutputStream out = null;

		checkThreadStopped();
		if (this.socket == null) {
			createSocket();
		}
		out = this.socket.getOutputStream();

		return new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
	}

	/**
	 * Creates a DataOutputStream that allows to send binary data via the socket to the webservice.
	 * 
	 * @return the dataoutputstream that can be used to send binary data
	 * @throws IOException
	 *             If socket or outputstream cannot be created
	 * @throws SendingStoppedException
	 *             If user interrupted the action
	 */
	public DataOutputStream createDataOutputStream() throws IOException, SendingStoppedException {
		OutputStream out = null;

		checkThreadStopped();
		if (this.socket == null) {
			createSocket();
		}

		out = this.socket.getOutputStream();

		return new DataOutputStream(out);
	}

	/**
	 * Creates a buffered reader that allows to read the response from the webservice (as utf8 string)
	 * 
	 * @return The buffered reader
	 * @throws IOException
	 *             If socket or outputstream cannot be created
	 * @throws SendingStoppedException
	 *             If user interrupted the action
	 */
	public BufferedReader createSocketReader() throws IOException, SendingStoppedException {
		InputStream in = null;

		checkThreadStopped();
		if (this.socket == null) {
			createSocket();
		}
		in = this.socket.getInputStream();

		return new BufferedReader(new InputStreamReader(in, "UTF-8"));
	}

	/**
	 * Creates a DataInputStream that allows to read binary data coming from the webservice.
	 * 
	 * @return the datainputstream that can be used to receive binary data
	 * @throws IOException
	 *             If socket or outputstream cannot be created
	 * @throws SendingStoppedException
	 *             If user interrupted the action
	 */
	public DataInputStream createDataInputStream() throws IOException, SendingStoppedException {
		InputStream in = null;

		checkThreadStopped();
		if (this.socket == null) {
			createSocket();
		}

		in = this.socket.getInputStream();

		return new DataInputStream(in);
	}

	public boolean runInBackground() {
		return runInBackground;
	}

	/**
	 * Sets an exception that has been retrieved from the webservice or during processing the webservice response.<br/>
	 * When an exception is set, the status is automatically changed to {@link #STATUS_ERROR error}
	 * 
	 * @param e
	 *            The Exception that should be set
	 */
	public void setException(Exception e) {
		this.status = STATUS_ERROR;
		this.exception = e;
	}

	public Exception getException() {
		return this.exception;
	}

	/**
	 * Indicates if an Exception was risen when receiving the response from the webservice.<br/>
	 * <br/>
	 * In case of an exception, it makes sense to check the error details with <ul>
	 * <li>{@link #getErrorCode()}</li>, 
	 * <li>{@link #getErrorMessage()}</li>, and 
	 * <li>{@link #getErrorDetail()}</li></ul>
	 * If the webservice returned an error, it always causes the rise of an exception.
	 * 
	 * @return True, if an exception was risen, false otherwise
	 */
	public boolean isException() {
		return getException() != null;
	}

	/**
	 * Provides the error code of the exception
	 * 
	 * @return The error code
	 */
	public String getErrorCode() {
		return this.errorCode;
	}

	/**
	 * Provides a more detailed description about the exception
	 * 
	 * @return Detailed description
	 */
	public String getErrorDetail() {
		return this.errorDetail;
	}

	/**
	 * Provides the error message of the exception
	 * 
	 * @return Error message
	 */
	public String getErrorMessage() {
		return this.errorMessage;
	}

	/**
	 * This function indicates if a presence password is required for accessing a particular health record.<br/>
	 * <br/>
	 * When the user wants to gain access to a patient health record, he might not possess the appropriate access permissions. In this case, the call for
	 * authenticating the access to the health record has to contain a presence password. Providing such a password temporarily changes the mandate of the user,
	 * which temporarily grants access to the desired health record (currently for 10 days).<br/>
	 * <br/>
	 * <b>Before this function is called</b>, it is necessary to make a DSP-22 for checking the access permissions of a health record for the current user. The
	 * subsequent call of DSP-11 (access health record), respectively DSP-12 (access health record with presence password) has to be made to the same health
	 * record for which the access permissions have been checked.
	 * 
	 * @return True, if a presence password is required, false otherwise
	 */
	public boolean isPresencePasswordRequired() {
		return presencePasswordRequired;
	}

	/**
	 * Indicates if a presence password is required for accessing a particular patient health record. This information is usually obtained by making a DSP-22
	 * call for the desired health record.<br/>
	 * <br/>
	 * <i>Please also check {@link #isPresencePasswordRequired()} for further details about this topic.</i>
	 * 
	 * @param required
	 *            True to indicate that a presence password is required and thus a DSP-12 instead a DSP-11 call has to be used for gaining access to a
	 *            particular health record.
	 */
	public void setPresencePasswordRequired(boolean required) {
		this.presencePasswordRequired = required;
	}

	/**
	 * Provides the creation date of the oldest documents that should be included in the list, when requesting a document list for a particular health record.<br/>
	 * <br/>
	 * Please also see {@link #setQuerySince(Date)} for further details about this topic
	 * 
	 * @return The creation date from which on a list of documents should be requested from the webservice
	 */
	public Date getQuerySince() {
		return querySince;
	}

	/**
	 * Sets the creation date of the oldest documents that should be included in the list, when requesting a document list for a particular health record.<br/>
	 * <br/>
	 * A list of all documents for this health record since this date will be returned by the webservice when making a retrieve document list (ITI-18) call.<br/>
	 * <br/>
	 * <i><b> However, be aware that this list <u>might not</u> contain all documents of this period that are available due to limited access permissions to
	 * this health record. (e.g. owner applied restrictions)</b></i>
	 * 
	 * @param querySince
	 *            The creation date from which on a list of documents should be requested from the webservice
	 */
	public void setQuerySince(Date querySince) {
		this.querySince = querySince;
	}

	/**
	 * Provides the unique id of a specific document. This is used for obtaining the metadata of the specified document.<br/>
	 * Please also see {@link #setDocumentUniqueId(String)} for further details about this topic
	 * 
	 * @return The unique document id
	 */
	public String getDocumentUniqueId() {
		return documentUniqueId;
	}

	/**
	 * Sets the unique id of a specific document to the sender. If this attribute is set, only the metadata of the specified document is retrieved when an RSQ
	 * is executed.<br/>
	 * This is used for retrieving information that are preconditions for changing the attributes of an already uploaded document.
	 * 
	 * @param documentUniqueId
	 *            The unique id of the document of which the metadata should be retrieved
	 */
	public void setDocumentUniqueId(String documentUniqueId) {
		this.documentUniqueId = documentUniqueId;
	}

	/**
	 * Creates a socket for the communication with the webservice. Depending on the type of the url, either a http socket or a TLS socket is created
	 * 
	 * @throws IOException
	 *             If it was not possible to create a socket. (e.g. server does not accept connections via the given url)
	 * @throws SendingStoppedException
	 *             If the user aborted the action
	 */
	public void createSocket() throws IOException, SendingStoppedException {
		checkThreadStopped();
		// The subsequent code disables security mechanisms of TLS. Use wisely...
		// HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
		// public boolean verify(String hostname, SSLSession session) {
		// return true;
		// }
		// });

		closeSocket();
		try {
			Configuration config;

			config = LoginDialog.getConfiguration();

			// open the connection
			InetAddress addr = InetAddress.getByName(hostname);

			// if url was https
			if (useTLS) {
				this.socket = config.getTlsSocketFactory().createSocket(addr, this.port);
				logger.info("Established TLS connection to \"https://" + hostname + ":" + port + path + "\",");
			} else {
				this.socket = config.getTcpSocketFactory().createSocket(addr, this.port);
				logger.info("Established HTTP connection to \"http://" + hostname + ":" + port + "/" + path + "\",");
			}
		} catch (ConnectException conex) {
			logger.error("Connection refused by service \"" + hostname + ":" + port + "/" + path + "\" for message of type " + this.getLogSuffix());
			throw conex;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// To be decided how this case will be treated....
			e.printStackTrace();
		}
		if (this.socket != null) {
			socket.setSoTimeout(1000 * 15);
		}

	}

	/**
	 * Closes the currently open socket
	 */
	public void closeSocket() {
		if ((this.socket != null) && !this.socket.isClosed()) {
			try {
				this.socket.close();
			} catch (IOException e) {
				Log.warn("Unable to close socket.");
			}
		}
		this.socket = null;
	}

	/**
	 * Re-initializes all relevant parameters with its default values
	 * 
	 * @param logSuffix
	 *            The suffix that will be added to the message name when it is logged.<br/>
	 * <br/>
	 *            <i>(e.g. "RST" might result into a file name like "20150424_115038_227_REQUEST_<b>RST</b>.xml")</i>
	 * @throws SendingStoppedException
	 *             When sending has been aborted by the user
	 */
	public synchronized void resetSender(String logSuffix) throws SendingStoppedException {
		checkThreadStopped();
		this.logSuffix = logSuffix;
		dialog = ConnectingDialog.getInstance();
		request = new StringBuilder();
		responseMessage = null;
		socket = null;
		errorCode = null;
		errorDetail = null;
		errorMessage = null;
		attachment = null;
		attachmentPayload = null;
		status = STATUS_UNDEFINED;
		sendAsMimeMultipart = false;
		closeSocket();
	}

	/**
	 * Starts the sending process. Depending on the runInBackground flag, this action will be performed in a separate thread or block till completed
	 * 
	 * @return Reference to the thread
	 * @throws SendingStoppedException
	 *             If user aborted the action
	 */
	public Thread start() throws SendingStoppedException {
		downloader = new SOAPUtilities(this);

		if (runInBackground)
			downloader.run();
		else
			downloader.start();

		checkThreadStopped();

		return downloader;
	}

	/**
	 * Clears all parameters of the sender object
	 */
	public void clear() {
		status = STATUS_UNDEFINED;
		stop = false;
		socket = null;
		hostname = null;
		path = null;
		request = null;
		responseMessage = null;
		dialog = null;
		logSuffix = null;
		downloader = null;
		exception = null;
		errorCode = null;
		errorMessage = null;
		errorDetail = null;
		setPresencePasswordRequired(true);
		querySince = null;
		closeSocket();
	}

	/**
	 * Analyses the response of the webservice.<br/>
	 * <br/>
	 * Error information can be obtained by calling {@link #getErrorCode()}, {@link #getErrorDetail()}, and {@link #getErrorMessage()}.
	 * 
	 * @return True, if the service call was successful, false otherwise
	 */
	public boolean validateServiceResponse() {
		String xmlResponse;
		errorCode = null;
		errorMessage = null;
		errorDetail = null;

		if (status == STATUS_UNDEFINED || !isSuccessful() || isException()) {
			// This part might be unnecessary - to be checked
			return false;
		}

		if (this.responseBytes == null || responseBytes.toString().trim().length() == 0) {
			// there was no response at all
			return false;
		}

		if (responseBytes.toString().contains("<html")) {
			// is it an HTML error
			setException(new HtmlErrorCodeException(responseBytes.toString()));
			return false;
		}

		try {
			checkThreadStopped();

			// get the XML response to analyse it
			xmlResponse = getResponseMessage();

			if (xmlResponse == null) {
				return false;
			}
		} catch (SendingStoppedException e) {
			// User stopped processing
			setException(e);
			return false;
		} catch (RuntimeException e2){
			// User stopped processing
			setException(e2);
			return false;	
		}
		// analyse the content of the webservice response for any error messages
		readErrors(xmlResponse);

		if (errorCode.toLowerCase().contains("success")) {
			status = STATUS_SUCCESS;
			return true;
		} else if (errorCode.equals(WebserviceException.ERROR_CODE_WARNING)) {
			status = STATUS_WARNING;
			// if webservice returns just a warning, the call was nevertheless successful
			return true;
		} else {
			status = STATUS_ERROR;
			setException(new WebserviceException(this));
			return false;
		}
	}

	/**
	 * Extracts the error details, if any, from the response message coming from the server and stores them in the SoapSender object.<br/>
	 * <br/>
	 * Error information can be obtained by calling {@link #getErrorCode()}, {@link #getErrorDetail()}, and {@link #getErrorMessage()}.
	 * 
	 * @param xmlResponse
	 *            The response of the service.
	 */
	private void readErrors(String xmlResponse) {
		try {
			// RSQ success status
			errorCode = (String) ESanteUtils.parseXPath("//*[local-name()='AdhocQueryResponse']/@status|" + "//*[local-name()='RegistryResponse']/@status",
					xmlResponse, true);
			errorMessage = (String) ESanteUtils.parseXPath("//*[local-name()='RegistryError']/@errorCode", xmlResponse, true);
			errorDetail = (String) ESanteUtils.parseXPath("//*[local-name()='RegistryError']/@codeContext", xmlResponse, true);
			if (!GECAMedUtils.isEmpty(errorCode))
				return;

			errorCode = (String) ESanteUtils.parseXPath("//*[local-name()='StatusCode']/@Value", xmlResponse, true);
			errorMessage = (String) ESanteUtils.parseXPath("//*[local-name()='StatusMessage']/text()", xmlResponse, true);
			errorDetail = (String) ESanteUtils.parseXPath("//*[local-name()='StatusDetail']/*[local-name()='anyType']/text()", xmlResponse, true);
			if (!GECAMedUtils.isEmpty(errorCode))
				return;

			errorCode = (String) ESanteUtils.parseXPath("//*[local-name()='status']/*[local-name()='code']/text()", xmlResponse, true);
			errorMessage = (String) ESanteUtils.parseXPath("//*[local-name()='status']/*[local-name()='message']/text()", xmlResponse, true);
			errorDetail = (String) ESanteUtils.parseXPath("//*[local-name()='status']/*[local-name()='detail']/text()", xmlResponse, true);
			if (!GECAMedUtils.isEmpty(errorCode))
				return;

			errorCode = (String) ESanteUtils.parseXPath("/Envelope/Body/Fault/faultcode", xmlResponse, true);
			errorMessage = (String) ESanteUtils.parseXPath("/Envelope/Body/Fault/faultstring", xmlResponse, true);
			if (!GECAMedUtils.isEmpty(errorCode))
				return;

			if (GECAMedUtils.isEmpty(errorCode)) {
				// the errorCode is not set, mark it as unknown error
				errorCode = "UNKNOWN";
				errorDetail = xmlResponse;
				logger.warn("CAUGHT THIS UNKOWN ERROR:\n" + xmlResponse);
				return;
			}
		} catch (Exception e) {
			// the errorCode is not set, mark it as unknown error
			errorCode = "UNKNOWN";
			errorMessage = e.getMessage();
			errorDetail = xmlResponse;
			logger.warn("CAUGHT THIS UNKOWN ERROR:\n" + xmlResponse);
		}
	}

	/* ======================================== */
	// CLASS: Mime
	/* ======================================== */

	/**
	 * The sole reason for this class is to extract relevant data from a mime message. Those are: The message header, the message content and, if present, the
	 * message attachment as well as the media type of this attachment. All information is automatically stored in the corresponding SoapSender object.
	 * 
	 * Actually, imho, there is no real reason to put this functionality in an inner class. This is legacy code that has been pimped and corrected. Should
	 * either be moved to an external class or integrated into SoapSender at a later point of time.
	 * 
	 * @author ferring, donak
	 *
	 */
	private class Mime {

		/** Pattern that identifies the empty line between header and content */
		private final Pattern emptyLinePattern = Pattern.compile("(\r?\n){2}", Pattern.MULTILINE & Pattern.DOTALL);
		/** Pattern that identifies the content type element */
		private final Pattern contentTypePattern = Pattern.compile("(?<=content-type: )([^\\r\\n;]*)", Pattern.CASE_INSENSITIVE);
		/** Pattern that identifies the multipart message boundary */
		private final Pattern boundaryDefPattern = Pattern.compile("(?<=boundary=\"?)[^;\\s\"]+", Pattern.MULTILINE);

		/**
		 * Processes a multipart mime message and
		 * 
		 * @param mimeMessage
		 *            The multipart mime message that has been received as response from the webservice
		 * @throws SendingStoppedException
		 *             If user stopped the processing
		 */
		public Mime(byte[] mimeMessage) throws SendingStoppedException, RuntimeException {
			read(mimeMessage);
		}

		/**
		 * Extracts the header, message body and if existing (mime-multipart) also the attachment of a message. Results will be written to the corresponding
		 * class members and accessed via SoapSender.class
		 * 
		 * @param mimeMessage
		 *            The raw mime message (with headers and everything)
		 * @throws SendingStoppedException
		 *             When the thread was requested to stop
		 */
		private void read(byte[] mimeMessage) throws SendingStoppedException, RuntimeException {

			String header = null;
			String message = null;
			String boundary = null;
			Matcher contentTypeMatcher;
			Matcher boundaryMatcher;
			String contentTypeLine;
			boolean isMultipart;

			// check if thread was asked to stop
			checkThreadStopped();

			if ((mimeMessage == null) || (mimeMessage.length == 0)) {
				throw new RuntimeException("Received an empty response from the server");
			}
			// transform the response to a string for being able to apply string operations
			String mimeMessageString = null;
			try {
				mimeMessageString = new String(mimeMessage, "UTF8");
			} catch (UnsupportedEncodingException e) {
				// will never occur
			}
			// find the end of the general header
			Matcher headMatcher = emptyLinePattern.matcher(mimeMessageString);

			if (!headMatcher.find()) {
				throw new RuntimeException("MIME header couldn't be recognized.\nMIME Message:\n\"" + mimeMessage + "\"");
			}
			// and extract the general header
			header = mimeMessageString.substring(0, headMatcher.start());
			// the remaining part is the multipart mime message
			message = mimeMessageString.substring(headMatcher.end());

			// this matcher identifies content type attribute values
			contentTypeMatcher = contentTypePattern.matcher(header);
			if (!contentTypeMatcher.find()) {
				throw new RuntimeException("No Content-Type found in MIME header.\nMIME Message:\n" + mimeMessage);
			}

			// find the content type in the general header
			contentTypeLine = contentTypeMatcher.group();
			if (contentTypeLine.equalsIgnoreCase("multipart/related")) {
				// multipart-related means this is a message composed of several elements (usually 1 message and 1 or more attachments)
				isMultipart = true;
				// so far not used
				String messageMimeType = null;

				if (contentTypeMatcher.find()) {
					// the next fit is the content type of the message
					messageMimeType = contentTypeMatcher.group();
					if (contentTypeMatcher.find()) {
						// the next fit is the content type of the attachment. If there is no fit, it means the attachment is missing
						SoapSender.this.setMediaType(contentTypeMatcher.group());
					}
				}

				// matcher that identifies the tag that indicates the location of each message element
				boundaryMatcher = boundaryDefPattern.matcher(header);
				if (!boundaryMatcher.find()) {
					throw new RuntimeException("No multipart boundary definition found in Content-Type definition.\nMIME Message:\n" + mimeMessage);
				}

				// remember the element identifier
				boundary = boundaryMatcher.group();

				Matcher xmlMatcher;
				int xmlStart;
				int xmlEnd;

				// the boundary normally must not occur anywhere in the content but anyway, just to be sure...
				boundary = "\n--" + boundary;
				int messageSize = mimeMessageString.length();
				int boundaryLength = boundary.length();

				// fetch the first mime boundary position
				int boundaryPos = mimeMessageString.indexOf(boundary);
				// if the boundary was not found or the end tag, indicate it to the user
				if ((boundaryPos == -1) || isEndTag(mimeMessageString, boundaryPos, boundaryLength)) {
					logger.error("RAW MIME MESSAGE:\n\n|"+mimeMessageString+"|");
					throw new RuntimeException("Couldn't find MIME boundary for begin of message:\n"+mimeMessageString);
				}

				xmlStart = boundaryPos + boundaryLength;
				boundaryPos = mimeMessageString.indexOf(boundary, boundaryPos + boundaryLength);
				if (boundaryPos == -1) {
					// no further boundary has been found, so the remaining part of the message will be used and no (external) attachment is existent
					logger.warn("Couldn't  find MIME boundary for end of message");
					xmlEnd = messageSize;
					isMultipart = false;
				} else {
					// there is only an (external) attachment if the second boundary tag was not the end tag
					isMultipart = !isEndTag(mimeMessageString, boundaryPos, boundaryLength);
					xmlEnd = boundaryPos;
				}

				// now the actual message has been extracted - without attachment
				message = mimeMessageString.substring(xmlStart, xmlEnd);

				// identify where the actual attachment data starts
				xmlMatcher = emptyLinePattern.matcher(message);
				if (!xmlMatcher.find()) {
					throw new RuntimeException("Couldn't find MIME spacing for envelope");
				}
				// finally strip the header from the message element
				message = message.substring(xmlMatcher.end());

				// continue processing if message contains more mime parts
				if (isMultipart) {
					// the begin of the (1st) external attachment part is after the boundary that indicated the end of the message
					xmlStart = boundaryPos + boundaryLength;
					// identifies the separation between attachment header and data. This should not(!) but might be os dependent.
					byte[] lineBreak = (mimeMessageString.charAt(xmlStart) == '\r') ? new byte[] { '\r', '\n', '\r', '\n' } : new byte[] { '\n', '\n' };
					// do a binary search for this empty line
					boundaryPos = SOAPUtilities.indexOf(responseBytes, boundary.getBytes(), boundaryPos - 5);
					// find the attachment start position in the BINARY(!!) representation of the webservice response. This is crucial, as it is sent in mtom
					// format!
					int attachmentStart = SOAPUtilities.indexOf(responseBytes, lineBreak, boundaryPos) + lineBreak.length;
					// well, the same is valid for the end position (there might be several attachments but all but the first one are currently ignored)
					int attachmentEnd = SOAPUtilities.indexOf(responseBytes, (boundary + "--").getBytes(), attachmentStart);
					if (attachmentEnd == -1) {
						attachmentEnd = responseBytes.length;
					}
					// and extract the attachment from the BINARY(!!) representation of the webservice response. This is crucial, as it is sent in mtom format!
					// set the binary content as attachment. The mime type will decide about how it is treated (as string or binary)
					SoapSender.this.setAttachment(Arrays.copyOfRange(responseBytes, attachmentStart, attachmentEnd));
				}
			} else {
				SoapSender.this.setAttachment((byte[]) null);
				// logger.warn("No attachment in multipart MIME message found!");
			}
			
			// and finally store the response message in the sender object
			SoapSender.this.setResponseMessage(message);
		}
	}

	/**
	 * Checks if the the mime boundary tag is the end tag of the mime message
	 * 
	 * @param message
	 *            the mime message
	 * @param boundaryPos
	 *            the position at which the boundary has been found
	 * @param boundaryLength
	 *            the length of the boundary
	 * @return True if the boundary was the end tag, false otherwise
	 */
	private boolean isEndTag(String message, int boundaryPos, int boundaryLength) {
		if (message.length() >= (boundaryPos + boundaryLength + 2)) {
			if ((message.charAt(boundaryPos + boundaryLength + 1) == '-') && (message.charAt(boundaryPos + boundaryLength + 1) == '-')) {
				return true;
			}
		}
		return false;
	}
}
