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

import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JLabel;
import javax.swing.JPanel;

import lu.luxtrust.cryptoti.CryptoTI_Certificate;
import lu.luxtrust.cryptoti.CryptoTI_Mechanism;
import lu.luxtrust.cryptoti.CryptoTI_Module;
import lu.luxtrust.cryptoti.CryptoTI_PrivateKey;
import lu.luxtrust.cryptoti.CryptoTI_Session;
import lu.luxtrust.cryptoti.CryptoTI_Token;
import lu.luxtrust.cryptoti.CryptoTI_X509;
import lu.luxtrust.cryptoti.exceptions.CryptoTI_Exception;
import lu.luxtrust.gemalto.classic.cryptoti.GTO_CTI_Module;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.esante.ejb.entity.beans.ESanteProperty;
import lu.tudor.santec.gecamed.esante.ejb.session.beans.ESanteConfigManagerBean;
import lu.tudor.santec.gecamed.esante.ejb.session.interfaces.ESanteConfigManager;
import lu.tudor.santec.gecamed.esante.gui.data.Configuration;
import lu.tudor.santec.gecamed.esante.gui.dialogs.LoginDialog;
import lu.tudor.santec.gecamed.esante.gui.webservice.SOAPUtilities;
import lu.tudor.santec.gecamed.esante.gui.webservice.Security;
import lu.tudor.santec.gecamed.esante.gui.webservice.WebserviceConstants;
import lu.tudor.santec.gecamed.usermanagement.gui.settings.UserSettingsPlugin;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Logger;

/**
 * Monitors the user inactivity and removes all saml assertions for accessing the eSanté DSPs when the inactivity exceeded a certain period of time. The
 * Card/Stick watchdog becomes only active
 * 
 * @author donak
 * 
 * @version <br>
 *          $Log: UserInactivityMonitor.java,v $ Revision 1.28 2014-02-04 10:08:40 ferring eSante ID management completed Only those documents will be shown,
 *          that are retrieved by the RSQ
 *
 *          Revision 1.27 2014-01-23 15:04:06 ferring logout button added
 *
 *          Revision 1.26 2014-01-17 11:14:03 ferring System.out out commented
 *
 *          Revision 1.25 2014-01-16 17:25:42 donak Rewrote watchdogs - should have now more convenient and reliable handling. Better not use Timer objects for
 *          watchdog. Proper handling of card removal during operation. Proper handling of smartcards unknown to eSanté platform. Smartcard detection should be
 *          faster - however this watchdog should also be revised (low priority, currently) Revision 1.24 2014-01-15 14:30:09 donak added explicit configuration
 *          of common layer to fix linkingerror on systems other than win32
 * 
 *          Revision 1.23 2014-01-10 13:43:12 ferring purge privileges when pulling out the luxtrust card or when
 * 
 *          Revision 1.22 2014-01-08 17:46:20 donak Improved card reader feedback delay when inserting smartcard Solved issue with storing UserAssertion in
 *          table DSP Added handling for closed/deleted dsps and blacklisted users Created class DspPrivileges for convenient handling of dsp access rights. The
 *          privileges are cached and renewed with each dp saml assertion request. Cached privileges should be used for displaying dsp state and mandate on
 *          screen Fixed exception that was occasionally thrown at smartcard handling in login dialog (hopefully)
 * 
 *          Revision 1.21 2014-01-02 15:59:02 donak suppressed stacktrace logging if smartcard driver was not found
 * 
 *          Revision 1.20 2013-12-27 18:09:26 donak Cleanup of imports
 * 
 *          Revision 1.19 2013-12-27 17:05:05 donak Fixed bug that prevented saml assertions from being cached (hopefully) fixed watchdog issue causing
 *          unpredictable user timeouts
 * 
 *          Revision 1.18 2013-12-24 17:33:15 donak Changed exception handling to focus assertion related exceptions to gerValidSamlAssertionForPatient()
 *          Included exception handling for SmartCardExceptions Fixed bug in UserInactivityMonitor which caused inactive sessions Partially rewrote
 *          SamlAssertion to ease and centralize logic
 * 
 *          Revision 1.17 2013-12-23 18:07:36 donak Stabilized smartcard login - using now the LuxTrust API and should thus be easily extendible to signing
 *          server usage. Revision 1.16 2013-12-17 17:41:49 donak Fix: ITI-41 creationTime tag contains now the real creation time of the document instead of
 *          the submission time Fix: ITI-41 the full oid of the transmitted document is now written to the database (synchronization with document download
 *          should work again) Stabilized smartcard management Revision 1.15 2013-12-13 16:17:08 donak LuxTrust authentication completed
 * 
 *          Revision 1.14 2013-12-13 11:35:48 donak Further adaptions for displaying the data that is printed on the smart card when using LuxTrust
 *          authentication
 * 
 *          Revision 1.13 2013-12-12 18:08:15 donak Added SmartCard functionality to login dialog. Functionality is now integrated with watchdog. Next step will
 *          the integration of the signing class and changing the saml request behavior. Revision 1.12 2013-11-26 08:16:23 ferring Option added for temporary
 *          downloads of CDA documents Property names changed
 * 
 *          Revision 1.11 2013-11-14 18:17:17 donak Fixed versioning issue between JODConverter (Java 1.6) and JBOSS (Java 1.5). Server-side PDF ==> PDF/A
 *          conversion should work now. Fixed IncidentEntry getBinaryContent(). Now a call to this functions always provides the binary content of an incident
 *          entry, if any. function works on client and server side. Implemented function to update incident entry upload status w/o updating all bean
 *          properties. Fixed issue with retrieving default user inactivity logout delay from database
 * 
 *          Revision 1.10 2013-11-12 12:48:22 donak Document upload: * 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. * 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 * Upload of incident type letters has been fixed * upload for docx files has been added
 * 
 *          Upload parameters: * database does now support storage of user dependent properties * 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.
 * 
 *          Inactivity Monitor: * the event mouse wheel scrolling is now taken into account for resetting the logoff timer * the logoff delay is now stored in
 *          the database. If the database does not contain this parameter, it will be created
 * 
 *          General: * 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.
 * 
 *          Revision 1.9 2013-10-31 16:35:21 donak Adjusted formatting of date in default name for prescriptions in upload metadata dialog Additional fixes for
 *          inactivity monitor. Remember: card watchdog becomes only active after getAuthentificationCertificate() was called. (has to be re-called after
 *          watchdog fired). Additional fixes for 2 pass conversion to pdf/a format Revision 1.8 2013-10-31 14:20:26 donak Restructured LT card/stick watchdog:
 *          Presence of a LT driver is now recognized automatically. "Card inserted" watchdog will only be activated if user used a card/stick for eSanté
 *          authentication. Revision 1.7 2013-10-28 17:00:08 donak Added pauseInactivityTimer() and resumeInactivityTimer() to allow pausing the inactivity
 *          watchdog during batch processing. Calls still have to be added to the batch processing.
 * 
 *          Revision 1.6 2013-10-24 15:54:21 donak Added fix for systems where the luxtrust driver is not installed. Should now be detected properly.
 * 
 *          Revision 1.5 2013-10-24 15:11:56 donak Added LuxTrust card watchdog. Harmonized "invalid authentication data" dialogs optimized upload process
 *          Revision 1.4 2013-10-23 14:56:25 donak Inactivity watchdog integration (currently timeout is set to 5 minutes). 30 seconds before automated logout
 *          takes place, a warning message will be displayed on screen. Watchdos is activated as soon as a saml assertion is cached and disabled after it purged
 *          the saml cache and reset the user password for accessing the DSP. Availability of LuxTrust card is currently not checked but will be added to the
 *          next version. Added specifc error message if user logs in with invalid authentication information. Revision 1.3 2013-10-23 10:56:28 donak Enhanced
 *          inactivity watchdog Revision 1.2 2013-10-22 17:09:49 donak User inactivity Watchdog (currently not enabled) Fix for pdf --> pdf/a conversion
 *          Revision 1.1 2013-10-21 15:52:00 donak Class for monitoring user inactivity and empty saml assertion cache as soon as the inactivity exceeds a
 *          certain amount of time. All activities (key and mouse are monitored) So far this class does not contain a warning dialog that pops up after a
 *          certain percentage of the given delay has been passed.
 * 
 * 
 */
