package lu.tudor.santec.gecamed.esante.ejb.session.beans;

import java.io.File;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import lu.tudor.santec.gecamed.core.ejb.session.beans.GECAMedSessionBean;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.core.utils.ServerConfig;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.CdaDocument;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.Dsp;
import lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager;
import lu.tudor.santec.gecamed.esante.utils.exceptions.DspLinkingException;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntry;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;

import org.apache.log4j.Logger;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version <br>
 *          $Log: CDAManagerBean.java,v $
 *          Revision 1.32  2014-02-18 13:17:33  ferring
 *          Patients with Luxembourg ID, but without DSP, are now connected to their DSP or Luxembourg ID is removed, if no DSP was found.
 *          If only one DSP was found in search, the compare DSP and patient dialog is shown directly.
 *
 *          Revision 1.31  2014-02-06 17:48:40  ferring
 *          flag added, to decide if the document shall be stored on the server or not
 *
 *          Revision 1.30  2014-02-04 10:08:36  ferring
 *          eSante ID management completed
 *          Only those documents will be shown, that are retrieved by the RSQ
 *
 *          Revision 1.29  2014-02-03 10:26:40  ferring
 *          Don't look for the best matching, simply return any
 *
 *          Revision 1.28  2014-01-21 14:32:05  ferring
 *          myDSP configuration can be changed and reloaded.
 *          The clean CDA files on server function was added
 *
 *          Revision 1.27  2014-01-09 14:31:18  ferring
 *          clear CDA file methods provided
 *
 *          Revision 1.26  2013-12-17 16:49:51  ferring
 *          RSQ handling changed
 *
 *          Revision 1.25  2013-12-06 16:25:30  ferring
 *          CDA synch fixed
 *
 *          Revision 1.24  2013-12-05 16:38:52  ferring
 *          Throwables catching changed to get more output about errors
 *
 *          Revision 1.23  2013-12-05 12:27:19  ferring
 *          CDA  columns renamed
 *          Login data of user stored
 *          Checking if CDA doc exists, creating a new one
 *
 *          Revision 1.22  2013-11-26 13:36:04  ferring
 *          column history entries removed
 *
 *          Revision 1.21  2013-11-22 14:44:16  ferring
 *          method added to delete CDA file
 *
 *          Revision 1.20  2013-11-21 09:45:48  ferring
 *          hand over DSP to CDA documents
 *
 *          Revision 1.19  2013-11-18 15:37:04  ferring
 *          Restructured methods with SoapSender and DocumentWorker restructured.
 *          Checking the IPID, if it changes and refreshing the data.
 *
 *          Revision 1.18  2013-11-15 08:46:42  ferring
 *          deleting empty incidents in delete statement instead of getting them via select and removing them one by one
 *
 *          Revision 1.17  2013-11-14 17:56:18  ferring
 *          Deleting all incident entries with type 'cda' instead of all with entry id in cda table (for a patient)
 *
 *          Revision 1.16  2013-11-08 08:44:34  ferring
 *          loading documents when fetching linked DSP
 *
 *          Revision 1.15  2013-11-05 09:47:56  ferring
 *          ESantePatient changed to Dsp. The Dsp object will be stored in the database, if a patient is linked with eSanté.
 *
 *          Revision 1.14  2013-10-29 10:04:08  donak
 *          Fixed: Unlinked patient records possessed the possibility to upload documents to the DSP, which is impossible
 *          Fixed: User will now be informed if client time is out of sync with the server and thus the saml assertion is denied
 *
 *          Revision 1.13  2013-10-21 08:14:55  ferring
 *          linking and unlinking patient changed
 *
 *          Revision 1.12  2013-10-11 07:51:28  ferring
 *          saving GecamedOriginId if doc ID was found in incident entry of patient
 *
 *          Revision 1.11  2013-10-08 16:43:43  donak
 *          Adjusted ErrorDialog to allow user to copy error details to clipboard or save them to file system
 *          Changed order of code display names in CdaUpload dialog for desc. to asc.
 *
 *          Revision 1.10  2013-10-08 12:46:48  donak
 *          eSant� upload dialog and logging
 *
 *          Revision 1.9  2013-10-08 08:54:14  ferring
 *          class name refactoring and tree view implementation (first steps)
 *
 *          Revision 1.8  2013-10-07 16:12:12  donak
 *          Logging, message backup, and error protocol creation
 * <br>
 *          Revision 1.7 2013-10-07 10:48:49 ferring <br>
 *          Upload logging preparation <br>
 * <br>
 *          Revision 1.6 2013-10-03 12:51:26 ferring <br>
 *          eSanté logging method added <br>
 * <br>
 *          Revision 1.5 2013-09-30 10:56:33 ferring <br>
 *          eSante integration in GECAMed and synchronise function added <br>
 * <br>
 *          Revision 1.4 2013-09-23 09:30:43 ferring <br>
 *          eSante: saving document data in database <br>
 * <br>
 *          Revision 1.3 2013-09-19 12:24:43 ferring <br>
 *          eSante bugs fixed and documents stored in database <br>
 * <br>
 *          Revision 1.2 2013-09-13 15:03:56 ferring <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.1 2013-09-13 14:18:56 ferring <br>
 *          eSante bugs fixed and database implementation started <br>
 */
