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

import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import lu.tudor.santec.gecamed.core.gui.GECAMedIconNames;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.plugin.filehandler.FileOpener;
import lu.tudor.santec.gecamed.core.gui.plugin.filehandler.FileOpener.FileOpenerListener;
import lu.tudor.santec.gecamed.core.gui.widgets.ErrorDialog;
import lu.tudor.santec.gecamed.core.gui.widgets.GECAMedBaseDialogImpl;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
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.beans.CDAManagerBean;
import lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager;
import lu.tudor.santec.gecamed.esante.gui.dialogs.DocumentNotSupportedPopup;
import lu.tudor.santec.gecamed.esante.gui.dialogs.ESanteDialog;
import lu.tudor.santec.gecamed.esante.gui.history.CdaHistoryHandler;
import lu.tudor.santec.gecamed.esante.gui.tab.CdaHandler;
import lu.tudor.santec.gecamed.esante.gui.tab.ESanteTab;
import lu.tudor.santec.gecamed.esante.gui.utils.ESanteGuiUtils;
import lu.tudor.santec.gecamed.esante.gui.webservice.Security;
import lu.tudor.santec.gecamed.esante.gui.webservice.SoapSender;
import lu.tudor.santec.gecamed.esante.gui.webservice.WebserviceException;
import lu.tudor.santec.gecamed.esante.utils.ESanteUtils;
import lu.tudor.santec.gecamed.esante.utils.Utility;
import lu.tudor.santec.gecamed.esante.utils.exceptions.SendingStoppedException;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Incident;
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.beans.IncidentManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.gui.PatientManagerModule;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: DocumentWorker.java,v $
 * <br>Revision 1.38  2014-02-20 16:38:52  ferring
 * <br>notify when patient was stopped
 * <br>
 * <br>Revision 1.37  2014-02-06 17:48:40  ferring
 * <br>flag added, to decide if the document shall be stored on the server or not
 * <br>
 * <br>Revision 1.36  2014-02-06 16:39:18  ferring
 * <br>Removing error, if document was opened correctly
 * <br>
 * <br>Revision 1.35  2014-02-04 10:08:16  ferring
 * <br>eSante ID management completed
 * <br>Only those documents will be shown, that are retrieved by the RSQ
 * <br>
 * <br>Revision 1.34  2014-01-30 14:20:43  donak
 * <br>applied valid rules for determining if user is blacklisted
 * <br>No presence password is asked for blacklisted user but a dsp assertion is obtained as the user still has access to his own documents
 * <br>
 * <br>Revision 1.33  2014-01-27 10:05:28  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.32  2014-01-17 08:47:57  ferring
 * <br>check, whether presence password is required
 * <br>
 * <br>Revision 1.31  2014-01-06 12:34:28  ferring
 * <br>download site constants moved
 * <br>
 * <br>Revision 1.30  2013-12-19 12:31:16  ferring
 * <br>GECAMedBaseDialogImpl.showMessage changed to ESanteDialog.showMessage
 * <br>
 * <br>Revision 1.29  2013-12-17 12:54:49  ferring
 * <br>comment and TODO changed
 * <br>
 * <br>Revision 1.28  2013-12-17 08:39:36  ferring
 * <br>Document not found exception caught and dialog shown
 * <br>
 * <br>Revision 1.27  2013-12-16 12:05:12  ferring
 * <br>logging eSanté actions
 * <br>
 * <br>Revision 1.26  2013-12-13 15:02:24  ferring
 * <br>Exception handling changed
 * <br>
 * <br>Revision 1.25  2013-12-13 12:31:31  ferring
 * <br>Exception handling changed
 * <br>
 * <br>Revision 1.24  2013-12-10 12:08:18  ferring
 * <br>load locally cached CDA files when loading DSP
 * <br>
 * <br>Revision 1.23  2013-12-06 16:25:20  ferring
 * <br>CDA synch fixed
 * <br>
 * <br>Revision 1.22  2013-12-04 13:12:29  ferring
 * <br>Checking eSante server status
 * <br>
 * <br>Revision 1.21  2013-11-28 16:11:51  ferring
 * <br>CDA columns changed and added
 * <br>
 * <br>Revision 1.20  2013-11-26 10:29:02  ferring
 * <br>Send client CDA to server, when integrating CDA
 * <br>
 * <br>Revision 1.19  2013-11-26 08:16:23  ferring
 * <br>Option added for temporary downloads of CDA documents
 * <br>Property names changed
 * <br>
 * <br>Revision 1.18  2013-11-25 08:26:09  ferring
 * <br>notify listeners, that elements are dequeued
 * <br>
 * <br>Revision 1.17  2013-11-22 14:51:10  ferring
 * <br>Restructured CDA tree and list notification, in order not to use an EntityBean as model.
 * <br>Buttons added to delete the CDA file and remove the incident entry
 * <br>
 * <br>Revision 1.16  2013-11-20 10:32:59  ferring
 * <br>integration and opening will be enqueued to the thread in any case
 * <br>
 * <br>Revision 1.15  2013-11-19 17:37:38  ferring
 * <br>checks for downloaded and integrated documents changed
 * <br>
 * <br>Revision 1.14  2013-11-18 15:36:51  ferring
 * <br>Restructured methods with SoapSender and DocumentWorker restructured.
 * <br>Checking the IPID, if it changes and refreshing the data.
 * <br>
 * <br>Revision 1.13  2013-11-14 17:56:33  ferring
 * <br>check if document needs to be downloaded and better check if documents can be integrated
 * <br>
 * <br>Revision 1.12  2013-11-12 15:37:53  ferring
 * <br>downloads can now be stopped
 * <br>
 * <br>Revision 1.11  2013-11-11 11:42:37  ferring
 * <br>stop option removed
 * <br>
 * <br>Revision 1.10  2013-11-05 09:47:56  ferring
 * <br>ESantePatient changed to Dsp. The Dsp object will be stored in the database, if a patient is linked with eSanté.
 * <br>
 * <br>Revision 1.9  2013-10-28 16:54:12  ferring
 * <br>eSante and GECAMed patient compare dialog added and a lot of bug fixes
 * <br>
 * <br>Revision 1.8  2013-10-23 07:31:20  ferring
 * <br>document download fixed
 * <br>
 * <br>Revision 1.7  2013-10-22 14:33:19  ferring
 * <br>Thread canceling prepared
 * <br>
 * <br>Revision 1.6  2013-10-22 06:00:38  ferring
 * <br>stop downloading folder (first steps)
 * <br>
 * <br>Revision 1.5  2013-10-21 08:22:07  ferring
 * <br>linking and unlinking patient changed
 * <br>dequeuing downloads
 * <br>
 * <br>Revision 1.4  2013-10-08 08:54:13  ferring
 * <br>class name refactoring and tree view implementation (first steps)
 * <br>
 * <br>Revision 1.3  2013-10-01 13:14:57  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.2  2013-09-30 11:53:49  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.1  2013-09-30 10:56:03  ferring
 * <br>eSante integration in GECAMed and synchronise funktion added
 * <br>
 * <br>Revision 1.2  2013-09-23 09:30:43  ferring
 * <br>eSante: saving document data in database
 * <br>
 * <br>Revision 1.1  2013-09-19 12:24:43  ferring
 * <br>eSante bugs fixed and documents stored in database
 * <br>
 */

