/*****************************************************************************
 *                                                                           *
 *  Copyright (c) 2010 by SANTEC/TUDOR www.santec.tudor.lu                   *
 *                                                                           *
 *                                                                           *
 *  This library is free software; you can redistribute it and/or modify it  *
 *  under the terms of the GNU Lesser General Public License as published    *
 *  by the Free Software Foundation; either version 2 of the License, or     *
 *  (at your option) any later version.                                      *
 *                                                                           *
 *  This software is distributed in the hope that it will be useful, but     *
 *  WITHOUT ANY WARRANTY; without even the implied warranty of               *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
 *  Lesser General Public License for more details.                          *
 *                                                                           *
 *  You should have received a copy of the GNU Lesser General Public         *
 *  License along with this library; if not, write to the Free Software      *
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  *
 *                                                                           *
 *****************************************************************************/
package lu.tudor.santec.gecamed.importexport.gui.importer;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import lu.tudor.santec.gecamed.address.ejb.session.beans.AddressManagerBean;
import lu.tudor.santec.gecamed.address.ejb.session.interfaces.AddressManagerInterface;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.utils.InvoiceWorkflow;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.utils.ChunkedFileSender;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.core.utils.SSNChecker;
import lu.tudor.santec.gecamed.core.utils.entitymapper.XML2EntityMapper;
import lu.tudor.santec.gecamed.core.utils.printing.ireport.UtilFormatter;
import lu.tudor.santec.gecamed.importexport.ejb.session.beans.ImportExportBean;
import lu.tudor.santec.gecamed.importexport.ejb.session.interfaces.ImportExportInterface;
import lu.tudor.santec.gecamed.importexport.utils.PatientAllreadyExitsException;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Allergies;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Antecedents;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Incident;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntry;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntryType;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.MeasurementType;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.MeasurementValue;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientAddress;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientContact;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientDatas;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientFoto;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientMemo;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientPhone;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.PatientStub;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.IncidentManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.PatientAdminBean;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.PatientAdminInterface;
import lu.tudor.santec.gecamed.prescription.ejb.entity.beans.Prescription;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;