public class UserInactivityMonitor implements AWTEventListener {

	private static Logger logger = Logger.getLogger(UserInactivityMonitor.class.getName());

	// monitor all possible user interaction (keyboard and mouse)
	public final static long MONITORED_EVENTS = AWTEvent.KEY_EVENT_MASK + AWTEvent.MOUSE_MOTION_EVENT_MASK + AWTEvent.MOUSE_EVENT_MASK
			+ AWTEvent.MOUSE_WHEEL_EVENT_MASK;

	/** the luxtrust driver that is needed to access LuxTrust cards/sticks in order to search valid certificates */
	private static CryptoTI_Module luxtrustDriver = null;
	/** the luxtrust card contaning the certificates for authentication and signing */
	private static CryptoTI_Token luxtrustCard = null;
	/** the luxtrust certificate used for user authentication */
	private static CryptoTI_X509 authenticationCertificate = null;
	/** reference to the card watchdog thread */
	private static Thread cardWatchdog = null;
	/** reference to the login watchdog thread */
	private static Thread loginWatchdog = null;

	/** flag that stops the watchdog when set to false. */
	private static boolean watchdogActive = false;
	/** flag that allows to pause the inactivity timer. This is needed to avoid timeouts during batch download. */
	private static boolean inactivityTimerPaused = false;
	/** flag that indicates that the login watchdog is running. */
	private static boolean controlLoginDialog = false;

	/** reference to the user inactivity logout thread (the one that displays the countdown) */
	private static Thread timeoutMessage = null;
	/** an active luxtrust smartcard session */
	private static CryptoTI_Session session = null;
	/** reference to the private key for authentication */
	private static CryptoTI_PrivateKey privateKey = null;
	/** reference to the mechanism used for creating the signature */
	private static CryptoTI_Mechanism mechanism = null;
	/** Contains all objects, that shall be notified, when the user is disconnected from eSanté */
	private static List<DisconnectListener> disconnectListeners = new LinkedList<DisconnectListener>();
	/** The username is fix for all luxtrust cards */
	private final static String userName = "User";
	private final static String NAME_MECHAMISM_SHA256 = "CKM_SHA256_RSA_PKCS";
	private final static String NAME_MECHAMISM_SHA1 = "CKM_SHA1_RSA_PKCS";

	/**
	 * delay between last user activity and automated eSanté logout in minutes (saml assertion purge). The real default value is a property in the database (
	 * {@link ESanteProperty#PROP_INACTIVITY_TIMEOUT}) But, if the db entry does not exist yet, this value is used to create it.
	 */
	private static int defaultDelayTillLogout = 5;
	/** The overall period of inactivity <b>in minutes</b> till the user will be logged out by the system */
	private static int delayTillLogout = defaultDelayTillLogout;
	/** the <b>number of seconds</b> the logout warning message will be displayed before the user will be automatically logged out from the eSanté platform */
	private final static int logoutMessageAppearanceDelay = 30;
	/** The delay form the start of user inactivity till the logout warning message wil be displayed */
	private static int countdown = -1;
	/** defines the frequency in ms the watchdog will use to perform its checks */
	private final static int checkFrequency = 100;

	// references to on-screen message elements
	private final static JPanel glassPane = MainFrame.getInstance().getMessageGlassPane();
	private final static JLabel glassLabel = MainFrame.getInstance().getGlassLabel();

	private final static ESanteConfigManager eManager = ESanteConfigManagerBean.getInstance();

	private final static Pattern ownerIdentificationPat = Pattern.compile("SERIALNUMBER=\\d{8}(\\d{12}).+CN=(.+), C=");