public class DocumentWorker implements Runnable
{
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */

	public static final int	DOWNLOAD_DOCUMENT			= 1<<0;
	public static final int OPEN_DOCUMENT				= 1<<1 | DOWNLOAD_DOCUMENT;
	public static final int	INTEGRATE_DOCUMENT			= 1<<2 | DOWNLOAD_DOCUMENT;
	public static final int DISINTEGRATE_DOCUMENT		= 1<<4;
	public static final int DELETE_DOWNLOADED_CONTENT	= 1<<5;
	public static final int OPEN_AND_INTEGRATE_DOCUMENT	= OPEN_DOCUMENT | INTEGRATE_DOCUMENT;
	public static final int DELETE_DOCUMENT				= DELETE_DOWNLOADED_CONTENT | DISINTEGRATE_DOCUMENT;
	
	
	private static final int OPTION_PROCEED		= 0;
	private static final int OPTION_NEXT		= 1;
	private static final int OPTION_END			= 2;
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(DocumentWorker.class.getName());
	
	private static DocumentWorker		instance;
	
	/**
	 * Maps the CAD document ID to its temporary file name after the download
	 */
	private static Map<String, String>	cdaTmpFilenameMap;
	
	
	private Queue<DocumentQueueItem>	documentQueue;
	
	private Queue<DocumentQueueItem>	patientQueue;
	
