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

import java.text.ParseException;
import java.util.Date;

import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.esante.utils.ESanteUtils;
import lu.tudor.santec.i18n.Translatrix;

/**
 * Bean containing information about the status and access privileges of a patient DSP for the requesting user
 * 
 * @author donak
 * 
 * @version <br>
 *          $Log: DspPrivileges.java,v $
 *          Revision 1.12  2014-02-13 15:35:06  donak
 *          Fixed bug in routine detecting if the user is already logged into a patient dap but needs a presence password for uploading a document
 *          Fixed isUploadAllowed() routine and also its usage. This routine now indicates if the user is allowed to upload a document with his current permissions. If you want to determine if the user might be allowed to upload a document by providing a presence password, please use isPresencePasswordAllowed()
 *
 *          Revision 1.10  2014-01-31 16:29:43  donak
 *          Added error dialog when document cannot be uploaded due to invalid access privileges
 *          Fixed bug that prevented saml assertions from being renewed after they exceeded in cache
 *          Fixed bug that prevented documents from being uploaded (gecamed id has not been written to template due to renaming of placeholder)
 *          SMART UPLOAD (TM) feature: Upload option is added to context menu dependent on dsp access permissions and upload success probability calculations
 *          Upload support for images added
 *
 *          Revision 1.9  2014-01-31 10:18:23  ferring
 *          ask for presence password and allow upload conditions changed
 *
 *          Revision 1.8  2014-01-30 18:14:12  donak
 *          upload option will only be offered, if the user possesses the appropriate mandate and access rights to the dsp.
 *
 *          Revision 1.7  2014-01-30 15:43:51  donak
 *          Added function isPresencePasswordAllowed() that indicates if the user should provide a presence password
 *          Added function isAllowedToAccessOwnDocuments() that indicates if the user has access to his own documents for the current dsp
 *
 *          Revision 1.6  2014-01-30 14:20:43  donak
 *          applied valid rules for determining if user is blacklisted
 *          No presence password is asked for blacklisted user but a dsp assertion is obtained as the user still has access to his own documents
 *
 *          Revision 1.5  2014-01-17 08:49:34  ferring
 *          DspPrivileges not stored in DSP anymore
 *
 *          Revision 1.4  2014-01-10 08:21:54  ferring
 *          tranlsations added
 *
 *          Revision 1.3  2014-01-09 16:44:33  ferring
 *          Error handling for SAML assertions changed
 *
 *          Revision 1.2  2014-01-09 14:32:50  ferring
 *          blacklist check set to always false
 *
 *          Revision 1.1  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)
 *
 */
public class DspPrivileges {

	/**
	 * This dsp state is indicated if this bean has not been initialized with a dsp state or the provided state is not known to GECAMed. Should currently never
	 * occur
	 */
	public final static int DSP_STATE_UNKNOWN = -1;
	/**
	 * Based on the opt-out consent model, a DSP is opened immediately after the creation.<br/>
	 * <br/>
	 * In this state, the sharing of medical documents inside the DSP of a patient is enabled. Health professionals with appropriate authorization can provide
	 * and consult documents to/from the DSP.<br/>
	 * <br/>
	 * <i><b>Please be aware:</B>
	 * <ul>
	 * <li>that even if the DSP is open, the user might not be able to access its content because he has been blocked by the owner (patient has set the
	 * physician on his blacklist).</li>
	 * <li>even if this is not the case, the user might need - depending on his mandate - the presence password of the patient for successful dsp access.</li>
	 * </i>
	 * </ul>
	 */
	public final static int DSP_STATE_OPEN = 1;
	/**
	 * This state is reached when a request to close the DSP was made by the patient.<br/>
	 * <br/>
	 * This is usually the case when the patient has revoked the consent for a DSP, after the DSP has already been opened and may contain health information
	 * about the patient.<br/>
	 * <br/>
	 * The data inside the DSP are not accessible, but remain and are not physically removed in this closed state. The DSP can be opened again by initiating the
	 * reactivation operation.
	 */
	public final static int DSP_STATE_CLOSED = 2;
	/**
	 * A deleted DSP is a <b>non-reversible</b> state: it is closed and can’t be opened again.<br/>
	 * <br/>
	 * Only in strict specific cases is it allowed to delete a DSP, and this is managed by the Agence e-Santé (cellule Identito Vigilance). Such cases may
	 * include:
	 * <ul>
	 * <li>After a legally defined period following the death of a patient and if no activity has been recorded</li>
	 * <li>If a duplicate has been unambiguously detected</li>
	 * <li>In certain specific law contexts (right to forget…)</li>
	 * </ul>
	 */
	public final static int DSP_STATE_DELETED = 3;
	// this string is returned by the webservice in case of a successful request
	private final static String requestSuccessIndicator = "Success";
	// needed to parse the validity period of the mandate
	private final static String validityPeriodFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ";
	