	/**
	 * Creates a new token purge timeout by using the default timeout
	 */
	public UserInactivityMonitor() {
		init();
	}

	/**
	 * initializes the timer with the last set logout delay (if none has been set before, the default delay is used)
	 */
	private void init() {
		String timeoutProperty = eManager.getESantePropertyValue(ESanteProperty.PROP_INACTIVITY_TIMEOUT);
		if (timeoutProperty != null) {
			try {
				// guarantee that a positive value is assigned to the timeout delay (at least one minute)
				delayTillLogout = Math.abs(Integer.valueOf(timeoutProperty));
				if (delayTillLogout == 0) {
					delayTillLogout++;
				}
			} catch (NumberFormatException e) {
				// value was found in db but does not represent a valid integer
				eManager.setESanteProperty(ESanteProperty.PROP_INACTIVITY_TIMEOUT, String.valueOf(defaultDelayTillLogout));
			}
		} else {
			// value did not yet exist in the db thus create it by using the default value
			eManager.setESanteProperty(ESanteProperty.PROP_INACTIVITY_TIMEOUT, String.valueOf(defaultDelayTillLogout));
		}

		// assure that there is no watchdog instance running
		setWatchdogActive(false);

		// check if there has already a driver been identified. Otherwise try to find one
		if (UserInactivityMonitor.luxtrustDriver == null && !searchLuxtrustDriver()) {
			// no driver has been found thus disable card watchdog
			logger.info("No LuxTrust driver installed, smartcard watchdog will remain inactive.");
		}
	}

	/**
	 * Activates or deactivates the watchdog
	 * 
	 * @param isActive
	 *            True if the watchdog is active, false otherwise
	 */
	private static void setWatchdogActive(boolean isActive) {

		UserInactivityMonitor.watchdogActive = isActive;
	}
	
	/**
	 * Activates the inactivity monitoring
	 * 
	 * @param tokenPurgeTimeoutInMin
	 *            The inactivity delay till the system will automatically log out the user from the eSanté platform
	 * @return The number of minutes or inactivity after which the user will be automatically logged out from the eSanté platform
	 */
	public int activateMonitor(int tokenPurgeTimeoutInMin) {

		// set a new inactivity delay till user will be logged out
		UserInactivityMonitor.delayTillLogout = tokenPurgeTimeoutInMin;

		return activateMonitor();
	}

	/**
	 * Activates the inactivity monitoring. If the timer was called before, the last provided timeout delay is used. If no timeout delay has been explicitly set
	 * before, the default timeout is used.
	 * 
	 * @return The number of minutes or inactivity after which the user will be automatically logged out from the eSanté platform
	 */
	public int activateMonitor() {
		// initialize the contdown. It might be changed later on if activateMonitor is called with an explicit timeout
		countdown = delayTillLogout * 60;

		// only start the monitor if it is not yet running
		if (!isActive()) {

			UserInactivityMonitor.setWatchdogActive(true);

			// start user inactivity monitor
			spawnActivityWatchdog();

			if (getAuthenticationCertificate() != null) {
				// a luxtrust card is present thus also monitor if someone removes it from the card reader
				checkCardPresence();
			} else {
				logger.info("No LuxTrust card inserted, watchdog will remain inactive.");
			}

			// check if the listener is already registered
			if (Toolkit.getDefaultToolkit().getAWTEventListeners(MONITORED_EVENTS).length == 0) {
				// tell the listener that all user events should be monitored
				Toolkit.getDefaultToolkit().addAWTEventListener(this, MONITORED_EVENTS);
			}
		}

		return delayTillLogout;
	}

	/**
	 * Indicates if the inactivity monitor is currently activated.<br>
	 * <br>
	 * <b>Attention:</b> The inactivity monitor might be active but nevertheless paused. please use {@link #inactivityTimerIsPaused() inactivityTimerIsPaused}
	 * to check if the timer is currently paused.
	 * 
	 * @return True if the monitor is currently active, false otherwise
	 */
	public static boolean isActive() {
		return watchdogActive;
	}

	/**
	 * stop the timer when thread is destroyed
	 */
	public void deactivateMonitor() {
		// indicate that the watchdogs are stopped to end all threads
		UserInactivityMonitor.setWatchdogActive(false);
		// first deactivate the on screen message if present
		UserInactivityMonitor.inactivityTimerPaused = false;
		// needed to stop card watchdog
		UserInactivityMonitor.glassPane.setVisible(false);
		// listeners no longer needed at this level
		Toolkit.getDefaultToolkit().removeAWTEventListener(this);
		if (isLuxTrustDriverInstalled()) {
			// reset smartcard elements
			UserInactivityMonitor.authenticationCertificate = null;
			UserInactivityMonitor.session = null;
			UserInactivityMonitor.privateKey = null;
			UserInactivityMonitor.luxtrustCard = null;
		}

		fireConnectionCanceled();
	}

	/**
	 * Restarts the timeout without a user event. This functionality is e.g. used when GECAMed performs a batch download from an eSanté DSP to avoid having a
	 * timeout during the batch process.
	 * 
	 * @return True if the monitor was still active, false otherwise. If the timer was not active anymore, it is NOT restarted.
	 */
	public boolean touch() {

		UserInactivityMonitor.countdown = UserInactivityMonitor.delayTillLogout * 60;

		// deactivate logout warning message if it has been displayed
		if (UserInactivityMonitor.glassPane.isVisible()) {
			UserInactivityMonitor.glassPane.setVisible(false);
		}

		return isActive();
	}

	/**
	 * Pauses the inactivity timer. No user inactivity timeout action will be fired during the timer is paused
	 */
	public void pauseInactivityTimeout() {

		UserInactivityMonitor.inactivityTimerPaused = true;
	}

	/**
	 * Resumes the user inactivity timer. Due to design decision, the timer will not be resumed with the remaining but the initial interval.
	 */
	public void resumeInactivityTimeout() {

		UserInactivityMonitor.inactivityTimerPaused = false;
	}