	private Set<Integer>				patientsToReset;
	
	private DocumentQueueItem			queueItem;
	
	private DocumentQueueItem			patientQueueItem;
	
	private Thread						thread;
	
	private SoapSender					sender;
	
	
	
	/* ======================================== */
	// CONSTRUCTORS
	/* ======================================== */
	
	private DocumentWorker ()
	{
		patientQueue	= new LinkedList<DocumentQueueItem>();
		documentQueue	= new LinkedList<DocumentQueueItem>();
		patientsToReset	= new HashSet<Integer>();
		sender			= new SoapSender(null, true);
	}
	
	
	private static DocumentWorker getInstance ()
	{
		if (instance == null)
		{
			instance	= new DocumentWorker();
		}
		
		return instance;
	}
	
	
	
	/* ======================================== */
	// CLASS BODY
	/* ======================================== */
	
	public void run ()
	{
		int				option;
		StringBuilder	logMsg;
		CdaDocument		logDoc;
		
		
		try
		{
			Security.getInactivityMonitor().pauseInactivityTimeout();
			
			while (true)
			{
				try
				{
					sender.clear();
					
					// ------------------------------
					// Get the queue item
					// ------------------------------
					option	= workerDefineQueueItem();
					if (option == OPTION_NEXT)
						continue;
					else if (option == OPTION_END)
						break;
					
					// check if the process was stopped
					if (sender.isStopped())
						continue;
					
					// ------------------------------
					// Download the document
					// ------------------------------
					option	= workerDownloadDocument();
					if (option == OPTION_NEXT)
						continue;
					else if (option == OPTION_END)
						break;
					
					// check if the process was stopped
					if (sender.isStopped())
						continue;
					
					// ------------------------------
					// Open and / or integrate document
					// depends on the todo option
					// ------------------------------
					option	= workerOpenAndIntegrateDocument();
					if (option == OPTION_NEXT)
						continue;
					else if (option == OPTION_END)
						break;
					
					// ------------------------------
					// Delete document
					// ------------------------------
					option	= deleteCdaFile();
					if (option == OPTION_NEXT)
						continue;
					else if (option == OPTION_END)
						break;
				}
				catch (Throwable e)
				{
					logger.error("Problem while handling CDA document.\n"
							+ "Will go on with the next, if there are more in the queue ...", e);
				}
				finally
				{
					// ------------------------------
					// Check if the patient of the 
					// current item has more items. 
					// ------------------------------
					if (queueItem != null)
					{
						queueItem.getDocument().setEnqueued(false);
						queueItem.getDocument().setBusy(false);
						queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
						queueItem.getDocument().setContent(null);
						
						// log into GECAMed system log
						logDoc	= queueItem.getDocument();
						logMsg	= new StringBuilder("CDA document \"")
								.append(logDoc.getDocumentUniqueId())
								.append("\"");
						if (queueItem.includesTodo(DOWNLOAD_DOCUMENT)
								&& logDoc.isDownloaded())
							logMsg.append(" downloaded")
									.append(ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_CLIENT 
											? " (client)"
											: " (server)");
						else if (queueItem.includesTodo(DELETE_DOCUMENT)
								&& !logDoc.isDownloaded())
							logMsg.append(" deleted");
						if (queueItem.includesTodo(INTEGRATE_DOCUMENT)
								&& logDoc.getIncidentEntryId() != null)
							logMsg.append(" integrated");
						else if (queueItem.includesTodo(DISINTEGRATE_DOCUMENT)
								&& logDoc.getIncidentEntryId() == null)
							logMsg.append(" disintegrated");
						
						workerCheckItemsOfPatient();
						
						// reset the queue items
						queueItem = null;
						if (patientQueueItem != null
								&& !patientQueueItem.hasNext())
						{
							patientQueueItem = null;
						}
					}
				}
				// ===== END OF LOOP =====
			}
		}
		finally
		{
			Security.getInactivityMonitor().resumeInactivityTimeout();
		}
	}


	/* ---------------------------------------- */
	// STATIC CLASS BODY
	/* ---------------------------------------- */
	