	// Defines a list of mandates that don't require the presence password of the patient for obtaining full access to his DSP
	private static final int[] VALID_ACCESS_MANDATES	= new int[]
	{
		WebserviceConstants.MANDATE_CONSULTATION,
		WebserviceConstants.MANDATE_CIRCLE_OF_TRUST,
		WebserviceConstants.MANDATE_REFERING_DOCTOR
	};

	// Please check the DSP connectivity specification of DSP-22 (currently page 42) for further details
	private boolean success = false;
	private String errorMessage = null;
	private String errorDetail = null;
	private boolean authorized = false;
	private String dspId = null;
	private String ehrMode = "Sharing";
	private int ehrState = DspPrivileges.DSP_STATE_UNKNOWN;
	private String[] rightList = null;
	private boolean delegatee = false;
	private String profileId = null;
	private boolean accessToRestricted = false;
	private int mandate = WebserviceConstants.MANDATE_NOT_AUTHORIZED;
	private Date mandateFrom = null;
	private Date mandateTo = null;
	private boolean activated = false;
	private boolean presencePasswordAllowed = false;
	private boolean uploadAllowed = false;
	

	public DspPrivileges(String webServiceResponse) {

		init(webServiceResponse);
	}

	/**
	 * Parses all relevant information of the access rights webservice response into the bean elements.
	 * 
	 * @param webServiceResponse
	 *            The access rights webservice response
	 */
	private void init(String webServiceResponse) {
		// first obtain the status for checking if the request was successful
		try {
			setRequestSuccessful(ESanteUtils.parseXPath("//*[local-name()='code']", webServiceResponse, true).toString());
			if (requestSuccessful()) {
				setAuthorized(ESanteUtils.parseXPath("//*[local-name()='authorized']", webServiceResponse, true).toString());
				setDspId(ESanteUtils.parseXPath("//*[local-name()='resourceId']", webServiceResponse, true).toString());
				setEhrState(ESanteUtils.parseXPath("//*[local-name()='ehrState']", webServiceResponse, true).toString());
				if (isAuthorized()) {
					setRightList(ESanteUtils.parseXPath("//*[local-name()='rightList']", webServiceResponse, true).toString());
					setDelegatee(ESanteUtils.parseXPath("//*[local-name()='delegatee']", webServiceResponse, true).toString());
					setProfileId(ESanteUtils.parseXPath("//*[local-name()='profileId']", webServiceResponse, true).toString());
					setAccessToRestrictedDocuments(ESanteUtils.parseXPath("//*[local-name()='profileLevel']", webServiceResponse, true).toString());
					setMandate(ESanteUtils.parseXPath("//*[local-name()='mandate']", webServiceResponse, true).toString());
					setMandateFrom(ESanteUtils.parseXPath("//*[local-name()='mandateDateFrom']", webServiceResponse, true).toString());
					setMandateTo(ESanteUtils.parseXPath("//*[local-name()='mandateDateTo']", webServiceResponse, true).toString());
				}
				setPresencePasswordAllowed();
				setUploadAllowed();
			} else {
				// obtain message and further details about the error
				setErrorMessage(ESanteUtils.parseXPath("//*[local-name()='message']", webServiceResponse, true).toString());
				setErrorDetail(ESanteUtils.parseXPath("//*[local-name()='detail']", webServiceResponse, true).toString());
			}
		} catch (Exception T) {
			T.printStackTrace();
		}
	}

	/**
	 * Indicates if the request for dsp information was successful.
	 * 
	 * @return True, if the dsp information could be obtained, false otherwise.<br/>
	 * <br/>
	 *         If this function returns false, further information about the error can be obtained via {@link #getErrorMessage()} and {@link #getErrorDetail()}
	 */
	public boolean requestSuccessful() {
		return success;
	}

	private void setRequestSuccessful(String status) {
		this.success = requestSuccessIndicator.equalsIgnoreCase(status);
	}

