/* To change this template, choose Tools | Templates
 * and open the template in the editor. */
package lu.tudor.santec.gecamed.esante.ejb.entity.beans;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import lu.tudor.santec.gecamed.address.ejb.entity.beans.GECAMedAddressBean;
import lu.tudor.santec.gecamed.core.ejb.entity.beans.GECAMedEntityBean;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientAddress;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * @author jens.ferring(at)tudor.lu
 * 
 * Based on infomed client sources.
 */
@NamedQueries
({
	@NamedQuery(name = Dsp.GET_DSP_BY_PATIENT_ID,	query = "SELECT OBJECT(o) FROM Dsp o " + 
															"WHERE o.patientId = :patientId " + 
															"ORDER BY o.id DESC"),
	@NamedQuery(name = Dsp.UPDATE_DSP_OID,			query="UPDATE Dsp "
														+ "SET dspOid = :dspOid "
														+ "WHERE id = :dspId "),
	@NamedQuery(name = Dsp.UPDATE_PATIENT_LUXEMBOURG_ID, 
													query = "UPDATE Patient p " + 
															"SET p.idLuxembourg = :luxembourgId " + 
															"WHERE id = :patientId")
})

@Entity
@Table (schema = "esante", name = "dsp")
public class Dsp extends GECAMedEntityBean
{
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */
	
	private static final long	serialVersionUID	= 1L;
	
	private static final int ADDRESS_NUMBER		= 0;
	private static final int ADDRESS_STREET		= 1;
	private static final int ADDRESS_INFO		= 2;
	private static final int ADDRESS_ZIP		= 3;
	private static final int ADDRESS_CITY		= 4;
	private static final int ADDRESS_COUNTRY	= 5;
	
	
	public static final String	GET_DSP_BY_PATIENT_ID			= "getDspByPatientId";
	public static final String	UPDATE_DSP_OID					= "updateDspOid";
	public static final String	UPDATE_PATIENT_LUXEMBOURG_ID	= "updatePatientLuxembourgId";
	
	
	public static final String	GENDER_MALE		= "M";
	public static final String	GENDER_FEMALE	= "F";
	public static final String	GENDER_OTHER	= "U";
	
	public static final String	DSP_DATE_FORMAT		= "yyyyMMdd";
	public static final String	INTER_DATE_FROMAT	= "yyyy-MM-dd";
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	/* ---------------------------------------- */
	// Database members
	/* ---------------------------------------- */
	
	private Integer		patientId;
	private String		dspOid;
	private BigDecimal	matchRate;
	private String		givenName;
	private String		commonName;
	private String		familyName;
	private Date		birthDate;
	private String		sex;
	private String		ehrStatus;
	private String		dspAddressString;
	
	
	/* ---------------------------------------- */
	// TRANSIENT MEMBERS
	/* ---------------------------------------- */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(Dsp.class.getName());
	
	
	private String					shortDspOid;
	
	private String					matchRatePercentage;
	
	private List<CdaDocument>		documents	= new ArrayList<CdaDocument>();
	
	private transient String		ssn;
	
	private transient Patient		patient;
	
	private transient List<PatientAddress>	addresses;
	
	private transient String		addressesString;
	
	
	
	/* ======================================== */
	// CONSTRUCTORS
	/* ======================================== */
	
	public Dsp ()
	{
		// this is the default constructor for Hibernate
	}
	
	
	/**
	 * This constructor takes the information of the patient and transfers 
	 * it to the DSP.<br> 
	 * It is used only to load the matching patients.
	 * 
	 * @param patient The patient to match with
	 */
	public Dsp (Patient patient)
	{
		this.patient	= patient;
		this.patientId	= patient.getId();
		this.dspOid		= patient.getIdLuxembourg();
		this.givenName	= patient.getFirstName();
		this.familyName	= patient.getMaidenName();
		this.commonName	= patient.getSurName();
		this.birthDate	= patient.getBirthDate();
		this.ssn		= patient.getSocialSecurityNumber();
	}
	
	
	public Dsp (Node node)
	{
		NodeList nl = node.getChildNodes();
		Node n;
		for (int i = 0; i < nl.getLength(); i++)
		{
			n = nl.item(i);
			
			if (n.getNodeName().equals("id"))
			{
				this.dspOid = n.getTextContent();
			}
			else if (n.getNodeName().equals("matchRate"))
			{
				setMatchRate(n.getTextContent());
			}
			else if (n.getNodeName().equals("familyNameBR"))	// birth register name
			{
				this.familyName = n.getTextContent();
			}
			else if (n.getNodeName().equals("familyNameSP"))	// spouse name
			{
				this.commonName = n.getTextContent();
			}
			else if (n.getNodeName().equals("givenName"))
			{
				this.givenName = n.getTextContent();
			}
			else if (n.getNodeName().equals("birthDate"))
			{
				setBirthDate(n.getTextContent(), DSP_DATE_FORMAT);
			}
			else if (n.getNodeName().equals("sex"))
			{
				this.sex = n.getTextContent();
			}
			else if (n.getNodeName().equals("ehrState"))
			{
				this.ehrStatus = n.getTextContent();
			}
			else if (n.getNodeName().equals("addressList"))
			{
				readAddresses(n.getChildNodes());
			}
		}
	}
	
	
	