	public static void downloadDocument (CdaDocument document, CdaHandler handler)
	{
		getInstance().enqueue(document, handler, DOWNLOAD_DOCUMENT);
	}
	
	
	public static void openDocument (CdaDocument document, CdaHandler handler, boolean integrate)
	{
		if (document == null)
			return;
		
		getInstance().enqueue(document, handler, integrate ? OPEN_AND_INTEGRATE_DOCUMENT : OPEN_DOCUMENT);
	}
	
	
	public static void integrateDocument (CdaDocument document, CdaHandler handler)
	{
		if (document == null)
			return;
		
		getInstance().enqueue(document, handler, INTEGRATE_DOCUMENT);
	}
	
	
	public static void disintegrateDocument (CdaDocument document, CdaHandler handler)
	{
		getInstance().enqueue(document, handler, DISINTEGRATE_DOCUMENT);
	}
	
	
	public static void deleteCdaFile (CdaDocument document, CdaHandler handler)
	{
		getInstance().enqueue(document, handler, DELETE_DOCUMENT);
	}
	
	
	public static void downloadAll (CdaHandler handler)
	{
		getInstance().enqueue(null, handler, DOWNLOAD_DOCUMENT);
	}
	
	
	public static void integrateAll (CdaHandler handler)
	{
		getInstance().enqueue(null, handler, INTEGRATE_DOCUMENT);
	}
	
	
	public static void resetPatient (Integer patientId)
	{
		getInstance().addPatientToReset(patientId);
	}
	
	
	public static void dequeuePatient (Patient patient)
	{
		getInstance().dequeue(patient);
	}
	
	
	/**
	 * @param document The CDA document to search the local cache for its content
	 * @return The content of the cached file as String or <code>null</code> 
	 * if the file is not in the cache.
	 */
	public static String getLocalCachedCda (CdaDocument document)
	{
		String	tmpFilename;
		File	tmpFile;
		
		
		if (cdaTmpFilenameMap == null)
			return null;
		
		tmpFilename = cdaTmpFilenameMap.get(getCdaCacheKey(document));

		
		if (tmpFilename == null)
			return null;
		
		tmpFile		= FileOpener.getGECAMedTempFile(tmpFilename);
		
		if (tmpFile == null)
			return null;
		
		Dsp		dsp	= ESanteGuiUtils.getDspOfDocument(document);
		String	dspOid;
		if (dsp != null)
			dspOid	= dsp.getDspOid();
		else
			dspOid	= null;
		logger.debug("CDA document \"" + document.getDocumentUniqueId() 
				+ (dspOid == null ? "" : "\" of DSP \"" + dspOid) 
				+ "\" loaded from local cache.");
		
		try
		{
			return new String(FileUtils.readFile(tmpFile));
		}
		catch (IOException e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
			return null;
		}
	}
	
	
	/**
	 * @param document The document whose file was be cached.
	 * @param filename The name of the document's cache file, as it is named in the temp folder.
	 */
	public static void putLocalCachedCdaFile (CdaDocument document, String filename)
	{
		if (cdaTmpFilenameMap == null)
			cdaTmpFilenameMap = new HashMap<String, String>();
		
		cdaTmpFilenameMap.put(getCdaCacheKey(document), filename);
	}
	
	
	/**
	 * @param document The CDA document to search the local cache for its content
	 * @return The cached file of the document or <code>null</code> 
	 * if the file is not in the cache.
	 */
	public static File getLocaleCachedCdaFile (CdaDocument document)
	{
		String	tmpFilename;
		File	tmpFile;
		
		
		if (cdaTmpFilenameMap == null)
			return null;
		
		tmpFilename = cdaTmpFilenameMap.get(getCdaCacheKey(document));
		
		if (tmpFilename == null)
			return null;
		
		tmpFile = FileOpener.getGECAMedTempFile(tmpFilename);
		
		if (!tmpFile.exists() || tmpFile.isDirectory())
			return null;
		
		return tmpFile;
	}
	
	
	
	/* ======================================== */
	// HELP METHODS
	/* ======================================== */
	