	/**
	 * Provides the message describing the error that occurred during the dsp status request.<br/>
	 * <br/>
	 * Further details about the error can be obtained via {@link #getErrorDetail()}
	 * 
	 * @return The error message if {@link #getStatus()} returned false, false otherwise.
	 */
	public String getErrorMessage() {
		return errorMessage;
	}

	private void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

	/**
	 * Provides additional details describing the error that occurred during the dsp status request.
	 * 
	 * @return The error details if {@link #getStatus()} returned false, false otherwise.
	 */
	public String getErrorDetail() {
		return errorDetail;
	}

	private void setErrorDetail(String errorDetail) {
		this.errorDetail = errorDetail;
	}

	/**
	 * Indicates if the requesting user is authorized to access this DSP. If the user is not authorized, this might be due to the DSP is closed or deleted.
	 * Further information about the DSP state can be obtained via {@link #getEhrState()}.<br/>
	 * <br/>
	 * However if the DSP state is {@link #DSP_STATE_OPEN}, the user is on the blacklist and therefore not allowed to access the DSP. Information about if the
	 * user is on the blacklist can be obtained via {@link #isOnBlacklist()}<br/>
	 * <br/>
	 * If the user is authorized to access the DSP, further details about the authorization level can be obtained via {@link #getMandate()}
	 * 
	 * @return True, if the user is authorized to access the DSP, false otherwise.
	 */
	public boolean isAuthorized() {
		return authorized;
	}
	
	public boolean isActivated ()
	{
		return activated;
	}

	/**
	 * If the user has no access permissions for the dsp he is on a blacklist for <b>this</b> DSP. This will be indicated by this function.
	 * 
	 * @return True, if the patient set the user on the blacklist for his DSP, false otherwise
	 */
	public boolean isOnBlacklist() {
		// if the user is on black list no access permissions are provided by the server
		return isAuthorized() && (getRightList() == null);
	}

	private void setAuthorized(String authorized) {
		this.authorized = new Boolean(authorized);
	}

	/**
	 * Indicate the id of the patient's DSP
	 * 
	 * @return The DSP id
	 */
	public String getDspId() {
		return dspId;
	}

	private void setDspId(String dspId) {
		this.dspId = dspId;
	}

	/**
	 * The mode of this DSP
	 * 
	 * @return Currently this function always returns "<b><i>Sharing</i></b>".
	 */
	public String getEhrMode() {
		return ehrMode;
	}

//	private void setEhrMode(String ehrMode) {
//		this.ehrMode = ehrMode;
//	}

	/**
	 * Provides the status of the DSP.<br/>
	 * <br/>
	 * The following states are currently supported:
	 * <ul>
	 * <li>{@link #DSP_STATE_OPEN}</li>
	 * <li>{@link #DSP_STATE_CLOSED}</li>
	 * <li>{@link #DSP_STATE_DELETED}</li>
	 * </ul>
	 * 
	 * @return The status of the DSP
	 */
	public int getEhrState() {
		return ehrState;
	}

	private void setEhrState(String state) {
		switch (state.toLowerCase().charAt(0)) {
		case 'a':
			activated = true;
		case 'p':
			ehrState = DspPrivileges.DSP_STATE_OPEN;
			break;
			
		case 'd':
			ehrState = DspPrivileges.DSP_STATE_CLOSED;
			break;
			
		case 'f':
			ehrState = DspPrivileges.DSP_STATE_DELETED;
			break;
			
		default:
			// if there is any other value, indicate that the state is unknown
			ehrState = DspPrivileges.DSP_STATE_UNKNOWN;
		}
	}

	/**
	 * Provides the list of permissions for accessing the content of the DSP.<br/>
	 * <br/>
	 * <i><b>At the moment, the meaning of the different permissions is not completely clear.<br/>
	 * Thus no preprocessing of the permission list is done, yet. Only the raw strings are provided.</b></i>
	 * 
	 * @return An array containing the different permissions or null if the user is on the blacklist
	 */
	public String[] getRightList() {
		return rightList;
	}

	private void setRightList(String rightList) {
		if (rightList.trim().length() > 0) {
			this.rightList = rightList.split(";");
		}
	}

	/**
	 * indicates if the user is a delegatee of the patient. This is probably virtually never the case for a physician.
	 * 
	 * @return True, if the user is a delegatee of the patient, false otherwise
	 */
	public boolean isDelegatee() {
		return delegatee;
	}