	/* ======================================== */
	// GETTER & SETTER
	/* ======================================== */
	
	@Column(name = "patient_id")
	public Integer getPatientId ()
	{
		return patientId;
	}
	
	
	public void setPatientId (Integer patientId)
	{
		this.patientId = patientId;
	}
	
	
	@Column(name = "dsp_oid")
	public String getDspOid ()
	{
		return dspOid;
	}
	
	
	public void setDspOid (String oid)
	{
		this.dspOid = oid;
	}
	
	
	@Column(name = "match_rate")
	public BigDecimal getMatchRate ()
	{
		return matchRate;
	}
	
	
	public void setMatchRate (BigDecimal matchRate)
	{
		this.matchRate = matchRate;
		if (this.matchRate != null)
			this.matchRate.setScale(2, RoundingMode.HALF_UP);
		this.matchRatePercentage = null;
	}
	
	
	/**
	 * @return The birth name of the patient
	 */
	@Column(name = "birthname")
	public String getFamilyName ()
	{
		return familyName;
	}
	
	
	/**
	 * @param name The patient's birth name
	 */
	public void setFamilyName (String name)
	{
		this.familyName = name;
	}
	
	
	/**
	 * @return The current surname of the patient
	 */
	@Column(name = "surname")
	public String getCommonName ()
	{
		return commonName;
	}
	
	
	/**
	 * @param name The patient's current surname
	 */
	public void setCommonName (String name)
	{
		this.commonName = name;
	}
	
	
	@Column(name = "givenname")
	public String getGivenName ()
	{
		return givenName;
	}
	
	
	public void setGivenName (String name)
	{
		this.givenName = name;
	}
	
	
	@Column(name = "birthdate")
	public Date getBirthDate ()
	{
		return birthDate;
	}
	
	
	public void setBirthDate (Date birthDate)
	{
		this.birthDate = birthDate;
	}
	
	
	@Column(name = "sex")
	public String getSex ()
	{
		return sex;
	}
	
	
	public void setSex (String sex)
	{
		this.sex = sex;
	}
	
	
	@Column(name = "ehr_status")
	public String getEhrStatus ()
	{
		return ehrStatus;
	}
	
	
	public void setEhrStatus (String status)
	{
		this.ehrStatus = status;
	}
	
	
	@Column(name = "address")
	public String getDspAddressString ()
	{
		return dspAddressString;
	}
	
	
	public void setDspAddressString (String address)
	{
		dspAddressString = address;
		addresses		= null;
		addressesString	= null;
	}
	
	
	/* ---------------------------------------- */
	// TRANSIENT GETTER & SETTER
	/* ---------------------------------------- */
	