/**
 * Mapper class to create and save Patient Entities from the given mapper.
 * 
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 * 
 * @version <br>
 *          $Log: PatientMapper.java,v $
 *          Revision 1.43  2014-02-18 06:43:07  ferring
 *          While exporting file entries they will now be referenced correctly in the ZIP file (path is always separated with a "/", instead of system dependent).
 *          In addition the import checks, if file is available, by switching all '\' to '/'
 *
 *          Revision 1.42  2013-12-27 18:09:25  donak
 *          Cleanup of imports
 *
 *          Revision 1.41  2013-07-22 13:01:36  troth
 *          Monetize all import invoices with the monetize() method.
 *
 *          Revision 1.40  2013-07-15 06:18:36  ferring
 *          logging changed
 *
 *          Revision 1.39  2013-03-14 07:38:04  kutscheid
 *          bugfixes for the import of measurements and incident entries
 *
 *          Revision 1.38  2013-03-13 13:07:12  kutscheid
 *          read spouses and parents
 *
 *          Revision 1.37  2013-03-12 08:37:34  kutscheid
 *          export and import a patient's photo and status
 *
 *          Revision 1.36  2013-03-07 07:47:13  kutscheid
 *          some more fixes for the importer (it now checks if all added files are present)
 *
 *          Revision 1.35  2013-02-22 08:46:50  kutscheid
 *          remove the tupel classes (please redeploy)
 *
 *          Revision 1.34  2013-02-22 07:34:06  kutscheid
 *          remove filesize from the xml as it is anyway read from the file itself
 *          store patient files to a zip file and read form this file again when importing
 *
 *          Revision 1.33  2013-02-20 14:49:44  kutscheid
 *          import files form xml with a displayname
 *
 *          Revision 1.32  2013-02-18 07:22:11  kutscheid
 *          improve the import module
 *          fix a NullPointerException in the LineColorCellRenderer
 *          update the gecamedData xsd file and libs
 *
 *          Revision 1.31  2013-02-14 09:54:54  kutscheid
 *          first commit of the new/remodelled importer implementation
 *          fix some nullpointer exceptions
 *
 *          Revision 1.30 2012-05-23 08:24:55 ferring
 *          warning removed
 * 
 *          Revision 1.29 2012-03-15 08:05:20 ferring
 *          invalid pattern changed
 * 
 *          Revision 1.28 2012-03-07 14:33:52 ferring
 *          check whether there are empty fields imported, that could be removed <br>
 *          Revision 1.27 2012-02-07 10:40:38 ferring <br>
 *          Error message for user improved <br>
 * <br>
 *          Revision 1.26 2012-02-02 15:20:35 ferring <br>
 *          Exception is passed through to react on it. <br>
 * <br>
 *          Revision 1.25 2011-10-05 08:28:59 ferring <br>
 *          iReport printing changed. Beans added, only one formatter used for
 *          everything and reports changed, so that they only use the bean
 *          (util) Parameter <br>
 * <br>
 *          Revision 1.24 2010-07-27 13:30:18 ferring <br>
 *          spelling mistake corrected <br>
 * <br>
 *          Revision 1.23 2010-07-21 11:57:03 troth <br>
 *          add name and address formatter for patient/physician <br>
 * <br>
 *          Revision 1.22 2008-10-02 15:09:41 heinemann <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.21 2008-09-25 09:43:06 heinemann <br>
 *          fixed copyrights <br>
 * <br>
 *          Revision 1.20 2008-07-28 16:11:17 heinemann <br>
 *          saves now the right filename <br>
 * <br>
 *          Revision 1.18 2008-07-25 14:42:42 heinemann <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.16 2008-07-23 15:37:53 heinemann <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.15 2008-07-23 10:00:50 heinemann <br>
 *          removed stand_alone column from prescription and
 *          prescription_revisions table <br>
 * <br>
 *          Revision 1.14 2008-07-23 09:44:53 heinemann <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.13 2008-07-22 15:10:21 hermen <br>
 *          updated mappings <br>
 * <br>
 *          Revision 1.12 2008-07-22 10:06:37 hermen <br>
 *          updated mappings <br>
 * <br>
 *          Revision 1.11 2008-07-22 10:05:57 hermen <br>
 *          updated mappings <br>
 * <br>
 *          Revision 1.10 2008-07-21 14:06:45 hermen <br>
 *          updated mappings <br>
 * <br>
 *          Revision 1.9 2008-07-21 12:00:57 hermen <br>
 *          added statefull session bean for import/export <br>
 * <br>
 *          Revision 1.8 2008-07-18 13:19:51 hermen <br>
 *          updated mapping <br>
 * <br>
 *          Revision 1.7 2008-07-18 09:20:59 hermen <br>
 *          added logging and duplicate checking <br>
 * <br>
 *          Revision 1.4 2008-07-17 12:34:33 hermen <br>
 *          changed to single patient per file <br>
 * <br>
 *          Revision 1.2 2008-07-17 12:14:07 hermen <br>
 *          changed to single patient per file <br>
 * <br>
 *          Revision 1.1 2008-07-16 09:09:04 hermen <br>
 *          updated patient mappings <br>
 * 
 */
public class PatientMapper {

	private static final String PATIENT_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/Patient.properties";

	private static final String PATIENTMEMO_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/PatientMemo.properties";

	private static final String ALLERGIES_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/Allergies.properties";

	private static final String PATIENTDATAS_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/PatientDatas.properties";

	private static final String ANTECEDENTS_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/Antecedents.properties";

	private static final String INCIDENT_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/Incident.properties";

	private static final String INVOICES_MAPPING = "/lu/tudor/santec/gecamed/importexport/mapping/Invoices.properties";

	/**
	 * static logger for this class
	 */
	private static Logger logger = Logger.getLogger(PatientMapper.class.getName());

	private PatientAdminInterface patientAdmin;

	private ImportExportInterface importer;

	private IncidentManager incidentManager;

	private HashMap<String, MeasurementType> measurementTypes = new HashMap<String, MeasurementType>();

	private HashMap<String, IncidentEntryType> incidentEntryTypes = new HashMap<String, IncidentEntryType>();

	private List<Object[]> cachedFileInformation = new ArrayList<Object[]>();

	private Collection<Incident> incidents;

	/**
	 * name & address formatter
	 */
	private static UtilFormatter formatter = new UtilFormatter((AddressManagerInterface) ManagerFactory.getRemote(AddressManagerBean.class));