	private void setDelegatee(String delegatee) {
		// if the value is '1', the user is a delegatee, otherwise not
		this.delegatee = delegatee.charAt(0) == 49;
	}

	/**
	 * Provides the profile id.<br/>
	 * <i><b>So far it is <u>not</u> clear what is meant by this parameter</b></i>
	 * 
	 * @return The profile id
	 */
	public String getProfileId() {
		return profileId;
	}

	private void setProfileId(String profileId) {
		this.profileId = profileId;
	}

	/**
	 * Indicates if the user is allowed to access restricted documents of the patient.
	 * 
	 * @return True, if restricted access is allowed to access documents of the patient that possess the status "<i><b>restricted</b></i>", false otherwise
	 */
	public boolean hasAccessToRestrictedDocuments() {
		return accessToRestricted;
	}

	private void setAccessToRestrictedDocuments(String accessToRestricted) {
		// if the value is '1', the user is allowed to access restricted documents of the patient
		this.accessToRestricted = accessToRestricted.charAt(0) == 49;
	}

	/**
	 * Provides the mandate, the user possesses for the DSP.<br/>
	 * <br/>
	 * The following mandates are currently available:
	 * <ul>
	 * <li>1 : Consultation</li>
	 * <li>2 : Medical circle of trust</li>
	 * <li>3 : Legal representatives</li>
	 * <li>4 : Owner</li>
	 * <li>5 : Author</li>
	 * <li>6 : Institution</li>
	 * <li>7 : Emergency support</li>
	 * <li>8 : Health network</li>
	 * <li>9 : DSP administrator</li>
	 * <li>10 : DSP creator</li>
	 * <li>11 : XDS community (XCA framework : not used yet)</li>
	 * <li>12 : Person of trust</li>
	 * <li>13 : Médecin referent (referring physician)</li>
	 * </ul>
	 * If the user possesses any other mandate than {@link WebserviceConstants#MANDATE_CONSULTATION Consultation},
	 * {@link WebserviceConstants#MANDATE_CIRCLE_OF_TRUST Medical circle of trust}, or {@link WebserviceConstants#MANDATE_REFERING_DOCTOR Médecin referent}, an
	 * authentication request for accessing the DSP must contain a presence password (DSP-12) for gaining write access.
	 * 
	 * @return The mandate of the user for the patient's DSP
	 */
	public int getMandate() {
		return mandate;
	}

	private void setMandate(String mandate) {
		this.mandate = Integer.parseInt(mandate);
	}

	/**
	 * Provides the begin of the validity period of the current mandate.
	 * 
	 * @return The date and time from which on the mandate is valid
	 */
	public Date getMandateFrom() {
		return mandateFrom;
	}