	private void enqueue (CdaDocument item, CdaHandler handler, int todo)
	{
		Integer patientId = handler.getDsp() != null 
				? handler.getPatientId() 
				: ESanteGuiUtils.getDspOfDocument(item).getPatientId();
		ESanteTab.getESanteTab(patientId).downloadIsRunning(true);
		
		if (item != null)
		{
			synchronized (documentQueue)
			{
				DocumentQueueItem enqueuedItem = findEnqueuedItem(item);
				
				if (enqueuedItem == null)
				{
					documentQueue.offer(new DocumentQueueItem(
							item, handler, todo));
				}
				else
				{
					if ((todo & DELETE_DOWNLOADED_CONTENT) == DELETE_DOWNLOADED_CONTENT)
					{
						// remove all actions, that require a download (and with that the downloading itself)
						enqueuedItem.removeTodo(OPEN_AND_INTEGRATE_DOCUMENT);
					}
					if ((todo & DISINTEGRATE_DOCUMENT) == DISINTEGRATE_DOCUMENT)
					{
						enqueuedItem.removeTodo(INTEGRATE_DOCUMENT);
					}
					enqueuedItem.addTodo(todo);
				}
			}
			item.setEnqueued(true);
		}
		else if (item == null)
		{
			synchronized (patientQueue)
			{
				patientQueue.offer(new DocumentQueueItem(
						null, handler, todo));	
			}
		}
		
		startWorker();
	}
	
	
	private DocumentQueueItem findEnqueuedItem (CdaDocument document)
	{
		for (DocumentQueueItem item : documentQueue)
		{
			if (item.getDocument().equals(document))
				return item;
		}
		
		return null;
	}