@Remote(CDAManager.class)
@Stateless
public class CDAManagerBean extends GECAMedSessionBean implements CDAManager {
	/* ======================================== */
	// MEMBERS
	/* ======================================== */

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

	@PersistenceContext(unitName = "gecam")
	EntityManager em;
	
	@EJB
	IncidentManager incidentManager;

	/* ======================================== */
	// CLASS BODY
	/* ======================================== */

//	/*
//	 * (non-Javadoc)
//	 * 
//	 * @see
//	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#saveDocument
//	 * (lu.tudor.santec.gecamed.esante.ejb.entity.beans.CDADocument)
//	 */
//	public CdaDocument saveDocument(CdaDocument document) {
//		if (document.isPersistent())
//			return updateCdaDocument(document);
//		else
//			return addCdaDocument(document);
//	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#lastUpdated
	 * (java.lang.Integer)
	 */
	public Date lastUpdated(Integer dspId) {
		try {
			return (Date) em.createNamedQuery(CdaDocument.LAST_UPDATE_OF_DSP)
					.setParameter("dspId", dspId)
					.setMaxResults(1)
					.getSingleResult();
		} catch (NoResultException e) {
			// no CDA documents found for this patient
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#
	 * saveCdaDocument
	 * (lu.tudor.santec.gecamed.esante.ejb.entity.beans.CDADocument)
	 */
	public CdaDocument saveCdaDocument(CdaDocument cda) {
//		Calendar utcNow = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
//		update.setDownloadTime(utcNow.getTime());
//		update.setNoOfHistoryEntries(0);
		
		setGecamedOrigin(cda);
		CdaDocument update;
		
		try
		{
			update = (CdaDocument) em.createNamedQuery(CdaDocument.GET_DOCUMENT_BY_OID)
					.setParameter("oid", cda.getDocumentUniqueId())
					.setParameter("dspId", cda.getDspId())
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			update = em.merge(cda);
		}
		catch (NonUniqueResultException e)
		{
			logger.warn("Mutliple results for CDA document "+cda.getDocumentUniqueId()+" of DSP with ID "+cda.getDspId());
			update = (CdaDocument) em.createNamedQuery(CdaDocument.GET_DOCUMENT_BY_OID)
					.setParameter("oid", cda.getDocumentUniqueId())
					.setParameter("dspId", cda.getDspId())
					.setMaxResults(1)
					.getSingleResult();
		}
		
		// when loading the CDA from DB, the DSP object is lost
//		update.setDsp(cda.getDsp());
		getDspOfCda(update);
		
		return update;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#
	 * getCdaByUid
	 * (lu.tudor.santec.gecamed.esante.ejb.entity.beans.CDADocument)
	 */
	public CdaDocument getCdaByUid(String documentUid, Integer dspId) {

		CdaDocument document = null;

		try {
			document = (CdaDocument) em.createNamedQuery(CdaDocument.GET_DOCUMENT_BY_OID).setParameter("oid", documentUid).setParameter("dspId", dspId)
					.getSingleResult();
		} catch (NoResultException e) {
			logger.error("Unable to find CDA document with uid " + documentUid + " and dsp id " + dspId + " in database.");
		} catch (NonUniqueResultException e) {
			logger.warn("Mutliple results for CDA document " + documentUid + " of DSP with ID " + dspId);
			document = (CdaDocument) em.createNamedQuery(CdaDocument.GET_DOCUMENT_BY_OID).setParameter("oid", documentUid).setParameter("dspId", dspId)
					.setMaxResults(1).getSingleResult();
		}

		// when loading the CDA from DB, the DSP object is lost
		if (document != null) {
			getDspOfCda(document);
		}

		return document;
	}

	private void setGecamedOrigin (CdaDocument update)
	{
		try
		{
			String			docId	= update.getDocumentUniqueId();
			Integer			dspId	= update.getDspId();
			IncidentEntry	entry;
			
			if (docId == null || dspId == null)
				throw new RuntimeException("Patient id and CdaDocument id MUST NOT BE NULL!");
			
			List<?> result = em.createNamedQuery(CdaDocument.GET_INCIDENT_ENTRY_BY_DOC_ID)
			 		.setParameter("uid", docId)
			 		.setParameter("dspId", dspId)
			 		.getResultList();
			
			if (result == null || result.isEmpty())
			{
				// document ID not found in incident entries
				return;
			}
			
			entry = (IncidentEntry) result.get(0);
			
			if (result.size() > 1)
			{
				logger.error("CdaDocument ID '" + docId + 
						"' should be unique but does exist several time for patient with ID " + 
						((IncidentEntry)result.get(0)).getIncident().getPatientId() + ".\nIncidentEntry " + entry.getId() + 
						" is marked as origin of CDA anyway ...");
			}
			
			update.setOriginIncidentEntryId(entry.getId());
		}
		catch (Exception e)
		{
			logger.error(e.getMessage(), e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#
	 * changeDocument
	 * (lu.tudor.santec.gecamed.esante.ejb.entity.beans.CDADocument)
	 */
	public CdaDocument updateCdaDocument(CdaDocument document, boolean saveCdaDocument) {
		if (!document.isPersistent())
			throw new RuntimeException("Document does not yet exist in database. You can only change persistent documents.\n"
					+ "To store a document, please use the method CDAManager#addOrUpdateDocument(CDADocument)!");
		
		return save(document, saveCdaDocument);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#getDocuments
	 * (java.lang.Integer)
	 */
	public List<CdaDocument> getDocuments(Integer dspId) {
		@SuppressWarnings("unchecked")
		List<CdaDocument> documents = em.createNamedQuery(
				CdaDocument.GET_DOCUMENTS_OF_DSP)
				.setParameter("dspId", dspId)
				.getResultList();

		if (documents == null)
			return null;

		return documents;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#getDocument
	 * (java.lang.Integer)
	 */
	public CdaDocument getDocument(Integer entryId) {
		try {
			return (CdaDocument) em.createNamedQuery(
					CdaDocument.GET_DOCUMENT_OF_ENTRY)
					.setParameter("entryId", entryId)
					.getSingleResult();
		} catch (NoResultException e) {
			logger.info("There was no CDA document for the entry with the ID " + entryId);
			return null;
		}
	}
	

	/*
	 * (non-Javadoc)
	 * 
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#
	 * loadCDAFileContent
	 * (lu.tudor.santec.gecamed.esante.ejb.entity.beans.CDADocument)
	 */
	public byte[] loadCDAFileContent(CdaDocument document) throws Exception {
		String[] cdaPath;
		
		if (document.getServerFileName() == null || document.getDspId() == null)
			return null;

		cdaPath = getCDAPath(document);

		return FileUtils.getGECAMedFile(cdaPath[1], cdaPath[0]);
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#linkPatient(java.lang.Integer, java.lang.String)
	 */
	public Dsp linkDsp (Dsp dsp) throws DspLinkingException
	{
		if (dsp == null || dsp.getPatientId() == null)
		{
			logger.error("The DSP to link with and its patient ID must not be null!");
			return dsp;
		}
		
		try
		{
			dsp = em.merge(dsp);
			
			// no DSP linked to this patient, yet
			em.createNamedQuery(Dsp.UPDATE_PATIENT_LUXEMBOURG_ID)
					.setParameter("luxembourgId", dsp.getDspOid())
					.setParameter("patientId", dsp.getPatientId())
					.executeUpdate();
			dsp.setPatientId(dsp.getPatientId());
			
			return dsp;
		}
		catch (EntityExistsException e)
		{
			// there is already a DSP linked to this patient
			throw new DspLinkingException("The patient (ID = " + dsp.getPatientId() + 
					") is already linked to another DSP.");
		}
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#unlinkPatient(java.lang.Integer)
	 */
	public void unlinkDsp (Integer patientId)
	{
		if (patientId == null)
		{
			logger.error("The patient ID to unlink a DSP from must not be null!");
			return;
		}
		
		// delete all CDA incident entries of this patient
		Dsp dsp	= getLinkedDsp(patientId, true);
		
		if (dsp == null)
		{
			logger.info("DSP for patient with ID "+patientId+" has already been unlinked by another user!");
			return;
		}
		
		int deleted = em.createNamedQuery(
				CdaDocument.DELETE_CDA_ENTRIES_OF_PATIENT)
				.setParameter("patientId", patientId)
				.executeUpdate();
		logger.info(deleted + " incident entries for patient " + patientId + " deleted.");
		
		incidentManager.deleteNullIncident(patientId);
		
		// delete the DSP with its CDA documents (on delete cascade)
		em.remove(dsp);
		logger.info("DSP for patient with ID " + patientId + " and its CDA documents deleted.");
		
		// remove the eSanté ID (idLuxembourg) from the patient to mark it as not linked
		em.createNamedQuery(Dsp.UPDATE_PATIENT_LUXEMBOURG_ID)
				.setParameter("luxembourgId", null)
				.setParameter("patientId", patientId)
				.executeUpdate();
		logger.info("idLuxembourg removed from patient with ID " + patientId);
		
		
		// remove the esante folder in the patient folder
		if (FileUtils.deleteCdaPatientFolder(patientId))
			logger.info("Deleted the eSante folder of patient with ID " + patientId);
		else
			logger.error("Couldn't delete patient folder with eSante files of patient with ID "
					+ patientId);
	}
	
	
	public CdaDocument deleteCdaFile (CdaDocument document)
	{
		if (document == null || !document.isDownloaded())
			return document;
		
		String filename = document.getServerFileName();
		
		
		try
		{
			
			Integer patientId = getPatientIdOfDocument(document);
			if (patientId != null)
				FileUtils.deleteGECAMedPatientFile(filename, CdaDocument.CDA_PREFIX, patientId);
			else
				logger.warn("Couldn't find patient or DSP of document with ID " + document.getId());
		}
		catch (Exception e)
		{
			logger.error("Error when deleting the file of a CDA document", e);
		}
		document.setServerFileName(null);
		
		return em.merge(document);
	}
	
	
	public Long countDownloadedNotIntegratedCdaDocuments ()
	{
		return (Long) em.createNamedQuery(CdaDocument.COUNT_DOWNLOADED_NOT_INTEG_CDA_FILES)
				.getSingleResult();
	}
	
	
	@SuppressWarnings("unchecked")
	public List<CdaDocument> getDownloadedNotInegratedCdaDocuments (int maxCount)
	{
		Query query = em.createNamedQuery(CdaDocument.GET_DOWNLOADED_NOT_INTEG_CDA_FILES);
		
		if (maxCount > 0)
			query.setMaxResults(maxCount);
		
		return query.getResultList();
	}
	
	
	public void updateDspOid (Integer dspId, Integer patientId, String dspOid)
	{
		if (dspId != null)
			em.createNamedQuery(Dsp.UPDATE_DSP_OID)
					.setParameter("dspOid", dspOid)
					.setParameter("dspId", dspId)
					.executeUpdate();
		if (patientId != null)
			em.createNamedQuery(Dsp.UPDATE_PATIENT_LUXEMBOURG_ID)
					.setParameter("luxembourgId", dspOid)
					.setParameter("patientId", patientId)
					.executeUpdate();
	}
	
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#log(
	 * byte[], java.lang.String)
	 */
	public String log(byte[] data, String filename) {
		String eSanteLogDir = ServerConfig.getProperty(ServerConfig.JBOSS_DIR) + "/server/default/log/esante/";

		try {
			filename = FileUtils.saveGECAMedFile(data, filename, eSanteLogDir);
			return eSanteLogDir + filename;
		} catch (Exception e) {
			logger.error("Error while trying to log an eSanté error in folder " + eSanteLogDir, e);
			return null;
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#logErroneousMessage(
	 * java.lang.String, java.lang.String)
	 */
	public String logErroneousMessage (String data, String filename) {
		String dir = new StringBuilder(255).append(ServerConfig.JBOSS_DIR).append(CdaDocument.ESANTE_FOLDER)
				.append(CdaDocument.CDA_ERROR_PREFIX).toString().replace('\\', '/');

		return logMessage(data, dir, filename);
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager#logUploadedMessage(
	 * java.lang.String, java.lang.String, int)
	 */
	public String logUploadedMessage(String data, String filename, int patientId) {
		// generate the appropriate path for saving an uploaded file on the
		// server (<folder of specific patient>/CDA/upload)
		String dir = new StringBuilder(255).append(ServerConfig.getProperty(ServerConfig.PATIENT_FILES_DIR)).append(File.separator)
				.append(FileUtils.getPatientDir(patientId)).append(CdaDocument.CDA_UPLOAD_PREFIX).toString().replace('\\', '/');

		return logMessage(data, dir, filename);
	}
	
	
	public void updateError (CdaDocument document)
	{
		em.createNamedQuery(CdaDocument.UPDATE_CDA_ERROR)
				.setParameter("id", document.getId())
				.setParameter("error", document.getError())
				.executeUpdate();
	}
	
	
	/**
	 * Marks in the database, that the document has been read (well, at least opened) by the user.
	 */
	public void updateRead (CdaDocument document)
	{
		em.createNamedQuery(CdaDocument.UPDATE_CDA_READ)
				.setParameter("id", document.getId())
				.setParameter("read", document.getRead())
				.executeUpdate();
	}
	

	/**
	 * Provides the electronic health record (here called dsp) to which the patient has been linked
	 * 
	 * @param patientId
	 *            The id of the patient for whom the dsp should be determined
	 * @return The electronic health record of the patient or null, if no ehr has been linked to the patient
	 */
	public Dsp getLinkedDsp(Integer patientId) {
		return getLinkedDsp(patientId, false);
	}
	
	
	/**
	 * Provides the electronic health record (here called dsp) to which the patient has been linked
	 * 
	 * @param patientId
	 *            The id of the patient for whom the dsp should be determined
	 * @param deleteMultipleDsps
	 *            If the patient has been linked to multiple health records, all but the latest links will be removed
	 * @return The electronic health record of the patient or null, if no ehr has been linked to the patient
	 */
	public Dsp getLinkedDsp (Integer patientId, boolean deleteMultipleDsps)
	{
		try
		{
			Dsp dsp = (Dsp) em.createNamedQuery(Dsp.GET_DSP_BY_PATIENT_ID)
					.setParameter("patientId", patientId)
					.getSingleResult();
			
//			System.out.println("DSP doc size: "+dsp.getDocuments().size());
			return dsp;
		}
		catch (NoResultException e)
		{
			// no DSP found for this patient
			return null;
		}
		catch (NonUniqueResultException e)
		{
			// shouldn't happen, as the patient_id column does have an unique constraint by now
			
			// multiple DSPs found for this patient
			logger.error("There is an error in the database mapping. For the patient ID "+patientId+" exist multiple DSPs. This shouldn't be!");
			
			@SuppressWarnings("unchecked")
			List<Dsp> dsps		= (List<Dsp>) em.createNamedQuery(Dsp.GET_DSP_BY_PATIENT_ID)
					.setParameter("patientId", patientId)
					.getResultList();
			
			Patient	patient		= em.find(Patient.class, patientId);
			String	dspOid		= patient.getIdLuxembourg();
			Dsp		linkedDsp	= null;
			Dsp		dsp;
			
			for (int index = 0; (deleteMultipleDsps || linkedDsp == null) && index < dsps.size(); index++)
			{
				dsp = dsps.get(index);
				if (linkedDsp == null && dspOid != null && dspOid.equals(dsp.getDspOid()))
					linkedDsp	= dsp;
				else if (deleteMultipleDsps)
					// delete all except one that fits to the DSP OID of the patient
					em.remove(dsp);
			}
			
			// return only the DSP created last (query is: ORDER BY id DESC)
			return linkedDsp;
		}
	}
	
	
	public Set<String> getCdaOidSet (Integer dspId)
	{
		@SuppressWarnings("unchecked")
		List<String> result = em.createNamedQuery(CdaDocument.GET_CDA_OIDS)
				.setParameter("dspId", dspId)
				.getResultList();
		
		if (result == null || result.isEmpty())
			return null;
		
		return new HashSet<String>(result);
	}
	
	
	public static CDAManager getInstance() {
		return (CDAManager) ManagerFactory.getRemote(CDAManagerBean.class);
	}

	/* ======================================== */
	// HELP METHODS
	/* ======================================== */

	/**
	 * @param document
	 *            The documents whose content is to be saved.
	 * @return The eventually changed document.
	 */
	private CdaDocument saveCDAFile(CdaDocument document) {
		String content;
		String[] cdaPath;
		String filename;
		
		try {
			content = document.getContent();

			if (content != null) {
				cdaPath = getCDAPath(document);

				filename = FileUtils.saveGECAMedFile(content.getBytes(), cdaPath[1], cdaPath[0]);

				if (filename != null)
					document.setServerFileName(filename);
			}
			// else: if the content is null, don't save it
		} catch (Exception e) {
			logger.error("Error while trying to get the content of the document.", e);
		}
		
		return document;
	}

	/**
	 * Logs a file to a specified directory on the GECAMed server
	 * 
	 * @param data
	 *            The content of the file that is to be created
	 * @param logDir
	 *            The folder on the GECAMed server to which the file will be
	 *            written
	 * @param filename
	 *            The name of the file that will be created (filename should NOT
	 *            contain an extension as ".xml" is added automatically)
	 * @return The final path and filename. This might differ from the provided
	 *         one as the filename will be normalized according to the GECAMed
	 *         policies. If the operation could not be completed because of any
	 *         reason, an error will be logged to the normal log file and the
	 *         method returns null.
	 */
	private String logMessage(String data, String logDir, String filename) {
	
		try {
			filename = filename.trim().replace(" ", "_").replaceAll("[^0-9a-zA-Z_]", "").replaceAll("_{2,}", "_") + ".xml";
			if(data==null){
				logger.error("The logfile \""+logDir+filename+"\" has not been created as there is no content.");
				return null;
			}
			filename = FileUtils.saveGECAMedFile(data.getBytes("UTF-8"), filename, logDir);

			return logDir + filename;
		} catch (Exception e) {
			logger.error("Error while trying to log the file \"" + filename + "\" to folder " + logDir, e);
			return null;
		}
	}

	/**
	 * Saves the given {@link CdaDocument}
	 * 
	 * @param document
	 *            The document to be saved
	 * @return The saved document - this will be a new instance of the object
	 */
	private CdaDocument save (CdaDocument document, boolean saveCdaFile) {
		if (!document.isPersistent() && documentAlreadyExists(document))
			return null;
		
		saveCDAFile(document);

		return em.merge(document);
	}

	private boolean documentAlreadyExists (CdaDocument document)
	{
		Integer dspId	= document.getDspId();
		String	cdaUid	= document.getDocumentUniqueId();
		
		
		Long docCount = (Long) em.createNamedQuery(CdaDocument.GET_DOCUMENT_COUNT)
				.setParameter("cdaUid", cdaUid)
				.setParameter("dspId", dspId)
				.getSingleResult();
		
		if (docCount.intValue() > 0)
			return true;
		else
			return false;
	}

	private String[] getCDAPath(CdaDocument document) {
		return getCDAPath(document, false);
	}

	/**
	 * @param document
	 *            The {@link CdaDocument} of which file to get the path of
	 * @return A String array with 2 elements, representing the CDA file
	 *         location.<br>
	 *         0. item is the folder name.<br>
	 *         1. item is the file name.
	 */
	private String[] getCDAPath(CdaDocument document, boolean upload) {
		String prefix	= upload ? CdaDocument.CDA_UPLOAD_PREFIX : CdaDocument.CDA_PREFIX;
		String filename	= document.getServerFileName();
		String dir		= ServerConfig.getProperty(ServerConfig.PATIENT_FILES_DIR) + "/" 
				+ FileUtils.getPatientDir(getDspOfCda(document).getPatientId()) + prefix;
		
		if (filename == null) {
			String name = document.getTitle();
			int maxNameLength;

			if (name == null) {
				name = "gecamed_cda";
			} else {
				/*
				 * replace all spaces with underscores and remove everything,
				 * that is not a (normal) letter or a digit
				 */
				name = name.trim().replace(" ", "_").replaceAll("[^0-9a-zA-Z_]", "").replaceAll("_{2,}", "_");

				// cut the name, if it might be too long for NTFS
				maxNameLength = 255 - dir.length() - 20;
				if (name.length() > 50)
					name = name.substring(0, 50);
				if (maxNameLength < name.length() && maxNameLength > 5)
					name = name.substring(0, maxNameLength);

				filename = name + ".xml";
			}
		}

		return new String[] { dir, filename };
	}
	
	
	private Integer getPatientIdOfDocument (CdaDocument document)
	{
		Dsp	dsp	= getDspOfCda(document);
		if (dsp == null)
		{
			if (document.getDspId() != null)
			{
				try
				{
					dsp	= em.find(Dsp.class, document.getDspId());
				}
				catch (Exception e)
				{
					logger.error("Error while trying to load DSP of document with ID "+document.getId(), e);
					return null;
				}
			}
		}
		
		if (dsp != null)
			return dsp.getPatientId();
		else
			return null;
	}
	
	
	private Dsp getDspOfCda (CdaDocument document)
	{
		if (document.getDsp() == null)
		{
			Dsp dsp = em.find(Dsp.class, document.getDspId());
			document.setDsp(dsp);
		}
		
		return document.getDsp();
	}
}
