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

import ij.IJ;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.regex.Pattern;

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

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.GECAMedUtils;
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.CdaCode;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.CdaCodeCategory;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.CdaCodeDescription;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.ESanteLogin;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.ESanteProperty;
import lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager;

import org.apache.log4j.Logger;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: ESanteConfigManagerBean.java,v $
 * <br>Revision 1.19  2014-02-04 10:08:36  ferring
 * <br>eSante ID management completed
 * <br>Only those documents will be shown, that are retrieved by the RSQ
 * <br>
 * <br>Revision 1.18  2014-01-29 16:57:51  donak
 * <br>Renamed references to HPD to eHealth in respect to the request of the eHealth Agency
 * <br>Removed template DSP-11-12 as it is not needed and was created only for internal testing purposes
 * <br>
 * <br>Revision 1.17  2014-01-28 16:02:44  ferring
 * <br>eHealth ID management implemented
 * <br>
 * <br>Revision 1.16  2014-01-27 13:13:47  donak
 * <br>* Properties are now saved in db in respect to the following contexts:
 * <br>  - specific to a GECAMed user
 * <br>  - specific to a GECAMed physician
 * <br>  - specific to an eSanté plattform user
 * <br>  - general (independend of GECAMed user, physician, eSanté platform user)
 * <br>* Improved authentication handling. A signed authentication request is now only done for user authentication. for dsp authentication requests the provided saml assertion is used. (authentication speed up). ATTENTION: This fix is currently disabled till a bug in the eSanté platform has been fixed.
 * <br>* Naming of message loggings had been adapted to the naming in the connection kit (e.g. DSP-10, DSP-22)
 * <br>* Changed behavior for handling of dsps for which physician has insufficient access permissions. If physician does not want to provide presence password, a DSP-11 is sent instead of a DSP-12 to allow the physician to at least access the documents he is author of.
 * <br>
 * <br>Revision 1.15  2014-01-21 14:32:05  ferring
 * <br>myDSP configuration can be changed and reloaded.
 * <br>The clean CDA files on server function was added
 * <br>
 * <br>Revision 1.14  2013-12-13 12:30:29  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.13  2013-12-09 17:15:37  donak
 * <br>Bug-fixing for upload to eSanté platform
 * <br>
 * <br>Revision 1.12  2013-12-05 18:44:04  donak
 * <br>Partial upload support for the eSanté platform, not yet functional
 * <br>
 * <br>Revision 1.11  2013-12-05 15:19:26  donak
 * <br>Partial upload support for the eSanté platform, not yet functional
 * <br>
 * <br>Revision 1.10  2013-12-05 13:34:20  ferring
 * <br>bug fixed
 * <br>
 * <br>Revision 1.9  2013-12-04 15:22:34  ferring
 * <br>CdaCodeDescription added
 * <br>
 * <br>Revision 1.8  2013-12-04 13:11:18  ferring
 * <br>CdaCodeDescription added
 * <br>
 * <br>Revision 1.7  2013-11-12 12:48:23  donak
 * <br>Document upload:
 * <br>* conversion to pdf/a using open office has been moved to the server. OpenOffice 4 has to be located in the jboss work directory. ATTENTION: it still has to be evaluated if the license agreement dialog occurs when instance is started the first time on the server.
 * <br>* If document contains a description, the first forty characters of the description followed by three dots will be used as title instead of the filename
 * <br>* Upload of incident type letters has been fixed
 * <br>* upload for docx files has been added
 * <br>
 * <br>Upload parameters:
 * <br>* database does now support storage of user dependent properties
 * <br>* The system will automatically remember the last chosen values for confidentiality, facility type, and speciality and propose them as default when the next document will be uploaded.
 * <br>
 * <br>Inactivity Monitor:
 * <br>* the event mouse wheel scrolling is now taken into account for resetting the logoff timer
 * <br>* the logoff delay is now stored in the database. If the database does not contain this parameter, it will be created
 * <br>
 * <br>General:
 * <br>* Optimized incident entry bean handling. Caching will now avoid copying the binary content and the generated pdf content of an incident entry as these elements should only be loaded when needed. Now it should be save to re-implement a proper getBinaryContent() handling.
 * <br>
 * <br>Revision 1.6  2013-10-21 08:14:26  ferring
 * <br>loading CdaCodes if there not loaded and creating not existing, if they don't exist in the DB
 * <br>
 * <br>Revision 1.5  2013-10-08 08:57:36  ferring
 * <br>commit comments utf8 correction
 * <br>
 * <br>Revision 1.4  2013-10-03 11:17:21  donak
 * <br>eSanté integration CDA document upload metadata configuration dialog
 * <br>
 * <br>Revision 1.3  2013-10-01 10:03:11  ferring
 * <br>enhancement in code fetching
 * <br>
 * <br>Revision 1.2  2013-09-19 12:24:43  ferring
 * <br>eSante bugs fixed and documents stored in database
 * <br>
 * <br>Revision 1.1  2013-09-13 14:18:56  ferring
 * <br>eSante bugs fixed and database implementation started
 * <br>
 */