	private synchronized void dequeue (Object item)
	{
		if (item instanceof Dsp)
		{
			Dsp dsp = (Dsp) item;
			
			patientQueue.remove(dsp);
		}
		else if (item instanceof CdaDocument)
		{
//			synchronized (documentQueue)
			{
				documentQueue.remove((CdaDocument) item);
			}
		}
		else if (item instanceof Patient)
		{
			// remove all items of this patient
			Integer patientId = ((Patient) item).getId();
			List<DocumentQueueItem> toRemove = new LinkedList<DocumentQueueItem>();
			
			for (DocumentQueueItem dqi : patientQueue)
			{
				if (dqi.getHandler().getPatientId().equals(patientId))
					toRemove.add(dqi);
			}
			synchronized (patientQueue)
			{
				patientQueue.removeAll(toRemove);
			}
			toRemove.clear();
			
			for (DocumentQueueItem dqi : documentQueue)
			{
				if (dqi.getHandler().getPatientId().equals(patientId))
					toRemove.add(dqi);
			}
			synchronized (documentQueue)
			{
				documentQueue.removeAll(toRemove);
			}
			
			for (DocumentQueueItem i : toRemove)
			{
				i.getDocument().setEnqueued(false);
				i.getHandler().fireDocumentChanged(i.getDocument());
			}
			
			// stop the current download, if it's of the specified patient
			if (queueItem != null)
			{
				synchronized (queueItem)
				{
					if (patientQueueItem != null)
					{
						synchronized (patientQueueItem)
						{
							if (patientQueueItem.getHandler().getPatientId().equals(patientId))
								patientQueueItem = null;
						}
					}
					
					if (queueItem.getHandler().getPatientId().equals(patientId))
						sender.stop();
				}
			}
			
			workerCheckItemsOfPatient(patientId);
		}
	}
	
	
	private void dequeueAll ()
	{
		synchronized (documentQueue)
		{
			for (DocumentQueueItem item : documentQueue)
			{
				item.getDocument().setEnqueued(false);
				item.getHandler().fireDocumentChanged(item.getDocument());
			}
			documentQueue.clear();
		}
		
		synchronized (patientQueue)
		{
			patientQueue.clear();
		}
		
		sender.stop();
	}
	
	
	private void addPatientToReset (Integer patientId)
	{
		if (thread.isAlive())
		{
			synchronized (patientsToReset)
			{
				patientsToReset.add(patientId);
			}
		}
	}
	
	
	private static boolean openDownloadedDocument (final CdaDocument document, final CdaHandler handler)
	{
		String			extension;
		byte[]			data;
		File			file		= document.getContentClientFile();
		
		
		if (!document.isBusy())
		{
			document.setBusy(true);
			handler.fireDocumentChanged(document);
		}
		
		try
		{
			if (file != null 
					&& file.exists()
					&& !file.isDirectory()
					&& file.length() > 0)
			{
				// open the existing temp file ...
				FileOpener.open(file, new FileOpenerListener()
				{
					public void openingFailed (File file, String reason, Throwable cause)
					{
						// try to open again, this time create a new file
						document.setContentClientFile(null);
						openDownloadedDocument(document, handler);
					}
					
					public void openedSuccessfull (File file) 
					{
						Dsp		dsp	= ESanteGuiUtils.getDspOfDocument(document);
						String	dspOid;
						if (dsp != null)
							dspOid	= dsp.getDspOid();
						else
							dspOid	= null;
						logger.debug("CDA document \"" + document.getDocumentUniqueId() + (dspOid == null ? "" : "\" of DSP \"" + dspOid) + "\" opened successfully.");
					}
				});
			}
 else {
				// ... or create a new one
				data = CdaUtils.getBodyAsBytes(document);

				// get the file extension, as it might not have been set
				extension = Utility.getExtensionForMediaType(document.getMediaType());
				if ((extension == null) || ("pdf".compareToIgnoreCase(extension) != 0)) {
					// show error dialog and instruct the user to view the requested file via the web frontend of the DSP
					document.getTitle();

					// obtain the eHealth id support link from database
					DocumentNotSupportedPopup.getInstance().show(new String[] { document.getTitle(), extension });

					return false;
				}
				// open the file
				file = FileOpener.open(data, extension, new FileOpenerListener()			{
					public void openingFailed (File file, String reason, Throwable cause)
					{
						document.setError(cause.getMessage());
						
						// notify the user
						ErrorDialog.showErrorDialog(MainFrame.getInstance(), 
								Translatrix.getTranslationString("DefaultFileViewHandler.errorOpeningFileTitle"), 
								Translatrix.getTranslationString("DefaultFileViewHandler.errorOpeningFileMessage", new String[] { file.getAbsolutePath() })
								+ "\n" + reason,
								cause);
						CDAManagerBean.getInstance().updateError(document);
						
						if (handler != null)
							handler.fireDocumentChanged(document);
					}
					
					public void openedSuccessfull (File file) 
					{
						if (!document.getRead() || document.getError() != null)
						{
							// Mark the document as read, if it wasn't marked as read before
							document.setRead(true);
							document.setError(null);
							if (document.isPersistent())
								CDAManagerBean.getInstance().updateCdaDocument(document,
										ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER);
							
							if (handler != null)
								handler.fireDocumentChanged(document);
						}
					}
				});
				document.setContentClientFile(file);
			}
		}
		catch (Exception e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
			
			ESanteDialog.showMessageDialog(MainFrame.getInstance(), 
					Translatrix.getTranslationString("esante.DocumentWorker.errorOpeningDocumentTitle"), 
					Translatrix.getTranslationString("esante.DocumentWorker.errorOpeningDocumentMsg"), 
					GECAMedBaseDialogImpl.OK_BUTTON_MODE, 
					GECAMedModule.getBigIcon(GECAMedIconNames.ERROR));
			
			if (document.getError() == null || !document.getError().equals(e.getMessage()))
				document.setError(e.getMessage());
			if (document.isPersistent())
				CDAManagerBean.getInstance().updateCdaDocument(document,
						ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER);
			handler.fireDocumentChanged(document);
			
			return false;
		}
		
		return true;
	}
	
	
	private static void integrateDownloadedDocument (CdaDocument document, CdaHandler handler)
	{
		if (!document.canBeIntegrated())
			return;
		
		if (!document.isBusy())
		{
			document.setBusy(true);
			handler.fireDocumentChanged(document);
		}
		
		IncidentEntry	ie			= new IncidentEntry();
		Incident		incident	= new Incident();
		IncidentManager	iManager	= (IncidentManager) ManagerFactory.getRemote(IncidentManagerBean.class);
		CDAManager		cdaManager	= CDAManagerBean.getInstance();
		Date			now			= new Date();
		Integer			userId		= GECAMedModule.getCurrentUser().getId();
		
		
		// create the incident
		incident.setCreated(now);
		incident.setCreatedBy(userId);
		incident.setModified(now);
		incident.setModifiedBy(userId);
		incident.setIncidentDate(document.getCreationTime());
		incident.setSiteId(MainFrame.getCurrentSiteId());
		incident.setPatientId(ESanteGuiUtils.getDspOfDocument(document).getPatientId());
		incident.setPhysicianId(GECAMedModule.getCurrentPhysician().getId());
		incident = iManager.saveIncident(incident);
		
		// create the incident entry
		ie.setCreated(now);
		ie.setCreatedBy(userId);
		ie.setModified(now);
		ie.setModifiedBy(userId);
		ie.setEntryDate(document.getCreationTime());
		ie.setTextContent(document.toHtml(true));
		ie.setIncident(incident);
		ie	= iManager.saveEntry(ie, IncidentManager.CDA);
		
		// make sure the content of the document is loaded
//		if (document.getClientFile() != null)
		{
			try
			{
				CdaUtils.getContent(document);
			}
			catch (Exception e)
			{
				logger.log(Level.ERROR, e.getMessage(), e);
			}
		}
		
		// set the entry and save the document
		document.setIncidentEntryId(ie.getId());
		if (document.isPersistent())
			document = cdaManager.updateCdaDocument(document,
					ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER);
		
		ESanteGuiUtils.trace("Documents", "CDA document \"" + document.getDocumentUniqueId() + "\" (incident entry " + ie.getId() 
				+ ") integrated into GECAMed history of patient " + incident.getPatientId());
		
		if (handler != null)
			handler.fireDocumentChanged(document);
		PatientManagerModule.getInstance().getPatientPanel().reloadHistory();
	}
	
	
	private void startWorker ()
	{
		if (thread == null || !thread.isAlive())
		{
			// start worker thread and wait
			thread	= new Thread("DocumentWorker")
			{
				@Override
				public void run ()
				{
					DocumentWorker.this.run();
					
//					for (ESanteTab tab : ESanteTab.getESanteTabs())
//						tab.enablePatientDspDownload();
				}
			};
			thread.start();
		}
	}
	
	
	private int workerDefineQueueItem ()
	{
		synchronized (documentQueue)
		{
			queueItem	= documentQueue.poll();
		}
		
		if (queueItem == null) 
		{
			// the single document queue is empty, go on with the patient folder
			if (patientQueueItem != null)
			{
				synchronized (patientsToReset)
				{
					if (patientsToReset.contains(patientQueueItem.getHandler().getPatientId()))
					{
						patientQueueItem.reset();
					}
					
					patientsToReset.clear();
				}
			}
			
			if (patientQueueItem == null
					|| !patientQueueItem.next())
			{
				synchronized (patientQueue)
				{
					patientQueueItem = patientQueue.poll();
				}
				
				if (patientQueueItem == null)
					// both queues are empty
					return OPTION_END;
				
				if (!patientQueueItem.next())
				{
					queueItem = null;
					return OPTION_NEXT;
				}
			}
			
			queueItem	= patientQueueItem;
		}
		return OPTION_PROCEED;
	}
	
	
	private int workerDownloadDocument ()
	{
		if (queueItem.getDocument().isDownloaded())
			return OPTION_PROCEED;
		
		try
		{
			boolean downloadSuccessfull = false;
			
			if (!queueItem.getDocument().isBusy())
			{
				queueItem.getDocument().setBusy(true);
				queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
			}
			
			try
			{
				// DOWNLOADING ...
				sender.setDsp(queueItem.getHandler().getDsp());
				downloadSuccessfull = CdaUtils.downloadContent(queueItem.getDocument(), sender, 
						queueItem.includesTodo(INTEGRATE_DOCUMENT)
							|| ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER
						? ESanteUtils.CDA_DOWNLOAD_SITE_SERVER : ESanteUtils.CDA_DOWNLOAD_SITE_CLIENT);
			}
			catch (SendingStoppedException e)
			{
				if (sender.isStopped())
					return OPTION_NEXT;
			}
			catch (SocketException e)
			{
				ESanteGuiUtils.showConnectionError(e);
				return OPTION_END;
			}
			catch (WebserviceException e)
			{
				switch (e.getErrorType()) 
				{
				case WebserviceException.TYPE_SERVER:

					ESanteGuiUtils.showConnectionError(e);
					return OPTION_END;

				case WebserviceException.TYPE_NOT_AUTHORIZED:

					dequeue(e);
					return OPTION_NEXT;

				case WebserviceException.TYPE_INVALID_LOGIN_DATA:

					// LoginDialog.incorrectLoginData();
					return workerDownloadDocument();

				case WebserviceException.TYPE_DOCUMENT_NOT_AVAILABLE:

					// unable to download the document
					ESanteDialog.showMessageDialog(MainFrame.getInstance(),
							Translatrix.getTranslationString("esante.DocumentWorker.documentNotAvailableTitle"),
							Translatrix.getTranslationString("esante.DocumentWorker.documentNotAvailableMsg"), 
							GECAMedBaseDialogImpl.OK_BUTTON_MODE,
							GECAMedModule.getBigIcon(GECAMedIconNames.WARNING));
					return OPTION_NEXT;
						
					default:
						
					ESanteGuiUtils.showAndLogWebserviceException(e);
					return OPTION_END;
				}
			}
			
			if (!downloadSuccessfull && !sender.isStopped())
			{
				dequeueAll();
				return OPTION_END;
			}
			
			queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
		}
		catch (Throwable e)
		{
			logger.error("Couldn't download the document", e);
			queueItem.getDocument().setError(e.getMessage());
			if (queueItem.getDocument().isPersistent())
				CDAManagerBean.getInstance().updateCdaDocument(queueItem.getDocument(),
						ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER);
			
			return OPTION_NEXT;
		}
		
		if (queueItem.getDocument().getError() != null)
		{
			queueItem.getDocument().setError(null);
			if (queueItem.getDocument().isPersistent())
				CDAManagerBean.getInstance().updateCdaDocument(queueItem.getDocument(),
						ESanteUtils.getCdaDownloadSite() == ESanteUtils.CDA_DOWNLOAD_SITE_SERVER);
		}
		
		return OPTION_PROCEED;
	}