	@Transient
	public Patient getPatient ()
	{
		return patient;
	}
	
	
	@Transient
	public void setPatient (Patient patient)
	{
		if (patient != null)
			patientId = patient.getId();
		
		this.patient = patient;
	}
	
	
	@Transient
	public String getShortDspOid ()
	{
		if (shortDspOid == null && dspOid != null)
		{
			int index	= dspOid.indexOf("^");
			if (index > 0)
				shortDspOid	= dspOid.substring(0, index);
		}
		
		return shortDspOid;
	}
	
	
	@Transient
	public void setMatchRate (String matchRate)
	{
		if (matchRate == null)
		{
			this.matchRate = null;
			return;
		}
		
		try
		{
			this.matchRate = new BigDecimal(matchRate.replace(',', '.'));
			this.matchRate.setScale(2, RoundingMode.HALF_UP);
			this.matchRatePercentage = null;
		}
		catch (NumberFormatException e)
		{
			logger.error(matchRate+" is not a valid number format for a BigDecimal", e);
		}
	}
	
	
	@Transient
	public String getMatchRateString ()
	{
		if (matchRatePercentage == null || matchRate != null)
		{
			matchRatePercentage = new StringBuilder()
					.append(matchRate.toString())
					.append(" %")
					.toString()
					.replace('.', ',');
		}
		
		return matchRatePercentage;
	}
	
	
	/**
	 * <b>May NOT be called on the server!</b><br>
	 * <br>
	 * Defines the birth date of this DSP.
	 * 
	 * @param birthDate A date as string in the form, specified by the date format
	 * @param dateFormat The date format of the birth date.
	 */
	@Transient
	public void setBirthDate (String birthDate, String dateFormat)
	{
		if (birthDate != null)
		{
			DateFormat dateFormatter	= GECAMedUtils.getDateFormatter(dateFormat);
			try
			{
				this.birthDate	= dateFormatter.parse(birthDate);
			}
			catch (ParseException e)
			{
				logger.error(e.getMessage(), e);
			}
		}
		else
		{
			this.birthDate	= null;
		}
	}
	
	
	@Transient
	public String getBirthDateString (String dateFormat)
	{
		if (birthDate == null)
			return null;
		
		try
		{
			return GECAMedUtils.getDateFormatter(dateFormat).format(birthDate);
		}
		catch (Exception e)
		{
			logger.error("Error while formatting the brith date as String.", e);
			return null;
		}
	}
	
	
	@Transient
	public List<PatientAddress> getAddressList ()
	{
		if (addresses == null && dspAddressString != null)
		{
			String[]		addressesArray	= dspAddressString.split("\n");
			String[]		addressDataArray;
			PatientAddress	patientAddress;
			
			addresses = new ArrayList<PatientAddress>(addressesArray.length);
			for (String addressDataString : addressesArray)
			{
				addressDataArray = addressDataString.split(";");
				patientAddress = new PatientAddress();
				patientAddress.setPatientId(null);
				
				// read: street number
				if (addressDataArray.length > ADDRESS_NUMBER && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_NUMBER]))
					patientAddress.setStreetNumber(addressDataArray[ADDRESS_NUMBER]);
				// read: street name and more info
				if (addressDataArray.length > ADDRESS_STREET && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_STREET]))
				{
					if (addressDataArray.length > ADDRESS_INFO && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_INFO]))
						patientAddress.setStreetName(addressDataArray[ADDRESS_INFO]+" ("+addressDataArray[ADDRESS_STREET]+")");
					else
						patientAddress.setStreetName(addressDataArray[ADDRESS_STREET]);
				}
				else if (addressDataArray.length > ADDRESS_INFO && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_INFO]))
				{
					patientAddress.setStreetName(addressDataArray[ADDRESS_INFO]);
				}
				// read: ZIP
				if (addressDataArray.length > ADDRESS_ZIP && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_ZIP]))
					patientAddress.setZip(addressDataArray[ADDRESS_ZIP]);
				// read: city
				if (addressDataArray.length > ADDRESS_CITY && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_CITY]))
					patientAddress.setLocality(addressDataArray[ADDRESS_CITY]);
				// read: country
				if (addressDataArray.length > ADDRESS_COUNTRY && !GECAMedUtils.isEmpty(addressDataArray[ADDRESS_COUNTRY]))
					patientAddress.setCountry(addressDataArray[ADDRESS_COUNTRY]);
				addresses.add(patientAddress);
			}
		}
		
		return addresses;
	}
	
	
	@Transient
	public String getAddressesString ()
	{
		if (addressesString == null && dspAddressString != null)
			addressesString = GECAMedAddressBean.printAddresses(getAddressList(), false);
		
		return addressesString;
	}
	
	
	/**
	 * @return The trimmed combination of surname and birth name, separated with a blank 
	 * or only the surname or birth name, if the other is <code>null</code> or an empty string
	 * or <code>null</code> if both are null or empty strings.
	 */
	@Transient
	public String getFullFamilyName ()
	{
		if (commonName != null && commonName.trim().length() > 0)
		{
			if (familyName == null || familyName.trim().length() == 0)
			{
				// only the surname is filled out
				return commonName.trim();
			} 
			else
			{
				return new StringBuilder(commonName.length() + familyName.length()+1)
						.append(commonName.trim())
						.append(" ")
						.append(familyName.trim())
						.toString()
						.trim();
			}
		}
		else if (familyName != null && familyName.length() > 0)
		{
			return familyName.trim();
		}	
		else
		{
			return null;
		}
	}
	

	@Transient
	public List<CdaDocument> getDocuments ()
	{
		return documents;
	}
	

	@Transient
	public void setDocuments (Collection<CdaDocument> documents)
	{
		this.documents.clear();
		if (documents != null)
			this.documents.addAll(documents);
	}
	
	
	@Transient
	public void setDocument (int index, CdaDocument document)
	{
		this.documents.set(index, document);
	}
	
	
	@Transient
	public String getSsn ()
	{
		return this.ssn;
	}
	
	
	@Transient
	public void setSsn (String socialSecurityNumber)
	{
		this.ssn = socialSecurityNumber;
	}
	
	
	
	/* ======================================== */
	// HELP METHODS
	/* ======================================== */
	
	@Transient
	private void readAddresses (NodeList addressNodeList)
	{
		Node			addressNode;
		StringBuilder	addressBuilder = new StringBuilder();
		
		
		for (int i = 0; i < addressNodeList.getLength(); i++)
		{
			addressNode = addressNodeList.item(i);
			if (addressNode.getNodeName().equals("address"))
			{
				addressBuilder.append(addressNode.getTextContent())
						.append("\n");
			}
		}
		
		dspAddressString = addressBuilder.toString().trim();
		addresses		= null;
		addressesString	= null;
	}
}