	/**
	 * Indicates if the inactivity timer is currently paused. This timer does not affect the luxtrust card watchdog
	 * 
	 * @return True if the timer is paused, false otherwise
	 */
	public static boolean inactivityTimerIsPaused() {
		return UserInactivityMonitor.inactivityTimerPaused;
	}

	/**
	 * When any user activity is detected, restart the timer. "Any user activity" means any mouse or keyboard user action.
	 * 
	 * @param e
	 *            The user event that caused the timer reset
	 */
	public void eventDispatched(AWTEvent e) {
		if (isActive()) {
			// reset the timeout when the watchdog is active
			touch();
		} else {
			// this is triggered when the eSanté logout message is displayed
			UserInactivityMonitor.glassPane.setVisible(false);
			// when the message has been removed from screen the listener can be deactivated
			Toolkit.getDefaultToolkit().removeAWTEventListener(this);
		}
	}

	/**
	 * Purges all assertions and DSP privileges and resets the connection parameter.<br>
	 * No messageis shown.
	 */
	public static void logout() {
		logout(null);
	}

	/**
	 * Purges all assertions and DSP privileges and resets the connection parameter.<br>
	 * The translation of the messageTranslationString will be shown to the user as on screen message, if the messageTranslationString is not empty or
	 * <code>null</code>.
	 * 
	 * @param messageTranslationString
	 *            The message that will be shown or no message, if it is empty String or <code>null</code>.
	 */
	public static void logout(String messageTranslationString) {
		abandonEsanteConnection(messageTranslationString);
		if (!GECAMedUtils.isEmpty(messageTranslationString)) {
			new Thread("eSanté logout Thread") {
				public void run() {
					try {
						Thread.sleep((Integer) MainFrame.getInstance().userSettings.getValue(UserSettingsPlugin.OSD_TIME));
					} catch (InterruptedException e) {
						// thread was interrupted ... never mind
					} finally {
						UserInactivityMonitor.glassPane.setVisible(false);
					}
				}
			}.start();
		}
	}

	/**
	 * Abandons the connections to the eSanté platform and displays an informational message indicating this circumstance.
	 * 
	 * @param message
	 *            The message or the translatrix lookup string (for localized messages) that should be shown on screen
	 */
	private static void abandonEsanteConnection(String message) {

		UserInactivityMonitor.setWatchdogActive(false);
		UserInactivityMonitor.countdown = 0;
		
		// inform the syslog server that the user (was) logged off
		sendLogoutAtnaMessage();
		// empty all caches
		Security.purgeAllAssertions();
		Security.purgeAllPrivileges();
		// also reset the user password
		LoginDialog.resetConfiguration();
		// reset all smartcard references
		UserInactivityMonitor.session = null;
		UserInactivityMonitor.privateKey = null;
		// UserInactivityMonitor.mechanism = null;
		UserInactivityMonitor.luxtrustCard = null;
		UserInactivityMonitor.authenticationCertificate = null;
		// and display an information to the user
		if (!GECAMedUtils.isEmpty(message)) {
			UserInactivityMonitor.glassLabel.setText((Translatrix.getTranslationString(message)));
			MainFrame.getInstance().setGlassPane(UserInactivityMonitor.glassPane);
			UserInactivityMonitor.glassPane.setVisible(true);
		}
		fireConnectionCanceled();
	}

	/**
	 * Sends a logout message to a syslog server indicating the logout of the user
	 */
	private static void sendLogoutAtnaMessage() {
		try {
			// inform the syslog server that the user now logged off from the system
			String t = SOAPUtilities.getTemplateContent(WebserviceConstants.ATNA_STOP_APP);

			t = t.replace(WebserviceConstants.PLACEHOLDER_PROCESS_ID, WebserviceConstants.ATNA_PROCESS_ID);
			t = t.replace(WebserviceConstants.PLACEHOLDER_SENDER_IP, InetAddress.getLocalHost().getHostAddress());
			t = t.replace(WebserviceConstants.PLACEHOLDER_USER_ID, Integer.toString(MainFrame.getCurrentUserId()));

			SOAPUtilities.sendAtnaMessage(t, "LOGOUT");
		} catch (UnknownHostException e) {
			logger.error("Unable to determine IP address of the client system: " + e.getMessage());
		} catch (Exception e) {
			logger.error("Unable to send ATNA user logout message: " + e.getMessage());
		}
	}

	/**
	 * Initiates an instance of the activity watchdog. After a certain period of inactivity of the user ({@link #delayTillLogout}), it warns the user and a
	 * certain number of seconds later ({@link #logoutMessageAppearanceDelay}) logs the user out from the eSanté connection.
	 */
	private void spawnActivityWatchdog() {

		UserInactivityMonitor.timeoutMessage = new Thread("eSanté user inactivity monitor") {

			public void run() {
				// prepare display message
				int totalDelay = 0;
				String[] dynElements = { "" + delayTillLogout, "" + logoutMessageAppearanceDelay };
				try {
					// watchdogActive == true till card is removed from reader
					// countdown > 0 till user remained inactive for a period defined in UserInactivityMonitor.delayTillLogout
					while (isActive() && (countdown > 0)) {

						// show logout warning on screen
						if (countdown == logoutMessageAppearanceDelay) {

							// countdown is not yet expired and no user activity has been detected ==> update message
							dynElements[1] = "" + countdown;
							// set the initial message to the glass pane
							UserInactivityMonitor.glassLabel.setText(Translatrix.getTranslationString("esante.actions.security.inactivityWarning.label",
									dynElements));
							MainFrame.getInstance().setGlassPane(UserInactivityMonitor.glassPane);
							UserInactivityMonitor.glassPane.setVisible(true);

							// update countdown in logout warning
						} else if (countdown < logoutMessageAppearanceDelay) {

							// countdown is not yet expired and no user activity has been detected ==> update message
							dynElements[1] = "" + countdown;
							glassLabel.setText(Translatrix.getTranslationString("esante.actions.security.inactivityWarning.label", dynElements));
						}

						// sleep for (around) one second
						try {
							totalDelay = 0;
							while (isActive() && (totalDelay / 1000 == 0)) {
								// but just take naps and check if thread should be stopped
								Thread.sleep(checkFrequency);
								totalDelay += checkFrequency;
							}
						} catch (Exception e) {
						}

						// only count down when watchdog has not been manually stopped and is not paused
						if (isActive() && !inactivityTimerIsPaused()) {
							// reduce the countdown by one second
							countdown--;

							// if the thread ended because of a timeout, indicate this to the user
							if (countdown <= 0) {
								abandonEsanteConnection("esante.actions.security.inactivityLogout.label");
								logger.info("SAML assertion cache has been purged due to user inactivity of " + delayTillLogout + " minutes.");
							}
							// System.out.println("logout in " + countdown + "second" + ((countdown > 1) ? "s" : ""));
						} 
					}
				} catch (Exception e) {

					logger.warn(e.getMessage());
					glassPane.setVisible(false);
				}
			}
		};
		// fire up the thread
		UserInactivityMonitor.timeoutMessage.start();
	}