	static {
		try {
			formatter.setReferenceAddress(MainFrame.getCurrentOffice().getLatestOfficeAddress());
		} catch (Exception e) {
		}
	}

	/**
     * 
     */
	public PatientMapper() {
		/* ================================================== */
		this.patientAdmin = (PatientAdminInterface) ManagerFactory.getRemote(PatientAdminBean.class);
		this.importer = (ImportExportInterface) ManagerFactory.getRemote(ImportExportBean.class);
		this.incidentManager = (IncidentManager) ManagerFactory.getRemote(IncidentManagerBean.class);

		// fetch valid IncidentEntry types
		Collection<IncidentEntryType> entryTypes = incidentManager.getAllEntryTypes();
		for (IncidentEntryType entryType : entryTypes) {
			incidentEntryTypes.put(entryType.toDescriptionString(), entryType);
		}

		// fetch valid MeasurementType types
		Collection<MeasurementType> types = incidentManager.getMeasurementTypes();
		for (MeasurementType measurementType : types) {
			//accept predefined types preferrably to user defined types
			if(measurementType.getAlias().equals("HEI")
					|| measurementType.getAlias().equals("WEI")
					|| measurementType.getAlias().equals("POU")
					|| measurementType.getAlias().equals("SYS")
					|| measurementType.getAlias().equals("DIA")) {
				if(measurementType.isDefault()) {
					measurementTypes.put(measurementType.getAlias(), measurementType);
				}
			} else {
				//accept non standard types for display
				measurementTypes.put(measurementType.getAlias(), measurementType);
			}
			
		}
		/* ================================================== */
	}

	/**
	 * creates a Patient object from the given mapper
	 * 
	 * @param mapper
	 * @return
	 * @throws Exception
	 */
	private Patient getPatient(XML2EntityMapper mapper) throws Exception {
		return (Patient) mapper.mapSingle(Patient.class.getName(), PATIENT_MAPPING);
	}

	/**
	 * creates and saves the PatientMemo objects from the given mapper
	 * 
	 * @param mapper
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void createAndSaveMemos(Patient patient, XML2EntityMapper mapper) throws Exception {
		Collection<PatientMemo> memos = (Collection<PatientMemo>) mapper.mapRepeating(PatientMemo.class.getName(), PATIENTMEMO_MAPPING);
		for (PatientMemo memo : memos) {
			memo.setPatientID(patient.getId());
			memo.setAuthor(null);
			memo.setCreationDate(null);
			importer.saveEntity(memo);
		}
	}

	/**
	 * creates and saves the Allergies objects from the given mapper
	 * 
	 * @param mapper
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void createAndSaveAllergies(Patient patient, XML2EntityMapper mapper) throws Exception {
		Collection<Allergies> allergies = (Collection<Allergies>) mapper.mapRepeating(Allergies.class.getName(), ALLERGIES_MAPPING);
		// save the allergies
		for (Allergies allergy : allergies) {
			allergy.setPatientId(patient.getId());
			allergy.setCreated(null);
			allergy.setCreatedBy(null);
			importer.saveEntity(allergy);
		}
	}

	/**
	 * creates and saves the PatientDatas object from the given mapper
	 * 
	 * @param mapper
	 * @return
	 * @throws Exception
	 */
	private void createAndSavePatientData(Patient patient, XML2EntityMapper mapper) throws Exception {
		PatientDatas patientData = (PatientDatas) mapper.mapSingle(PatientDatas.class.getName(), PATIENTDATAS_MAPPING);
		// save the patientData
		patientData.setPatientId(patient.getId());
		importer.saveEntity(patientData);
	}

	/**
	 * creates and saves the Antecedents objects from the given mapper
	 * 
	 * @param mapper
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void createAndSaveAntecedents(Patient patient, XML2EntityMapper mapper) throws Exception {
		Collection<Antecedents> antecedents = (Collection<Antecedents>) mapper.mapRepeating(Antecedents.class.getName(),
				ANTECEDENTS_MAPPING);
		// save the antecedents
		for (Antecedents antecedent : antecedents) {
			antecedent.setPatientId(patient.getId());
			antecedent.setCreated(null);
			antecedent.setCreatedBy(null);
			importer.saveEntity(antecedent);
		}
	}

	/**
	 * creates and saves the Incidents objects from the given mapper
	 * 
	 * @param xmlFile
	 * @param patient
	 * @param mapper
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void createAndSaveIncidents(File xmlFile, Patient patient, XML2EntityMapper mapper) throws Exception {
		/* ================================================== */
		// Collection<Incident> incidents = (Collection<Incident>)
		// mapper.mapRepeating(Incident.class.getName(), INCIDENT_MAPPING);