	private void setMandateFrom(String mandateFrom) {
		try {
			this.mandateFrom = GECAMedUtils.getDateFormatter(validityPeriodFormat).parse(changeIso8601ToRfc822(mandateFrom));
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Provides the end of the validity period of the current mandate.
	 * 
	 * @return The date and time till which on the mandate is valid <b>or null if the validity of the current mandate is not limited</b>.
	 */
	public Date getMandateTo() {
		return mandateTo;
	}

	private void setMandateTo(String mandateTo) {
		if (mandateTo.length() != 0) {
			try {
				this.mandateTo = GECAMedUtils.getDateFormatter(validityPeriodFormat).parse(changeIso8601ToRfc822(mandateTo));
			} catch (ParseException e) {
			}
		}
	}

	/**
	 * Indicates if it makes sense to provide a presence password with the authentication request
	 * 
	 * @return <code>true</code>, if providing a presence password would extend the access permissions, false otherwise
	 */
	public boolean isPresencePasswordAllowed() 
	{
		return this.presencePasswordAllowed;
	}
	
	
	public void setPresencePasswordAllowed ()
	{
		/* First of all the eSante user must not be black listed and the DSP must be open, because it doesn't make sense 
		 * to ask for a presence password of a closed or deleted DSP.
		 * If the eSante user is not authorised, he never had contact with the patient and has to enter the presence 
		 * password in any case. 
		 * If the eSante user is authorised and has a valid mandate, he doesn't need the presence password, because 
		 * he already has enough right. If not, he can enter the presence password to get more rights.
		 */
		this.presencePasswordAllowed = !isOnBlacklist() 
				&& (getEhrState() == DspPrivileges.DSP_STATE_OPEN)
				&& (!isAuthorized() 
						|| !GECAMedUtils.contains(VALID_ACCESS_MANDATES, getMandate()));
	}
	
	/**
	 * Indicates if the user has sufficient permissions to upload documents to the dsp
	 * 
	 * @return True, the user is allowed to upload documents, false otherwise
	 */
	public boolean isUploadAllowed() {
		return uploadAllowed;
	}

	public void setUploadAllowed() {
		// first of all the user must not be black listed and the dsp has to be open.
		// Besides that he has to be authorized to access the dsp and he must possess a mandate that is valid for uploading
		this.uploadAllowed = !isOnBlacklist() 
				&& (getEhrState() == DspPrivileges.DSP_STATE_OPEN)
				&& isAuthorized()
				&& GECAMedUtils.contains(VALID_ACCESS_MANDATES, getMandate());
	}

	
	/**
	 * Indicates if the user is allowed to access his own documents in the patient DSP
	 * 
	 * @return True, if own documents are accessible (if existing), false otherwise
	 */
	public boolean isAllowedToAccessOwnDocuments() {
		return mandate != WebserviceConstants.MANDATE_NOT_AUTHORIZED;
	}
	
	/**
	 * Changes an ISO8601 time zone to an RFC822 time zone representation as Java6 does not support ISO 8601 time zones
	 * 
	 * @param dateString
	 *            Date string in ISO8601 format
	 * @return Date string in RFC822 format
	 */
	private String changeIso8601ToRfc822(String dateString) {
		if (dateString.length() == 0) {
			return dateString;
		}
		// This is a really nasty hack but unfortunately JDK6 does not support ISO8601 time zones (introduced with java7)
		return dateString.substring(0, dateString.length() - 3) + dateString.substring(dateString.length() - 2);
	}

	/**
	 * Generates an ISO8601 string representation of a date. This hackish helper is needed as Java6 does not support this date format
	 * 
	 * @param dateString
	 *            The date of which a string representation in ISO8601 format should be generated
	 * @return Date string in ISO8601 format
	 */
	private String createISO8601DateString(Date date) {
		if (date == null) {
			return "";
		}
		String dateString = GECAMedUtils.getDateFormatter(validityPeriodFormat).format(date);
		// This is a really nasty hack but unfortunately JDK6 does not support ISO8601 time zones (introduced with java7)
		return dateString.substring(0, dateString.length() - 2) + ":" + dateString.substring(dateString.length() - 2);
	}

	/**
	 * Generates a string representation of the dsp access rights
	 */
	public String toString() {
		StringBuilder displayText = new StringBuilder();
		displayText.append("Request was successful:\t\t").append(requestSuccessful());
		if (requestSuccessful()) {
			displayText
					.append("\nUser is authorized:\t\t")
					.append(isAuthorized())
					.append("\nMandate:\t\t\t")
					.append(Translatrix.getTranslationString("esante.eSanteTab.mandates."+getMandate()))
					.append("\nUser is blacklisted:\t\t")
					.append(isOnBlacklist())
					.append("\nDsp id:\t\t\t\t")
					.append(getDspId())
					.append("\nDsp mode: \t\t\t")
					.append(getEhrMode())
					.append("\nDsp State: \t\t\t")
					.append((getEhrState() == DSP_STATE_OPEN) ? "OPEN" : 
							(getEhrState() == DSP_STATE_CLOSED) ? "CLOSED" : 
							(getEhrState() == DSP_STATE_DELETED) ? "DELETED" : 
							"UNKNOWN");
			if (isAuthorized()&& (getRightList()!=null)) {
				displayText.append("\nAccess permissions:\t\t");
				for (int counter = 0; counter < getRightList().length; counter++) {
					displayText.append(getRightList()[counter]).append(", ");
				}
				displayText.append("\nUser is delegatee:\t\t").append(isDelegatee()).append("\nUser profile id:\t\t").append(getProfileId())
						.append("\nAccess to restricted docs:\t").append(hasAccessToRestrictedDocuments()).append("\nUser mandate:\t\t\t").append(getMandate())
						.append("\nMandate is valid from:\t\t").append(createISO8601DateString(getMandateFrom())).append("\nMandate is valid until:\t\t")
						.append((getMandateTo() == null) ? "UNLIMITED" : createISO8601DateString(getMandateTo()));
			}
		} else {
			displayText.append("\nError message:\t\t").append(getErrorMessage()).append("\nError message details:\t\t").append(getErrorDetail());
		}
		return displayText.toString();
	}

}