	private int workerOpenAndIntegrateDocument ()
	{
		if (queueItem.includesTodo(OPEN_DOCUMENT))
		{
			openDownloadedDocument(queueItem.getDocument(), queueItem.getHandler());
		}
		
		if (sender.isStopped())
			return OPTION_NEXT;
		
		if (queueItem.includesTodo(INTEGRATE_DOCUMENT))
		{
			integrateDownloadedDocument(queueItem.getDocument(), queueItem.getHandler());
		}
		return OPTION_PROCEED;
	}


	private int deleteCdaFile ()
	{
		if (queueItem.includesTodo(DISINTEGRATE_DOCUMENT)
				&& queueItem.getDocument().canBeDisintegrated())
		{
			// remove the incident entry
			if (!queueItem.getDocument().isBusy())
			{
				queueItem.getDocument().setBusy(true);
				queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
			}
			
			CdaHistoryHandler.deleteEntryOfDocument(queueItem.getDocument());
		}
		
		if (sender.isStopped())
			return OPTION_NEXT;
		
		if (queueItem.includesTodo(DELETE_DOWNLOADED_CONTENT)
				&& queueItem.getDocument().canDeleteLocalContent())
		{
			if (!queueItem.getDocument().isBusy())
			{
				queueItem.getDocument().setBusy(true);
				queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
			}
			
			CDAManagerBean.getInstance().deleteCdaFile(queueItem.getDocument());
			queueItem.getDocument().setServerFileName(null);
			queueItem.getHandler().fireDocumentChanged(queueItem.getDocument());
		}
		
		return OPTION_PROCEED;
	}
	
	
	private void workerCheckItemsOfPatient ()
	{
		if (queueItem != null
				&& (queueItem.isSingleDocument() 
				|| !queueItem.hasNext()
				|| patientQueueItem == null))
		{
			Integer patientId = queueItem.getHandler().getDsp() != null 
					? queueItem.getHandler().getPatientId() 
					: ESanteGuiUtils.getDspOfDocument(queueItem.getDocument()).getPatientId();
			workerCheckItemsOfPatient(patientId);
		}
	}
	
	
	private void workerCheckItemsOfPatient (Integer patientId)
	{
		boolean entryForPatientFound = false;
		
		if (!sender.isStopped())
		{
			for (DocumentQueueItem item : documentQueue)
			{
				if (item.getHandler().getPatientId().equals(patientId))
				{
					entryForPatientFound = true;
					break;
				}
			}
			if (!entryForPatientFound)
			{
				for (DocumentQueueItem item : patientQueue)
				{
					if (item.getHandler().getPatientId().equals(patientId))
					{
						entryForPatientFound = true;
						break;
					}
				}
			}
		}
		ESanteTab.getESanteTab(patientId).downloadIsRunning(entryForPatientFound);
	}
	
	
	private static String getCdaCacheKey (CdaDocument document)
	{
		return new StringBuilder()
				.append(document.getDsp().getPatientId())
				.append("|")
				.append(document.getDsp().getDspOid())
				.append("|")
				.append(document.getDocumentUniqueId())
				.toString();
	}
}