		for (Incident incident : incidents) {
			
			// get measurements
			Collection<MeasurementValue> measurements = incident.getMeasurementValues();
			incident.setMeasurementValues(null);

			// get incident entries
			Collection<IncidentEntry> entries = incident.getIncidentEntries();
			incident.setIncidentEntries(null);

			// get prescriptions
			Collection<Prescription> prescriptions = (Collection<Prescription>) incident.getNonAssignable("NAPrescriptions");
			if (prescriptions != null) {
				for (Prescription prescription : prescriptions) {
					// set patient address
					prescription.setPatientAddress(formatter.formatPatientHomeAddress(patient));
					// set physician data
					Physician p = (Physician) prescription.getNonAssignable("NAPhysician");
					if (p == null) {
						throw new Exception("No physician found with the CNS code of the prescription created at "
								+ formatter.formatDate(prescription.getCreationDate(), "yyyy-MM-dd"));
					}
					prescription.setPhysicianId(p.getId());
					prescription.setPhysicianFullName(p.toString());
					prescription.setPhysicianFax(p.getFax());
					prescription.setPhysicianGsm(p.getGsm());
					prescription.setPhysicianPhone(p.getPhoneExtension());
					prescription.setPhysicianSpeciality(p.getSpeciality());
					prescription.setPhysicianId(p.getId());
					prescription.setPhysicianUcmCode(p.getUcmCode());
					prescription.setPhysicianAddress(formatter.formatPhysicianAddress(p));
				}
			}

			// save the incident
			incident.setPatientId(patient.getId());
			incident = (Incident) importer.saveEntity(incident);

			// save the measurements
			if (measurements != null && !measurements.isEmpty()) {
				// create an incident entry and save it
				IncidentEntry measEntry = createIncidentEntry(incident, "measurement");
				for (MeasurementValue measurementValue : measurements) {
					String typeName = measurementValue.getMeasurementType().getName();
					if(typeName.equalsIgnoreCase(lu.gecamed.schemas.ximport.MeasurementDocument.Measurement.MeasurementType.Member.HEIGHT.toString())) {
						typeName = "HEI";
					} else if (typeName.equalsIgnoreCase(lu.gecamed.schemas.ximport.MeasurementDocument.Measurement.MeasurementType.Member.WEIGHT.toString())) {
						typeName = "WEI";
					} else if (typeName.equalsIgnoreCase(lu.gecamed.schemas.ximport.MeasurementDocument.Measurement.MeasurementType.Member.PULSE.toString())) {
						typeName = "POU";
					}
					MeasurementType existingType = measurementTypes.get(typeName);
					if (existingType == null) {
						// create Type
						existingType = measurementValue.getMeasurementType();
						existingType.setDefault(false);
						// save type
						existingType = (MeasurementType) importer.saveEntity(existingType);
						this.measurementTypes.put(existingType.getAlias(), existingType);
					}
					measurementValue.setMeasurementType(existingType);
					measurementValue.setIncident(incident);
					measurementValue.setIncidentEntry(measEntry);
					measurementValue.setIncidentEntryId(measEntry.getId());

					importer.saveEntity(measurementValue);
				}
			}
			/* ------------------------------------------------------- */
			// save the incident entries
			/* ------------------------------------------------------- */
			if (entries != null) {
				for (IncidentEntry entry : entries) {
					/* ------------------------------------------------------- */
					IncidentEntryType existingType = incidentEntryTypes.get(entry.getEntryType().toDescriptionString());
					if (existingType == null) {
						/* ------------------------------------------------------- */
						// create Type
						/* ------------------------------------------------------- */
						existingType = entry.getEntryType();
						existingType = (IncidentEntryType) importer.saveEntity(existingType);
						this.incidentEntryTypes.put(existingType.toDescriptionString(), existingType);
						/* ------------------------------------------------------- */
					}
					entry.setIncident(incident);
					entry.setIncidentId(incident.getId());
					entry.setEntryType(existingType);
					entry.setEntryTypeId(existingType.getId());

					if (entry.getFileName() != null && !"".equals(entry.getFileName().trim())) {

						/* ------------------------------------------------------- */
						// if the original filename is not set, we can abort
						// because
						// something wicked happened before
						/* ------------------------------------------------------- */
						if (entry.getOriginalFilename() == null || "".equals(entry.getOriginalFilename().trim()))
							throw new Exception("found unimported file attaced to entry " + entry.getFileName());
						/* ------------------------------------------------------- */
						// get the temp filename from the entity
						entry = importer.moveTempFile(entry, patient.getId());
						/* ------------------------------------------------------- */
						// get the filename and check if we can access it
						/* ------------------------------------------------------- */
						// store the file information in a global list to remove
						// them in case of an exception. This is our own file
						// transaction
						cachedFileInformation.add(new Object[] {entry.getFileName(), patient.getId()});
						/* ------------------------------------------------------- */
					}
					/* ------------------------------------------------------- */
					// save the entry
					/* ------------------------------------------------------- */
					importer.saveEntity(entry);
					/* ------------------------------------------------------- */
				}
			}

			// save the prescriptions
			if (prescriptions != null) {
				for (Prescription prescription : prescriptions) {
					IncidentEntry entry = createIncidentEntry(incident, "prescription");
					prescription.setIncidentId(incident.getId());
					prescription.setIncidentEntry(entry);
					prescription.setIncidentEntryId(entry.getId());
					importer.saveEntity(prescription);
				}
			}
		}

	}

	private IncidentEntry createIncidentEntry(Incident incident, String type) throws Exception {
		return createIncidentEntry(incident, type, "text/plain");
	}

	private IncidentEntry createIncidentEntry(Incident incident, String type, String mimeType) throws Exception {
		IncidentEntry entry = new IncidentEntry();
		entry.setIncident(incident);
		entry.setIncidentId(incident.getId());
		entry.setEntryType(incidentEntryTypes.get(type.toLowerCase() + "|" + mimeType.toLowerCase()));
		entry.setEntryTypeId(entry.getEntryType().getId());
		/* ------------------------------------------------------- */
		// save the incident entry
		/* ------------------------------------------------------- */
		return (IncidentEntry) importer.saveEntity(entry);
		/* ================================================== */
	}

	/**
	 * Parses the incident.
	 * 
	 * Copies files from the client to a temp file on the server The temp
	 * filenames are stored in the incident entries and must be moved from this
	 * to their right destination when the patient got its id.
	 * 
	 * @param xmlFile
	 * @param patient
	 * @param mapper
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void getFilesAndCopyToServerTemp(File xmlFile, XML2EntityMapper mapper) throws Exception {
		/* ================================================== */
		// get the incidents from the XML file
		this.incidents = (Collection<Incident>) mapper.mapRepeating(Incident.class.getName(), INCIDENT_MAPPING);
		/* ------------------------------------------------------- */
		// we need to iterate over the incidents and get all entries with a file
		// attached
		/* ------------------------------------------------------- */
		for (Incident incident : incidents) {
			/* ------------------------------------------------------- */
			// get incident entries
			Collection<IncidentEntry> entries = incident.getIncidentEntries();
			/* ------------------------------------------------------- */
			// save the incident entries
			/* ------------------------------------------------------- */
			if (entries != null) {
				for (IncidentEntry entry : entries) {
					/* ------------------------------------------------------- */
					if (entry.getFileName() != null && !"".equals(entry.getFileName().trim())) {
						/* ------------------------------------------------------- */
						// get the filename and check if we can access it
						/* ------------------------------------------------------- */
						String fileLocation = (String) entry.getNonAssignable("NAfilelocation");
						File file = new File(xmlFile.getParentFile(), xmlFile.getName().substring(0, xmlFile.getName().lastIndexOf('.')) + ".zip");
						if (file.canRead()) {
							ZipFile zipFile = new ZipFile(file);
							try
							{
								//read the file from the zip
								ZipEntry zipEntry = zipFile.getEntry(fileLocation);
								if (zipEntry == null) {
									fileLocation = fileLocation.replace("\\", "/");
									zipEntry	= zipFile.getEntry(fileLocation);
									if (zipEntry != null)
										entry.setNonAssignable("NAfilelocation", fileLocation);
								}
								
								if (zipEntry != null) {
									/* ------------------------------------------------------- */
									// save files
									/* ------------------------------------------------------- */
									// read the file
									/* ------------------------------------------------------- */
									ChunkedFileSender chunkSender = new ChunkedFileSender(zipFile.getInputStream(zipEntry), zipEntry.getName(),
											null);
	
									chunkSender.startSending();
	
									entry.setOriginalFilename(entry.getFileName());
									entry.setFileName(chunkSender.getFilename());
									entry.setFileSize(file.length());
									/* ------------------------------------------------------- */
									// save the file on the server
									/* ------------------------------------------------------- */
									// entry = importer.saveGecamedFile(b, entry);
									// store the file information in a global list
									// to
									// remove
									// them in case of an exception. This is our own
									// file transaction
									// cachedFileInformation.add(new
									// Tupel<Object>(entry
									// .getFileName(), patient.getId()));
									/* ------------------------------------------------------- */
								}
							} finally {
								zipFile.close();
							}
						} else {
							throw new FileNotFoundException("unable to read File: " + file.getAbsolutePath());
						}
						/* ------------------------------------------------------- */
					}
				}
			}
			/* ------------------------------------------------------- */
		}
		/* ================================================== */
	}

	/**
	 * creates and saves the Invoice objects from the given mapper
	 * 
	 * @param patient
	 * @param mapper
	 */
	private void createAndSaveInvoices(Patient patient, XML2EntityMapper mapper) throws Exception {
		@SuppressWarnings("unchecked")
		Collection<Invoice> invoices = (Collection<Invoice>) mapper.mapRepeating(Invoice.class.getName(), INVOICES_MAPPING);

		for (Invoice invoice : invoices) {
			invoice.setPatient(patient);
			invoice.setState(InvoiceWorkflow.lookupStateByName((String) invoice.getNonAssignable("stateName")));
			// set amount new
			invoice.monetize();
			importer.saveEntity(invoice);
		}
	}

	/**
	 * saves the patient object to the database
	 * 
	 * @param patient
	 * @return
	 * @throws Exception
	 */
	private Patient savePatient(Patient patient) throws Exception {
		
		//use the status of the given patient bean if possible
		Object status = patient.getNonAssignable("NAstatus");
		Integer oldStatus = patient.getStatus();
		if(status != null) {
			String statusString = status.toString();
			if(statusString.equals(Patient.STATES[0])) {
				patient.setStatus(Patient.STATUS_NEW);
			} else if(statusString.equals(Patient.STATES[1])) {
				patient.setStatus(Patient.STATUS_ACTIVE);
			} else if(statusString.equals(Patient.STATES[2])) {
				patient.setStatus(Patient.STATUS_INACTIVE);
			} else if(statusString.equals(Patient.STATES[3])) {
				patient.setStatus(Patient.STATUS_DEPARTED);
			} else {
				patient.setStatus(Patient.STATUS_NEW);
			}
		} else {
			patient.setStatus(Patient.STATUS_NEW);
		}
		
		if ((oldStatus == null && patient.getStatus() != null)
				|| oldStatus != null && !oldStatus.equals(patient.getStatus()))
			logger.info("Status of patient " + (patient.isPersistent() ? patient.getId() : patient.toString()) + " changed to \"" + patient.getStatusName() + "\"");
		//store the picture for the patient
		
		Object photo = patient.getNonAssignable("NAphoto");
		if(photo != null) {
			String photoAsB64 = photo.toString();
			//convert the photo to an image and upload it to the server
			if(photoAsB64.length() > 0) {
				PatientFoto newPhoto = new PatientFoto();
				newPhoto.setData(Base64.decodeBase64(photoAsB64.getBytes("UTF-8")));
				HashSet<PatientFoto> photos = new HashSet<PatientFoto>();
				photos.add(newPhoto);
				patient.setFotos(photos);
			}
		}
		
		Set<Patient> children = patient.getChildren();
		if (children != null) {
			patient.setChildren(new HashSet<Patient>());
			for (Patient child : children) {
				// check if child exists
				Patient existingChild = getPatientBySSN(child.getSocialSecurityNumber());
				if (existingChild != null) {
					patient.getChildren().add(existingChild);
				} else {
					patient.setChildrenName((patient.getChildrenName() != null ? patient.getChildrenName() + "\n" : "")
							+ child.getSurName());
				}
			}
		}

		Set<Patient> parents = patient.getParents();
		if (patient.getParents() != null) {
			patient.setParents(new HashSet<Patient>());
			for (Patient parent : parents) {
				// check if parent exists
				Patient existingParent = getPatientBySSN(parent.getSocialSecurityNumber());
				if (!SSNChecker.PATTERN_EMPTY_SSN.matcher(parent.getSocialSecurityNumber()).matches() && existingParent != null) {
					patient.getParents().add(existingParent);
				} else {
					patient.setParentName((patient.getParentName() != null ? patient.getParentName() + "\n" : "") + parent.getSurName());
				}
			}
		}

		return importer.savePatient(patient);
	}

	private Patient getPatientBySSN(String ssn) {
		List<Patient> patients = this.patientAdmin.checkDuplicateSSN(ssn);
		if (patients != null && patients.size() > 0)
			return patients.get(0);
		else
			return null;
	}

	/**
	 * checks if the Patient is allready in the db
	 * 
	 * @param patient
	 * @return true if he exists, else false
	 * @throws PatientAllreadyExitsException
	 */
	private boolean checkPatientExists(Patient patient) throws PatientAllreadyExitsException {

		if (patient == null)
			return false;

		// ssn is not 00000000000 -> check for duplicate ssn
		if (!SSNChecker.PATTERN_EMPTY_SSN.matcher(patient.getSocialSecurityNumber()).matches()) {
			List<Patient> patients = this.patientAdmin.checkDuplicateSSN(patient.getSocialSecurityNumber());
			// if duplicate ssn -> return true
			if (patients != null) {
				logger.info("Patient " + patient + " ALLREADY EXISTS in the database (duplicate SSN)");
				throw new PatientAllreadyExitsException("Patient " + patient + " ALLREADY EXISTS in the database (duplicate SSN)");
			} else {
				logger.info("Patient " + patient + " DOES NOT exist in the database");
				return false;
			}
		}

		// if no ssn is set -> check for duplicate name
		try {
			Collection<PatientStub> patientStubs = this.patientAdmin.getPatientStubListBySearchString(patient.getFirstName() + " "
					+ patient.getSurName(), "", "", 4);
			if (patientStubs.size() > 0) {
				logger.info("Patient " + patient + " ALLREADY EXISTS in the database (duplicate NAME)");
				return true;
//				throw new PatientAllreadyExitsException("Patient " + patient + " ALLREADY EXISTS in the database (duplicate NAME)");
			}
		} catch (Exception e) {
			logger.log(Level.ERROR, e.getMessage(), e);
		}
		logger.info("Patient " + patient + " DOES NOT exist in the database");
		return false;
	}

	/**
	 * creates an patient object from the given doc and inserts it into the
	 * database.
	 * 
	 * @param doc
	 *            the document containing the patient
	 * @param dryRun
	 *            true for simulation
	 * @param forceImport
	 *            insert patient, even if he exists
	 * @return true on success, else false
	 * @throws Exception
	 *             reports the error
	 */
	public boolean importPatientFromDocument(File xmlFile, Document doc, boolean dryRun, boolean forceImport) throws Exception {
		// get Mapper
		XML2EntityMapper mapper = new XML2EntityMapper(doc);
		// create Patient entity
		Patient patient = getPatient(mapper);
		if (patient == null) {
			// TODO
			throw new Exception("Error Mapping patient...");
		}

		// check if exists
		boolean patientExists = false;
		try {
			patientExists = checkPatientExists(patient);
		} catch (PatientAllreadyExitsException ex) {
			patientExists = true;
		}
		if (forceImport || !patientExists) {
			/* ================================================== */
			cachedFileInformation.clear();
			// if its a dryrun, stop here
			if (dryRun) {
				return true;
			}

			try {
				/* ------------------------------------------------------- */
				// create a new reference to the stateful session bean
				/* ------------------------------------------------------- */
				importer = (ImportExportInterface) ManagerFactory.getStatefulRemote(ImportExportBean.class);
				/* ------------------------------------------------------- */
				// get the files attached to this patient and
				// store them on the server in a temp folder
				// must be done before the transaction because large
				// files can run out of the default timeout of the
				// transaction.
				/* ------------------------------------------------------- */
				getFilesAndCopyToServerTemp(xmlFile, mapper);

				cutRedundant(patient);
				/* ------------------------------------------------------- */
				// start a new transaction
				importer.newTransaction();
				/* ------------------------------------------------------- */
				// save the patient
				/* ------------------------------------------------------- */
				patient = savePatient(patient);
				/* ------------------------------------------------------- */
				// create and save the memos
				/* ------------------------------------------------------- */
				createAndSaveMemos(patient, mapper);
				/* ------------------------------------------------------- */
				// create and save Allergies
				/* ------------------------------------------------------- */
				createAndSaveAllergies(patient, mapper);
				/* ------------------------------------------------------- */
				// create and save ImportantData
				/* ------------------------------------------------------- */
				createAndSavePatientData(patient, mapper);
				/* ------------------------------------------------------- */
				// create and save Antecedents
				/* ------------------------------------------------------- */
				createAndSaveAntecedents(patient, mapper);
				/* ------------------------------------------------------- */
				// create and save Incidents
				/* ------------------------------------------------------- */
				createAndSaveIncidents(xmlFile, patient, mapper);
				/* ------------------------------------------------------- */
				// create and save Invoices
				/* ------------------------------------------------------- */
				createAndSaveInvoices(patient, mapper);
				/* ------------------------------------------------------- */
				// commit the transaction
				/* ------------------------------------------------------- */
				importer.commitTransaction();
				// importer.rollbackTransaction();
				/* ------------------------------------------------------- */
			} catch (Exception e) {
				logger.log(Level.ERROR, "Error while importing patient with the SSN " + patient.getSocialSecurityNumber(), e);
				try {
					/* ------------------------------------------------------- */
					// rollback the transaction
					/* ------------------------------------------------------- */
					importer.rollbackTransaction();
					/* ------------------------------------------------------- */
					// delete saved files
					/* ------------------------------------------------------- */
					for (Object[] t : cachedFileInformation) {
						/*
						 * ------------------------------------------------------
						 * -
						 */
						try {
							importer.deleteGecamedFile(t[0].toString(), (Integer) t[1]);
						} catch (Exception ex) {
							logger.warn("Unable to rollback file " + t[0] + " " + t[1]);
						}
						/*
						 * ------------------------------------------------------
						 * -
						 */
					}
					/* ------------------------------------------------------- */
				} catch (Exception e2) {
					logger.warn(e.getLocalizedMessage());
				}
				throw e;
			}
		} else {
			return false;
		}
		return true;
	}

	private void cutRedundant(Patient patient) {
		Set<PatientAddress> addresses = patient.getAddress();
		Set<PatientContact> contacts = patient.getContacts();
		Set<PatientPhone> phones = patient.getPhones();
		List<Object> toRemove = new LinkedList<Object>();

		String name;
		String country;
		String street;
		String number;
		String zip;

		if(addresses != null) {
			// test & remove addresses
			for (PatientAddress address : addresses) {
				country = address.getCountry();
				street = address.getStreetName();
				number = address.getStreetNumber();
				zip = address.getZip();
	
				if ((country == null || country.length() == 0) && (street == null || street.length() == 0) && (number == null || number.length() == 0)
						&& (zip == null || zip.length() == 0)) {
					toRemove.add(address);
				}
			}
			for (Object o : toRemove) {
				addresses.remove(o);
			}
			toRemove.clear();
		}

		// test & remove contacts
		for (PatientContact contact : contacts) {
			country = contact.getCountry();
			name = contact.getName();
			street = contact.getStreetName();
			number = contact.getStreetNumber();
			zip = contact.getZip();

			if (name == null || "".equals(name) && country == null || "".equals(country) && street == null || "".equals(street)
					&& number == null || "".equals(number) && zip == null || "".equals(zip))
				toRemove.add(contact);
		}
		for (Object o : toRemove)
			contacts.remove(o);
		toRemove.clear();

		// test & remove phone numbers
		for (PatientPhone phone : phones) {
			number = phone.getNumber();
			if (number == null || "".equals(number))
				toRemove.add(phone);
		}
		for (Object o : toRemove)
			phones.remove(o);
	}
}