	/**
	 * Starts a watchdog thread that monitors if the luxtrust card/stick has been ejected. In this case the saml assertion cache will be purged and the
	 * monitoring ends
	 */
	private void checkCardPresence() {
		UserInactivityMonitor.cardWatchdog = new Thread("eSanté LuxTrust card watchdog") {
			public void run() {

				while (isActive()) {

					try {

						if (!UserInactivityMonitor.luxtrustCard.isAvailable()) {
							
							abandonEsanteConnection("esante.actions.security.cardRemoved.label");
							return;
						}
					} catch (Exception ex) {
						abandonEsanteConnection("esante.actions.security.cardRemoved.label");
						return;
					}

					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						;
					}
				}
			}
		};

		UserInactivityMonitor.cardWatchdog.start();
	}

	/**
	 * Starts a watchdog thread that monitors if the luxtrust card/stick is present or not. The login dialog will be adapted according to the card presence.
	 * (shows a tab to login via card or hides this tab)
	 * 
	 * @param dialog
	 *            Reference to the login dialog
	 */
	public void startLoginMonitor(LoginDialog dialog) {
		// no need to check if no driver is present
		if (UserInactivityMonitor.luxtrustDriver == null) {
			return;
		}
		final LoginDialog loginDialog = dialog;
		UserInactivityMonitor.controlLoginDialog = true;

		UserInactivityMonitor.loginWatchdog = new Thread("eSanté login dialog watchdog") {
			String[] ownerInfo = null;

			public void run() {
				// JPanel scTab = loginDialog.getSmartCardTab();
				boolean lastStateInserted = false;

				if (getAuthenticationCertificate() != null) {
					ownerInfo = getOwnerIdentification();
					loginDialog.setOwnerName(ownerInfo[0]);
					loginDialog.setSerialNumber(ownerInfo[1]);
					loginDialog.showRequestSmartcardMessage(false);
					loginDialog.activateTab(Configuration.LOGIN_SMARTCARD);

					lastStateInserted = true;
				} else {

					loginDialog.setSerialNumber("");
					loginDialog.showRequestSmartcardMessage(true);
				}
				loginDialog.evaluate();
				while (UserInactivityMonitor.controlLoginDialog) {
					try {
						if (lastStateInserted && !UserInactivityMonitor.luxtrustCard.isAvailable()) {
							lastStateInserted = false;
							loginDialog.setSerialNumber("");
							loginDialog.setOwnerName("");
							loginDialog.showRequestSmartcardMessage(true);
							loginDialog.evaluate();
							UserInactivityMonitor.luxtrustCard = null;
							UserInactivityMonitor.authenticationCertificate = null;

						} else if (!lastStateInserted && (UserInactivityMonitor.luxtrustDriver.listTokens().length > 0)) {

							lastStateInserted = true;
							loginDialog.showInitializeSmartcardMessage(true);
							if (UserInactivityMonitor.luxtrustCard == null) {
								getAuthenticationCertificate();
							}

							if (UserInactivityMonitor.luxtrustCard != null) {
								ownerInfo = getOwnerIdentification();
								loginDialog.setOwnerName(ownerInfo[0]);
								loginDialog.setSerialNumber(ownerInfo[1]);
								loginDialog.showInitializeSmartcardMessage(false);
								loginDialog.activateTab(Configuration.LOGIN_SMARTCARD);
								loginDialog.evaluate();
							} else {
								loginDialog.showSmartcardNotReadableMessage(true);
							}

						}
					} catch (CryptoTI_Exception e2) {
						;
					}
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					;
				}
			}
		};

		UserInactivityMonitor.loginWatchdog.start();
	}

	/**
	 * Stops the monitoring of the smartcard for the login dialog
	 */
	public void stopLoginMonitor() {
		UserInactivityMonitor.controlLoginDialog = false;
	}

	/**
	 * Generates an on the fly configuration file that can be fed to the Luxtrust common layer for configuring its behavior
	 * 
	 * @return the configuration as byte array
	 */
	private byte[] generateCommonLayerConfig() {
		StringBuilder clConfig = new StringBuilder();
		// Defines, if multiple login is supported. Possible values are: TRUE || FALSE
		clConfig.append("MULTI_LOGIN = TRUE\n");
		// PIN/PWD format check required ? Possible values are: TRUE || FALSE
		// If TRUE MAX_PIN_LENGTH, MIN_PIN_LENGTH, PIN_FORMAT_MODULE, PIN_FORMAT_SESSION needs to be specified
		clConfig.append("CHECK_PWD_FORMAT = TRUE\n");
		// Defines the minimum PIN length for the session login.
		// Only numbers are allowed. Default value: -1
		clConfig.append("MIN_PIN_LENGTH = 6\n");
		// Defines the maximum PIN length for the session login.
		// Only numbers are allowed. Default value: -1
		// V3 cards support a maximum length of 16
		clConfig.append("MAX_PIN_LENGTH = 16\n");
		// Defines the format of the PIN/PWD which is used for module login.
		// The format is denoted in a regular expression.
		// Default value: NULL
		clConfig.append("PIN_FORMAT_MODULE = NULL\n");
		// Defines the format of the PIN/PWD which is used for session login.
		// The format is denoted in a regular expression.
		// For example: ([0-9]){6,8} ==> PIN has a length of minimum 6 and maximum of 8 digits.
		// For example: ([0-9]){6,16} ==> PIN has a length of minimum 6 and maximum of 16 digits. V3 cards
		clConfig.append("PIN_FORMAT_SESSION = [0-9]{6,16}\n");
		// Defines the lookup level in order to detect the Gemalto Classic Client Library on the user's machine.
		// Possible values are: Optimistic || Pessimistic
		clConfig.append("LOOKUP_WIN_GCC = Pessimistic\n");
		// Defines, if the LuxTrust Global Root certificate SHALL be imported or not. Possible values are: TRUE || FALSE
		// By default, the import is enabled.
		clConfig.append("IMPORT_LTGR = FALSE\n");
		// Defines the supported OS for the Crypto TI Adapter. Possible Values are:
		// Windows32 ==> Windows 32 Bit
		// Windows64 ==> Windows 64 Bit
		// Linux32 ==> Linux 32 Bit
		// Linux64 ==> Linux 64 Bit
		// MAC ==> MAC OS X
		clConfig.append("SUPPORTED_OS = ");
		clConfig.append(determineOs()).append("\n");
		byte[] clConfigBytes = null;
		try {
			clConfigBytes = clConfig.toString().getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			// this exception will never be thrown as UTF-8 is always supported.
			logger.error("UTF8 seems not to be supported.", e);
		}
		// logger.info(clConfig.toString());
		return clConfigBytes;
	}

	/**
	 * Determines the os and also if it is 32 or 64 bit
	 * 
	 * @return The proper string needed for indicating the os to the common layer of luxtrust, which is incapable to determine this by itself -GOSH!!<br/>
	 *         if the os could not be determined, null is returned.
	 */
	private String determineOs() {
		String os = null;

		switch (GECAMedUtils.getOS()) {
		case GECAMedUtils.OS_WINDOWS:
			os = (GECAMedUtils.isJava64Bit()) ? "Windows64" : "Windows32";
			break;
		case GECAMedUtils.OS_MAC:
			os = "MAC";
			break;
		case GECAMedUtils.OS_LINUX:
			os = (GECAMedUtils.isJava64Bit()) ? "Linux64" : "Linux32";
			break;

		default:
			break;
		}

		return os;
	}

	/**
	 * Checks if the luxtrust driver for accessing luxtrust smartcards/sticks has been installed on the system. If this is the case the static variable will be
	 * set for further usage.<br>
	 * <br>
	 * * Reference to the luxtrust driver is stored in {@link #luxtrustDriver}
	 * 
	 * @return True, if a driver has been found, false otherwise
	 */
	private boolean searchLuxtrustDriver() {
		boolean driverFound = false;
		CryptoTI_Module cryptoModule = null;

		try {
			cryptoModule = (CryptoTI_Module) GTO_CTI_Module.class.newInstance();

			if (cryptoModule == null) {
				logger.warn("Unable to create a CTO module for LuxTrust card identification.");
				return false;
			}
			// generate a configuration file on the fly and provide it to the common layer as this layer is unable to detect
			// the operating system and if it is 32 or 64bit - what a pitty...
			InputStream is = new ByteArrayInputStream(generateCommonLayerConfig());

			// prepare the crypto module with data of the last load
			// try
			// {
			// String path = "C:\\Program Files\\Gemalto\\Classic Client\\BIN\\gclib.dll";
			// Object object;
			// Field field;
			//
			// field = cryptoModule.getClass().getDeclaredField("pkcs11Module");
			// field.setAccessible(true);
			// object = field.get(cryptoModule);
			// field = object.getClass().getDeclaredField("pkcs11Module_");
			// field.setAccessible(true);
			// object = field.get(object);
			// field = object.getClass().getDeclaredField("pkcs11ModulePath_");
			// field.setAccessible(true);
			//
			// }
			// catch (Exception e)
			// {
			// logger.warn("Couldn't pre-initialise the crypto module. "
			// + "Therefore the first load might take a bit longer.", e);
			// }

			cryptoModule.initialise(is);
			UserInactivityMonitor.luxtrustDriver = cryptoModule;
			driverFound = true;
		} catch (InstantiationException e1) {
			logger.error("Error during initialisation of CryptoTI", e1);
		} catch (IllegalAccessException e1) {
			logger.error("Error during initialisation of CryptoTI", e1);
		} catch (CryptoTI_Exception e) {
			// occurs if no driver is installed
			// if (e.getCode() == CryptoTI_Exception.LTEC_CTI_NOT_INSTALLED) {
			// logger.info("No LuxTrust card reader driver is installed. SmartCard watchdog will be disabled. (" + e.getCode() + ")");
			// } else {
			// logger.error("Error during initialisation of CryptoTI", e);
			// }
			logger.info("No LuxTrust card reader driver is installed. SmartCard watchdog will be disabled. (" + e.getCode() + ")");
		} catch (UnsatisfiedLinkError ule) {
			// logger.error("Unsatisfied link error - might indicate that driver is not installed. Message is "+ ule.getCause().getMessage());
			ule.printStackTrace();
			// ule.getCause().printStackTrace();
			// card reader driver has not been installed, thus no need to monitor card presence
			// logger.error("Error during initialisation of CryptoTI", ule);
		}
		return driverFound;
	}

	/**
	 * Reads and stores the first authentication certificate that is found on the connected luxtrust cards and also the card itself for ejection detection.<br>
	 * <br>
	 * Reference to the luxtrust card/stick is stored in {@link #luxtrustCard}<br>
	 * Reference to the authentication certificate is stored in {@link #authenticationCertificate}
	 * 
	 * @return True if a luxtrust card/stick with an authentication certificate could be found, false otherwise
	 */
	private boolean searchAuthenticationCertificate() {
		boolean certificateFound = false;
		CryptoTI_Token[] tokenList = null;

		// searching for a card only makes sense, if the driver is present...
		if (UserInactivityMonitor.luxtrustDriver == null) {
			logger.warn("There seems to be no LuxTrust driver installed. Impossible to identify smartcard/usb-stick. SmartCard watchdog will be disabled.");
			return certificateFound;
		}
		// if the card already had been identified, no need to search it again (When the LuxTrust card has been removed, this is set to null by the watchdog in
		// order to cover the case where a wrong LT-card has been inserted)
		if (UserInactivityMonitor.luxtrustCard != null) {
			tokenList = new CryptoTI_Token[] { UserInactivityMonitor.luxtrustCard };
		} else {
			try {
				// get a list of all available cards
				tokenList = UserInactivityMonitor.luxtrustDriver.listTokens();
			} catch (CryptoTI_Exception e2) {
				logger.error("Obtaining a list of all connected cards/sticks failed", e2);
				return certificateFound;
			}
		}
		int numTokens = 0;
		if (tokenList != null) {
			numTokens = tokenList.length;
		} else {
			logger.info("No Smartcards or Signing Sticks inserted. SmartCard watchdog will be disabled.");
			return certificateFound;
		}

		if (numTokens == 0) {
			return certificateFound;
		}

		// scan certificates of all cards
		for (int i = 0; i < numTokens; i++) {

			CryptoTI_Session _session = null;
			try {

				// start a new session for this token
				_session = tokenList[i].openSession();
			} catch (CryptoTI_Exception e1) {
				logger.error("Session Opening Failed!", e1);
			}

			CryptoTI_Certificate[] _certificates = null;
			try {
				_certificates = tokenList[i].listCertificates(_session);
			} catch (CryptoTI_Exception e1) {
				logger.error("Certificate Listing Failed!", e1);
			}

			if (_certificates == null || _certificates.length < 1) {
				logger.warn("No Certificates included!");
				continue;
			}

			CryptoTI_X509 x509Certificate = null;
			for (CryptoTI_Certificate _certificate : _certificates) {

				try {
					x509Certificate = (CryptoTI_X509) _certificate;

					if (!x509Certificate.isKU_NonRepudiation() && x509Certificate.isKU_DigitalSignature()) {
						// certificate found
						UserInactivityMonitor.authenticationCertificate = x509Certificate;
						// also remember the card. This is necessary for card removal detection
						UserInactivityMonitor.luxtrustCard = tokenList[i];
						try {
							// close the session after all certificates have been treated
							tokenList[i].closeSession(_session);
						} catch (CryptoTI_Exception e) {
						}
						return true;
					}
				} catch (CryptoTI_Exception e) {
					logger.error("Unable to identify certificate type", e);
				}

			}/* End Get Certificate Non Repudiation */
			try {
				// close the session after all certificates have been treated
				tokenList[i].closeSession(_session);
			} catch (CryptoTI_Exception e) {
				logger.warn("Unable to close LuxTrust card access session.");
			}
		}/* End SSN retrieving */

		return certificateFound;
	}

	/**
	 * Provides the LuxTrust authentication certificate read from a smartcard or usb-stick provided by LuxTrust.<br>
	 * <br>
	 * If the certificate has not yet been loaded, the system tries to load the desired certificate from a LuxTrust card or stick. Of course this only works if
	 * the corresponding driver is installed and a card or stick is inserted.<br>
	 * <br>
	 * <i><b>Attention:</b> Before the LT card watchdog becomes active, it is crucial that this function is called. Otherwise it remains inactive.<br>
	 * <br>
	 * The watchdog activation is coupled with this call because an active watchdog only makes sense, when the LT card has been used for the login to eSanté.
	 * However for being able to login to eSanté, the getAuthenticationCertificate() function has to be called</i>
	 * 
	 * @return The authentication certificate<br>
	 *         or null if either no LuxTrust driver had been installed, no card or stick is inserted or no inserted media contains the desired certificate
	 */
	public CryptoTI_X509 getAuthenticationCertificate() {
		// if the certificate has not yet been loaded or if it has been purged due to a timeout or card removal, try to reload it first. (of course only if the
		// driver had been installed on the system)
		if ((UserInactivityMonitor.luxtrustDriver != null) && (UserInactivityMonitor.authenticationCertificate == null)) {
			searchAuthenticationCertificate();
		}
		return UserInactivityMonitor.authenticationCertificate;
	}

	/**
	 * Indicates if a LuxTrust driver for reading smartcards/usb-sticks with LuxTrust certificates has been installed on the system
	 * 
	 * @return True if a driver has been found on the system, false otherwise
	 */
	public boolean isLuxTrustDriverInstalled() {
		return UserInactivityMonitor.luxtrustDriver != null;
	}

	/**
	 * Provides access to the luxtrust card
	 * 
	 * @return Reference to the luxtrust card
	 */
	public CryptoTI_Token getLuxtrustCard() {
		if (UserInactivityMonitor.luxtrustCard == null) {
			getAuthenticationCertificate();
		}
		return UserInactivityMonitor.luxtrustCard;
	}

	/**
	 * Indicates if a luxtrust card is currently inserted
	 * 
	 * @return True if a luxtrust card is present, false otherwise
	 */
	public boolean isLuxTrustCardInserted() {
		boolean available = false;
		if (isLuxTrustDriverInstalled()) {
			if (getLuxtrustCard() == null) {
				getAuthenticationCertificate();
			}
			try {
				if (getLuxtrustCard() != null) {
					available = getLuxtrustCard().isAvailable();
				}
			} catch (CryptoTI_Exception e2) {
				;
			}

		}
		return available;
	}

	/**
	 * Provides the serial number of the currently inserted LuxTrust smartCard/stick
	 * 
	 * @return The serial number or null if no valid smartcard/stick is inserted
	 */
	public String getCardSerialNumber() {
		String serial = null;
		if (isLuxTrustDriverInstalled()) {
			try {
				if (getLuxtrustCard() != null) {
					serial = getLuxtrustCard().getInfo().getSerialNumber();
				}
			} catch (CryptoTI_Exception e2) {
				;
			}
		}
		return serial;
	}

	/**
	 * Extracts owner information from the authentication certificate of the LuxTrust card. These are the same information as printed on the card.<br/>
	 * <br/>
	 * The following information is extracted:
	 * <ul>
	 * <li>Full name of the owner</li>
	 * <li>Serial number ob the LuxTrust card</li>
	 * </ul>
	 * 
	 * @return An array containing the information listed above in this very order
	 */
	public String[] getOwnerIdentification() {
		String[] ownerData = null;
		if (isLuxTrustDriverInstalled()) {
			if (getAuthenticationCertificate() != null) {
				try {
					// search for the relevant information in the subject DN information string of the certificate
					Matcher searchOwnerData = ownerIdentificationPat.matcher(getAuthenticationCertificate().getSubjectDN());
					if (searchOwnerData.find()) {
						ownerData = new String[2];
						// extract owner name
						ownerData[0] = searchOwnerData.group(2);
						// extract LuxTrust card id
						ownerData[1] = searchOwnerData.group(1);
					}
				} catch (CryptoTI_Exception e) {
					logger.error("Error while trying to read the LuxTrust card owner information", e);
				}
			}
		}
		return ownerData;
	}

	/**
	 * Provides a signing session
	 * 
	 * @param password
	 *            The password for the private key
	 * @return Reference to the session
	 * @throws Exception
	 *             When the system is unable to start a smartcard session
	 */
	public CryptoTI_Session getSession(String password) throws CryptoTI_Exception {
		// if there is already a session, reuse it
		if (UserInactivityMonitor.session == null) {
			CryptoTI_Session session = getLuxtrustCard().openSession();
			session.login(UserInactivityMonitor.userName, password.toCharArray());
			// just assign it now to avoid a session that has not been logged in
			UserInactivityMonitor.session = session;
		}
		return UserInactivityMonitor.session;
	}

	/**
	 * Loads the private key interface from the smartcard
	 * 
	 * @param password
	 *            The password that is needed to access the private key
	 * @return The private key, if it could be contained or null, if none was found.<br>
	 * <br>
	 *         <i>(A reference to the private key can also be found in {@link AuthenticationSignature#privateKey})</i>
	 * @throws CryptoTI_Exception
	 *             If password was incorrect or smartcard is blocked
	 */
	public CryptoTI_PrivateKey getPrivateKey(String password) throws CryptoTI_Exception {

		// if the private key is already available, it has not to be obtained from smart-card again
		if (UserInactivityMonitor.privateKey == null) {
			CryptoTI_Certificate certificate = getAuthenticationCertificate();
			CryptoTI_Session session = getSession(password);

			// now actually obtain the private key that corresponds to the authentication certificate from the smartcard
			UserInactivityMonitor.privateKey = getLuxtrustCard().getPrivateKey(certificate, session);

			if (UserInactivityMonitor.privateKey == null) {
				logger.error("No Private Key has been found");
			}
		}
		return UserInactivityMonitor.privateKey;
	}

	/**
	 * Gets the best algorithm for insert card
	 * 
	 * @param mechanisms
	 *            List of {@link CryptoTI_Mechanism}
	 * @return best algorithm
	 * @throws CryptoTI_Exception
	 */
	public CryptoTI_Mechanism getBestMechanism() throws CryptoTI_Exception {

		if (UserInactivityMonitor.mechanism == null) {
			CryptoTI_Mechanism[] mechanisms = getLuxtrustCard().listMechanisms();
			if ((mechanisms == null) || (mechanisms.length == 0)) {
				return null;
			}
			String version = luxtrustDriver.getVersion();
			if (version == null || version.equals("")) {
				return null;
			}

			/* Getting version of Gemalto Middleware */
			int firstIndex = version.indexOf("//") + 3;
			int lastIndex = version.lastIndexOf("//");

			String gemaltoVersion = version.substring(firstIndex, lastIndex).trim();

			if (gemaltoVersion.startsWith("5.") || gemaltoVersion.startsWith("Classic Client 5.")) {
				for (CryptoTI_Mechanism mechanism : mechanisms) {
					if (mechanism.getName().equals(NAME_MECHAMISM_SHA1) && mechanism.isSigningEnabled()) {
						UserInactivityMonitor.mechanism = mechanism;
						return UserInactivityMonitor.mechanism;
					}
				}
			} else {
				// for platform authentication only SHA1 is used for the moment
				// for (CryptoTI_Mechanism mechanism : mechanisms) {
				// if (mechanism.getName().equals(NAME_MECHAMISM_SHA256) && mechanism.isSigningEnabled()) {
				// UserInactivityMonitor.mechanism = mechanism;
				// System.out.println("CHOOSEN MECHANISM IS SHA256");
				// return UserInactivityMonitor.mechanism;
				// }
				// }
				if (UserInactivityMonitor.mechanism == null) {
					for (CryptoTI_Mechanism mechanism : mechanisms) {
						if (mechanism.getName().equals(NAME_MECHAMISM_SHA1) && mechanism.isSigningEnabled()) {
							UserInactivityMonitor.mechanism = mechanism;
							return UserInactivityMonitor.mechanism;
						}
					}
				}
			}
		}
		return UserInactivityMonitor.mechanism;
	}

	public static void addDisconnectListener(DisconnectListener l) {
		disconnectListeners.add(l);
	}

	public static boolean removeDisconnectListner(DisconnectListener l) {

		return disconnectListeners.remove(l);
	}

	private static void fireConnectionCanceled() {
		new Thread("Notify: eSante connection canceled") {
			@Override
			public void run() {
				for (DisconnectListener l : disconnectListeners)
					l.disconnected();
			}
		}.start();
	}
}