@Remote(ESanteConfigManager.class)
@Stateless
public class ESanteConfigManagerBean extends GECAMedSessionBean implements ESanteConfigManager
{
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	private static final String ESANTE_TEMPLATE_DIR	= "/esantetemplates";
	
	private static final int SEARCH_OPTION_CURRENT_DIR			= 1;
	private static final int SEARCH_OPTION_SUB_DIR				= 1<<1;
	private static final int SEARCH_OPTION_CURRENT_AND_SUB_DIR	= SEARCH_OPTION_CURRENT_DIR & SEARCH_OPTION_SUB_DIR;
	private static final int SEARCH_OPTION_RECURSIVELY			= SEARCH_OPTION_CURRENT_AND_SUB_DIR & 1<<2;
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(ESanteConfigManagerBean.class.getName());

	private static HashMap<String, FileFilter> recursiveFileFilter = new HashMap<String, FileFilter>();
	
	private static Pattern internationalDatePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
	
	private static Queue<File>	esanteTemplatestoDelete	= new LinkedList<File>();
	
	@PersistenceContext(name = "gecam")
	EntityManager em;
	
	
	/* ======================================== */
	// CLASS BODY
	/* ======================================== */
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getESanteProperty(java.lang.String)
	 */
	public String getESantePropertyValue (String name){
		return getUserProperty(name, null);
	}
	
	
	public ESanteProperty getESanteProperty (String name)
	{
		try
		{
			return (ESanteProperty) em.createNamedQuery(
					ESanteProperty.GET_PROPERTY)
					.setParameter("name", name)
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.error("Multiple results for property \"" + name + "\"");
			return null;
		}
	}
	
	
	/* ======================================== */
	// CLASS BODY
	/* ======================================== */
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getESanteProperty(java.lang.String, java.lang.Integer)
	 */
	public String getUserProperty (String name, Integer userId)
	{
		try
		{
			if (userId == null)
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_PROPERTY_VALUE)
						.setParameter("name", name)
						.getSingleResult();
			}
			else
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_USER_PROPERTY_VALUE)
						.setParameter("name", name)
						.setParameter("userId", userId)
						.getSingleResult();
			}
		}
		catch (NoResultException e)
		{
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.error("Multiple results for property \"" + name + "\"" + (userId == null ? "" : " and user ID \""+userId+"\""));
			return null;
		}
	}
	

	public String getPhysicianProperty(String name, Integer physicianId) {
		try
		{
			if (physicianId == null)
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_PROPERTY_VALUE)
						.setParameter("name", name)
						.getSingleResult();
			}
			else
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_PHYSICIAN_PROPERTY_VALUE)
						.setParameter("name", name)
						.setParameter("physicianId", physicianId)
						.getSingleResult();
			}
		}
		catch (NoResultException e)
		{
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.error("Result for property \"" + name + "\" " + (physicianId == null ? "" : ""));
			return null;
		}
	}
	
	
	public String getESanteUserProperty(String name, String esanteUserId) {
		try
		{
			if (esanteUserId == null)
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_PROPERTY_VALUE)
						.setParameter("name", name)
						.getSingleResult();
			}
			else
			{
				return (String) em.createNamedQuery(
						ESanteProperty.GET_ESANTE_USER_PROPERTY_VALUE)
						.setParameter("name", name)
						.setParameter("esanteUserId", esanteUserId)
						.getSingleResult();
			}
		}
		catch (NoResultException e)
		{
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.error("Result for property \"" + name + "\" " + (esanteUserId == null ? "" : ""));
			return null;
		}
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getESanteUserProperties(java.lang.String)
	 */
	public List<ESanteProperty> getESanteUserProperties (String name)
	{
		@SuppressWarnings("unchecked")
		List<ESanteProperty> properties	= em.createNamedQuery(
				ESanteProperty.GET_ESANTE_USER_PROPERTIES)
				.setParameter("name", name)
				.getResultList();
		
		return properties;
	}
	
	
	public Map<String, String> getESanteProperties ()
	{
		Map<String, String>	properties;
		List<?>				propertyList;
		ESanteProperty		p;
		
		
		properties		= new HashMap<String, String>();
		propertyList	= (List<?>) em.createNamedQuery(
				ESanteProperty.GET_PROPERTIES)
				.getResultList();
		
		for (Object o : propertyList)
		{
			p = (ESanteProperty) o;
			properties.put(p.getName(), p.getValue());
		}
		
		return properties;
	}
	
	
	public Map<String, String> getESanteProperties (String nameLike)
	{
		Map<String, String>	properties;
		List<?>				propertyList;
		ESanteProperty		p;
		
		
		properties		= new HashMap<String, String>();
		propertyList	= (List<?>) em.createNamedQuery(
				ESanteProperty.GET_PROPERTIES_LIKE_NAME)
				.setParameter("name", nameLike)
				.getResultList();
		
		for (Object o : propertyList)
		{
			p = (ESanteProperty) o;
			properties.put(p.getName(), p.getValue());
		}
		
		return properties;
	}
	
	
	public void setESanteProperties (HashMap<String, String> properties)
	{
		String			key;
		String			value;
		ESanteProperty	p;
		Date			now	= new Date();
		
		
		@SuppressWarnings("unchecked")
		List<ESanteProperty>	propertyList	= em.createNamedQuery(
				ESanteProperty.GET_PROPERTIES_BY_NAME)
				.setParameter("nameList", properties.keySet())
				.getResultList();
		
		// update the existing properties
		for (ESanteProperty property : propertyList)
		{
			key		= property.getName();
			value	= properties.get(key);
			
			if (value != null)
			{
				if (property.getCreated() == null)
					property.setCreated(now);
				else 
					property.setModified(now);
				property.setValue(value);
				em.merge(property);
				properties.remove(key);
			}
		}
		
		// add the missing properties
		for (String k : properties.keySet())
		{
			p	= new ESanteProperty();
			p.setName(k);
			p.setValue(properties.get(k));
			p.setCreated(now);
			p.setModified(now);
			em.merge(p);
		}
	}
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#saveESanteProperties(java.util.Collection)
	 */
	public List<ESanteProperty> saveESanteProperties (Collection<ESanteProperty> properties)
	{
		List<ESanteProperty> savedProperties = new ArrayList<ESanteProperty>(properties.size());
		
		for (ESanteProperty p : properties)
		{
			if (p.getCreated() == null)
				p.setCreated(new Date());
			else 
				p.setModified(new Date());
			
			savedProperties.add(em.merge(p));
		}
		
		return savedProperties;
	}
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#deleteProperties(java.util.Collection)
	 */
	public void deleteProperties (Collection<ESanteProperty> properties)
	{
		for (ESanteProperty p : properties)
		{
			if (p.isPersistent())
			{
				try
				{
					p = em.find(ESanteProperty.class, p.getId());
					if (p != null)
						em.remove(p);
				}
				catch (Exception e)
				{
					logger.warn("Couldn't delete ESanteProperty.", e);
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#setESanteProperty(java.lang.String, java.lang.String)
	 */
	public ESanteProperty setESanteProperty (String name, String value)
	{
		return setUserProperty(name, value, null);
	}
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#setESanteProperty(java.lang.String, java.lang.String)
	 */
	public ESanteProperty setUserProperty(String name, String value, Integer userId) {
		ESanteProperty property;

		try {
			if (userId == null) {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_PROPERTY).setParameter("name", name).getSingleResult();
			} else {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_USER_PROPERTY).setParameter("name", name).setParameter("userId", userId).getSingleResult();
			}
		} catch (NoResultException e) {
			property = new ESanteProperty();
			property.setName(name);
			property.setCreated(new Date());
			// if the property is user specific, the user id will also be set
			if (userId != null) {
				property.setUserId(userId);
			}
		}
		// if the property object contains a value than it already existed before and thus will be changed now
		if (property.getValue() != null) {
			property.setModified(new Date());
		}
		property.setValue(value);
		
		property	= em.merge(property);
		
		return property;
	}
	

	public ESanteProperty setPhysicianProperty(String name, String value, Integer physicianId) {
		ESanteProperty property;

		try {
			if (physicianId == null) {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_PROPERTY).setParameter("name", name).getSingleResult();
			} else {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_PHYSICIAN_PROPERTY).setParameter("name", name).setParameter("physicianId", physicianId).getSingleResult();
			}
		} catch (NoResultException e) {
			property = new ESanteProperty();
			property.setName(name);
			property.setCreated(new Date());
			// if the property is user specific, the user id will also be set
			if (physicianId != null) {
				property.setPhysicianId(physicianId);
			}
		}
		// if the property object contains a value than it already existed before and thus will be changed now
		if (property.getValue() != null) {
			property.setModified(new Date());
		}
		property.setValue(value);
		
		property	= em.merge(property);
		
		return property;
	}

	public ESanteProperty setESanteUserProperty(String name, String value, String esanteUserId) {
		ESanteProperty property;

		try {
			if (esanteUserId == null) {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_PROPERTY).setParameter("name", name).getSingleResult();
			} else {
				property = (ESanteProperty) em.createNamedQuery(ESanteProperty.GET_ESANTE_USER_PROPERTY).setParameter("name", name).setParameter("esanteUserId", esanteUserId).getSingleResult();
			}
		} catch (NoResultException e) {
			property = new ESanteProperty();
			property.setName(name);
			property.setCreated(new Date());
			// if the property is user specific, the user id will also be set
			if (esanteUserId != null) {
				property.setEsanteUserId(esanteUserId);
			}
		}
		// if the property object contains a value than it already existed before and thus will be changed now
		if (property.getValue() != null) {
			property.setModified(new Date());
		}
		property.setValue(value);
		
		property	= em.merge(property);
		
		return property;
	}
	
	
	
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getESanteLogin(java.lang.Integer)
	 */
	public ESanteLogin getESanteLogin (Integer physicianId)
	{
		ESanteLogin	login;
		
		
		try
		{
			login	= (ESanteLogin) em.createNamedQuery(ESanteLogin.GET_LOGIN_FOR_PHYSICIAN)
					.setParameter("physicianId", physicianId)
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			login	= null;
		}
		
		return login;
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#saveESanteLogin(lu.tudor.santec.gecamed.esante.ejb.entity.beans.ESanteLogin)
	 */
	public ESanteLogin saveESanteLogin (ESanteLogin login)
	{
		return em.merge(login);
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getCdaCodeChildren(java.lang.Integer)
	 */
	@SuppressWarnings("unchecked")
	public List<CdaCode> getCdaCodeChildren (Integer parentId)
	{
		return em.createNamedQuery(CdaCode.GET_CDA_CODE_CHILDREN)
				.setParameter("parentId", parentId)
				.getResultList();
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getCategoryByUuid(java.lang.String)
	 */
	public CdaCodeCategory getCategoryByUuid (String uuid)
	{
		try
		{
			return (CdaCodeCategory) em.createNamedQuery(CdaCodeCategory.GET_CATEGORY_BY_UUID)
					.setParameter("uuid", uuid)
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			logger.warn("No CdaCodeCategory with UUID "+uuid+" found!");
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.warn("Mutliple CdaCodeCategories with UUID "+uuid+" found!");
			List<?> categories = em.createNamedQuery(CdaCodeCategory.GET_CATEGORY_BY_UUID)
					.setParameter("uuid", uuid)
					.getResultList();
			return (CdaCodeCategory) categories.get(0);
		}
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getCategoryByName(java.lang.String)
	 */
	public CdaCodeCategory getCategoryByName (String name)
	{
		try
		{
			return (CdaCodeCategory) em.createNamedQuery(CdaCodeCategory.GET_CATEGORY_BY_NAME)
					.setParameter("name", name)
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			logger.warn("No CdaCodeCategory with name "+name+" found!");
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.warn("Mutliple CdaCodeCategories with name "+name+" found!");
			List<?> categories = em.createNamedQuery(CdaCodeCategory.GET_CATEGORY_BY_NAME)
					.setParameter("name", name)
					.getResultList();
			return (CdaCodeCategory) categories.get(0);
		}
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#getCdaCode(java.lang.Integer, java.lang.String)
	 */
	public CdaCode getCdaCode (Integer categoryId, String codeId, String descriptionLanguage)
	{
		CdaCode code;
		try
		{
			code = (CdaCode) em.createNamedQuery(CdaCode.GET_CODE_BY_CATEGORY_AND_CODE_ID)
					.setParameter("codeId", codeId)
					.setParameter("categoryId", categoryId)
					.getSingleResult();
		}
		catch (NoResultException e)
		{
			logger.warn("No CdaCode with category ID "+categoryId+" and code ID "+codeId+" found!");
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.warn("Mutliple CdaCodes with category ID "+categoryId+" and code ID "+codeId+" found!\n" + 
					"Returning the first");
			List<?> codes = em.createNamedQuery(CdaCode.GET_CODE_BY_CATEGORY_AND_CODE_ID)
					.setParameter("codeId", codeId)
					.setParameter("categoryId", categoryId)
					.getResultList();
			code = (CdaCode) codes.get(0);
		}
		
		code.setDescription(getCdaCodeDescription(code.getId(), descriptionLanguage));
		
		return code;
	}
	
	
	public CdaCodeDescription getCdaCodeDescription (Integer codeId, String language)
	{
		try
		{
			CdaCodeDescription description = (CdaCodeDescription) em.createNamedQuery(
					CdaCodeDescription.GET_CODE_DESCRIPTION_BY_CODE_ID_AND_LANGUAGE)
					.setParameter("codeId", codeId)
					.setParameter("language", language)
					.getSingleResult();
			return description;
		}
		catch (NoResultException e)
		{
			return null;
		}
		catch (NonUniqueResultException e)
		{
			logger.warn("Mutliple entries for code with ID "+codeId+" and language "+language+".\nReturning first element.");
			
			List<?> description = (List<?>) em.createNamedQuery(
					CdaCodeDescription.GET_CODE_DESCRIPTION_BY_CODE_ID_AND_LANGUAGE)
					.setParameter("codeId", codeId)
					.setParameter("language", language)
					.getResultList();
			
			return (CdaCodeDescription) description.get(0);
		}
	}
	
	public Map<Integer, String> getCdaCodeDescription (Collection<Integer> codeIdList, String language)
	{
		
		List<?> descriptionList = em.createNamedQuery(
				CdaCodeDescription.GET_CODE_DESCRIPTION_LIST_BY_CODE_ID_AND_LANGUAGE)
				.setParameter("language", (language.length()>2)?language.substring(0, 2):language)
				.setParameter("codeIdList", codeIdList)
				.getResultList();
		
		Map<Integer, String> descriptionMap = new HashMap<Integer, String>(descriptionList.size());
		
		for (Object d : descriptionList)
		{
			descriptionMap.put(((CdaCodeDescription) d).getCodeId(), ((CdaCodeDescription) d).getDisplayName());
		}
		
		return descriptionMap;
	}
	
	
	public Map<Integer, CdaCodeDescription> getCdaCodeDescriptionsOfCategory (Integer codeCategoryId, String language)
	{
		List<?> descriptionList = em.createNamedQuery(
				CdaCodeDescription.GET_CODE_DESCRIPTION_LIST_OF_CATEGORY)
				.setParameter("language", (language.length()>2)?language.substring(0, 2):language)
				.setParameter("categoryId", codeCategoryId)
				.getResultList();
		Map<Integer, CdaCodeDescription> descriptionMap = new HashMap<Integer, CdaCodeDescription>(descriptionList.size());
		
		for (Object d : descriptionList)
			descriptionMap.put(((CdaCodeDescription) d).getCodeId(), (CdaCodeDescription) d);
		
		return descriptionMap;
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#saveCdaCode(java.lang.Integer, java.lang.String, java.lang.String, java.lang.Integer)
	 */
	public CdaCode saveCdaCode (Integer categoryId, String codeId, String codeOid, String displayName, String description, String language, Integer parentId)
	{
		CdaCode code = new CdaCode();
		code.setCodeId(codeId);		
		code.setCodeOid(codeOid);
		code.setCodeCategoryId(categoryId);
		code.setParentId(parentId);
		
		code = em.merge(code);
		// TODO 
		saveCdaCodeDescription(code.getId(), displayName, description, language);
//		code.addDescription();
		
		return em.merge(code);
	}
	
	
	public CdaCodeDescription saveCdaCodeDescription (Integer codeId, String displayName, String description, String language)
	{
		return em.merge(new CdaCodeDescription(
				codeId, displayName, description, language));
	}
	
	
	public static ESanteConfigManager getInstance ()
	{
		return (ESanteConfigManager) ManagerFactory.getRemote(ESanteConfigManagerBean.class);
	}
	
	
	public HashMap<String, String> loadTemplateFiles (String[] templateNames)
	{
		HashMap<String, String> templates = new HashMap<String, String>();
		byte[] data;
		
		
		for (String templateName : templateNames)
		{
			data	= loadTemplateFile(templateName);
			if (data != null)
			{
				try
				{
					templates.put(templateName, new String(data, "UTF8"));
				}
				catch (UnsupportedEncodingException e)
				{
					logger.error("Error while trying to load the WSDL template \""+templateName+"\".", e);
				}
			}
		}
		
		return templates;
	}
	
	
	public byte[] loadTemplateFile (String templateName)
	{
		File		templateDir		= new File(ServerConfig.getProperty(ServerConfig.FILE_BASE_DIR) + ESANTE_TEMPLATE_DIR);
		File		templateFile	= null;
		List<File>	templateFiles;
//		List<File>	toDelete;
		String		dirName;
		Date		date;
		Date		latest			= null;
		Date		now				= new Date();
		
		
		if (!templateDir.exists())
			return null;
		
		templateFiles	= getFilesByName(templateDir, templateName, SEARCH_OPTION_CURRENT_DIR);
		if (templateFiles.size() > 0)
			templateFile	= templateFiles.get(0);
		
//		toDelete		= new LinkedList<File>();
		templateFiles	= getFilesByName(templateDir, templateName, SEARCH_OPTION_SUB_DIR);
		for (File f : templateFiles)
		{
			dirName = f.getParentFile().getName();
			
			try
			{
				date = GECAMedUtils.getDateFormatter(GECAMedUtils.DATE_FORMAT_INTERNATIONAL_DATE).parse(dirName);
			}
			catch (Exception e)
			{
				continue;
			}
			
			if (date.before(now) 
					&& (latest == null 
					|| date.after(latest)))
			{
//				toDelete.add(templateFile);
				markFileToBeDeleted(templateFile);
				templateFile = f;
				latest = date;
			}
		}
		
		deleteMarkedTemplates();
		
		if (templateFile == null)
		{
			return null;
		}
		else
		{
			try
			{
				File dir	= templateFile.getParentFile();
				if (!dir.equals(templateDir))
				{
					// move the template file to the eSante template base dir
					File tmp	= new File(dir.getParentFile(), templateFile.getName());
					if (templateFile.renameTo(tmp))
					{
						templateFile = tmp;
						markFileToBeDeleted(dir);
					}
					else
					{
						logger.warn("Couldn't move file \""+templateFile.getName()+"\" to eSanté template base dir");
					}
				}
			}
			catch (Exception e)
			{
				logger.warn("Error while trying to move file to eSanté template base dir and to delete the empty subdir.");
			}
			
			try
			{
				return FileUtils.readFile(templateFile);
			}
			catch (IOException e)
			{
				logger.error("Error while reading template file \"" 
						+ templateFile.getAbsolutePath() + "\".", e);
				return null;
			}
		}
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager#saveTemplateFile(java.lang.String, byte[], java.util.Date)
	 */
	public boolean saveTemplateFile (final String templateName, byte[] templateData, Date applicabilityDate)
	{
		File			templateDir	= new File(ServerConfig.getProperty(ServerConfig.FILE_BASE_DIR) + ESANTE_TEMPLATE_DIR);
		Date			now			= new Date();
		File			templateFile;
		
		
		templateDir.mkdirs();
		
		// check whether to save it as current used template or for future use
		if (applicabilityDate == null
				|| applicabilityDate.before(now))
		{
			markFileToBeDeleted(getFilesByName(templateDir, templateName, SEARCH_OPTION_CURRENT_DIR));
			templateFile	= new File(templateDir, templateName);
		}
		else
		{
			templateDir		= new File(templateDir, GECAMedUtils.getDateFormatter(
					GECAMedUtils.DATE_FORMAT_INTERNATIONAL_DATE).format(applicabilityDate));
			templateDir.mkdir();
			templateFile	= new File(templateDir, templateName);
		}
		
		// delete the existing unneeded template files
		deleteMarkedTemplates();
		
		if (templateFile.exists())
		{
			logger.warn("Couldn't write the eSanté template file \"" + templateFile.getAbsolutePath() 
					+ "\", because it already exists and couldn't be overwritten");
			return false;
		}
		else
		{
			try
			{
				// write the template file
				return FileUtils.writeFile(templateData, templateFile);
			}
			catch (IOException e)
			{
				logger.error("Error while trying to write the eSanté template file \"" 
						+ templateFile.getAbsolutePath() + "\".", e);
				return false;
			}
		}
	}
	
	
	public List<File> getFilesByName (File dir, final String filename, int searchOption)
	{
		FileFilter	filter			= recursiveFileFilter.get(filename);
		List<File>	templateFiles	= new LinkedList<File>();
		File[]		filteredFiles;
		
		
		// set the filter
		if (filter == null)
		{
			filter = new FileFilter()
			{
				public boolean accept (File pathname)
				{
					boolean flag;
					flag = pathname.exists()
							&& (
									pathname.getName().equalsIgnoreCase(filename)
								|| (
										pathname.isDirectory() 
									&&  internationalDatePattern.matcher(pathname.getName()).matches()
									)
								);
//					System.out.println("Is \""+pathname.getName()+"\" valid file for \""+filename+"\"? - "+flag);
					return flag;
				}
			};
			recursiveFileFilter.put(filename, filter);
		}
		
		// list the files with that exact filename and directories
		filteredFiles = dir.listFiles(filter);
		if (filteredFiles != null)
		{
			for (File f : filteredFiles)
			{
				if (((searchOption & SEARCH_OPTION_SUB_DIR) > 0) && f.isDirectory())
					// search the directory for files with the filename
					templateFiles.addAll(getFilesByName(f, filename, (searchOption & SEARCH_OPTION_RECURSIVELY) > 0 
							? SEARCH_OPTION_RECURSIVELY : SEARCH_OPTION_CURRENT_DIR));
				if (((searchOption & SEARCH_OPTION_CURRENT_DIR) > 0) && f.isFile())
					// found a file with the filename -> add it to the list
					templateFiles.add(f);
			}
		}
		
		// return the found files
		return templateFiles;
	}
	
	
	private static void markFileToBeDeleted (File file)
	{
		synchronized (esanteTemplatestoDelete)
		{
			esanteTemplatestoDelete.offer(file);
		}
	}
	
	
	private static void markFileToBeDeleted (Collection<File> files)
	{
		synchronized (esanteTemplatestoDelete)
		{
			for (File f : files)
				markFileToBeDeleted(f);
		}
	}
	
	
	private static void deleteMarkedTemplates ()
	{
		File		dir;
		File		f;
		List<File>	notDeleted	= new LinkedList<File>();
		
		synchronized (esanteTemplatestoDelete)
		{
			while (!esanteTemplatestoDelete.isEmpty())
			{
				f	= esanteTemplatestoDelete.poll();
				if (f  == null)
					break;
				if (!f.exists())
					continue;
				
				if (f.isDirectory())
				{
					dir = f;
				}
				else
				{
					dir = f.getParentFile();
					delete(f, notDeleted);
				}
				
				if (dir.list().length == 0)
					// delete empty dir
					delete(dir, notDeleted);
			}
			
			for (File file : notDeleted)
				esanteTemplatestoDelete.offer(file);
		}
		
	}
	
	
	private static void delete (File file, List<File> notDeleted)
	{
		if (!file.delete())
		{
			notDeleted.add(file);
			logger.info("Couldn't delete " + (file.isDirectory() ? "empty directory" : "file") 
					+ " \""+file.getAbsolutePath()+"\". Try to delete it the next time.");
		}
	}


	@SuppressWarnings("unchecked")
	public void deleteESanteUser(String eSanteUserId) throws Exception {
		if (eSanteUserId == null || eSanteUserId.length() == 0) return;
		
		List<ESanteProperty> prop = em.createNamedQuery(ESanteProperty.GET_ESANTE_USER_PROPERTYS_BY_ESANTEUSERID)
				.setParameter("esanteUserId", eSanteUserId)
				.getResultList();
		if (prop !=  null) {
			for (ESanteProperty eSanteProperty : prop) {
				em.remove(eSanteProperty);
			}
		}
	}


	@SuppressWarnings("unchecked")
	public void deletePracticeEhealthID() {
		List<ESanteProperty> prop = em.createNamedQuery(ESanteProperty.GET_PROPERTY)
				.setParameter("name", ESanteProperty.PROP_PRACTICE_EHEALTH_ID)
				.getResultList();
		if (prop !=  null) {
			for (ESanteProperty eSanteProperty : prop) {
				em.remove(eSanteProperty);
			}
		}
	}
	
	public String checkOpenOffice() {
		File destDir = new File(ServerConfig.JBOSS_DIR);
		File ooDir = new File(destDir, ServerConfig.OPEN_OFFICE_BASE_DIR);
		if ( ooDir.exists()) {
			return ooDir.getAbsolutePath();
		} else {
			return null;
		}
	}
	
	public void installOpenOffice(boolean deleteBefore) throws Exception {
		
		File destDir = new File(ServerConfig.JBOSS_DIR);
		URL ooUrl = null;
		
		String ooDir = checkOpenOffice();
		
		if (deleteBefore && ooDir != null) {
			logger.info("Deleting existing OpenOffice at: " + ooDir);
			org.apache.commons.io.FileUtils.deleteDirectory(new File(ooDir));
		}
		
		String osname = System.getProperty("os.name");

		if (osname.startsWith("Windows")) {			
			ooUrl = new URL("http://gecamed.lu/_media/download/openoffice/gecamed_ooo_windows.zip");
		} else if (osname.startsWith("Mac")) {
			ooUrl = new URL("http://gecamed.lu/_media/download/openoffice/gecamed_ooo_mac.zip");
		} else {
			ooUrl = new URL("http://gecamed.lu/_media/download/openoffice/gecamed_ooo_linux.zip");
		}
		
		logger.info("Downloading " + ooUrl + " and extracting to " + destDir.getAbsolutePath());
		FileUtils.unpackArchive(ooUrl, destDir, true);
		logger.info("OpenOffice Installation to " + destDir.getAbsolutePath() + " done!");
		
		if (osname.startsWith("Linux")) {
			File f = new File(destDir.getAbsolutePath(), "OpenOffice4/program/soffice.bin");
			try {
				String[] cmds = {
						"chmod",
						"755",
						f.getAbsolutePath()
				};
				Runtime.getRuntime().exec(cmds);	
				logger.info("Set  " + f.getAbsolutePath() + " to Executable!");
			} catch (Exception e) {
				logger.info("Error Setting  " + f.getAbsolutePath() + " to Executable!", e);
			}
		}
		
	}
}
