/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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 (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.patient.gui.history;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Vector;

import lu.gecamed.schemas.ximport.MeasurementDocument.Measurement;
import lu.tudor.santec.gecamed.core.gui.GECAMedLists;
import lu.tudor.santec.gecamed.core.gui.listener.EntryTypeRegister;
import lu.tudor.santec.gecamed.core.gui.listener.IEntryTypeHandler;
import lu.tudor.santec.gecamed.core.gui.utils.IChangeListener;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
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.Patient;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.HistoryManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.IncidentManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.HistoryManager;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.gui.history.table.HistRow;
import lu.tudor.santec.gecamed.patient.gui.incident.IncidentComparator;
import lu.tudor.santec.gecamed.prescription.ejb.entity.beans.Prescription;
import lu.tudor.santec.gecamed.prescription.ejb.session.interfaces.PrescriptionManager;

import org.apache.log4j.Logger;

/**
 * Class to load the history data of a patient
 * and offers it to the HistoryTablePanels
 * 
 * 
 * @author martin.heinemann@tudor.lu
 * 25.06.2008
 * 11:37:37
 *
 *
 * @version
 * <br>$Log: HistoryDataAgent.java,v $
 * <br>Revision 1.11  2013-12-27 18:09:25  donak
 * <br>Cleanup of imports
 * <br>
 * <br>Revision 1.10  2013-07-15 06:18:37  ferring
 * <br>logging changed
 * <br>
 * <br>Revision 1.9  2010-11-15 12:52:53  ferring
 * <br>Nullpointer Exception caught
 * <br>
 * <br>Revision 1.8  2010-10-19 09:54:29  troth
 * <br>change structure of updating history views to make the loading of patient faster
 * <br>
 * <br>Revision 1.7  2010-07-02 15:04:52  troth
 * <br>BUg Fix - # 548: history view are not visible in the prescription dialog
 * <br>http://santec.tudor.lu/trac/gecamed/ticket/548
 * <br>
 * <br>Revision 1.6  2010-04-26 16:42:58  troth
 * <br>Redesign of the prescription view
 * <br>
 * <br>Revision 1.5  2009-05-06 16:00:05  heinemann
 * <br>added method to generate printable output of the incident entrys
 * <br>
 * <br>Revision 1.4  2008-09-25 09:43:07  heinemann
 * <br>fixed copyrights
 * <br>
 * <br>Revision 1.3  2008-09-24 11:54:17  heinemann
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.2  2008-07-08 13:32:02  heinemann
 * <br>complete - # 162: double click on consultation in history sometimes opens the wrong incident
 * <br>http://santec.tudor.lu/trac/gecamed/ticket/162
 * <br>
 * <br>Revision 1.1  2008-07-03 11:59:57  heinemann
 * <br>*** empty log message ***
 * <br>
 *   
 */
public class HistoryDataAgent implements PropertyChangeListener {
	
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(HistoryDataAgent.class.getName());
	
	/**
 	 * The HistoryManager session bean
 	 */
 	private HistoryManager manager;

	/**
	 * The OfficeManager session bean
	 */
//	private OfficeManagerInterface officeManager;

	/**
	 * The IncidentManager session bean
	 */
	private IncidentManager incidentManager;

	
	/**
	 * All physicians in a hashmap
	 */
	private HashMap<Integer, Physician> physicians = new  HashMap<Integer, Physician>();


	/**
	 * The incidents<br/>
	 * key   -> incidentId<br/>
	 * value -> Incident<br/>
	 */
	private LinkedHashMap<Integer, Incident> incidentHash;
	
	
	/**
	 * The incident entries<br/>
	 * key   -> incidentId<br/>
	 * value -> list of the entries of this incident<br/>
	 */
	private HashMap<Integer, List<IncidentEntry>> incidentEntries;
	
	
	/**
	 * The extended IncidentEntries<br/>
	 * key   -> IncidentEntry id<br/>
	 * value -> an object<br/>
	 */
	private HashMap<Integer, Object> extendedEntries;
	
	
	
	private HashSet<String> entryTypes = new HashSet<String>();

	private Patient patient;

	private Vector<IChangeListener> updateListener  = new Vector<IChangeListener>();
	
	private Vector<HistRow> cacheData =  new Vector<HistRow>(0);

	public Vector<HistRow> getCacheData() {
		return cacheData;
	}

 
	private int instance;

	private boolean debug;


	private static int instances;
	
	/**
	 * 
	 */
	public HistoryDataAgent() {
		instances++;
		this.instance = instances;
		/* ================================================== */
		try {
 			/* ------------------------------------------------------- */
 			manager 		= (HistoryManager)			ManagerFactory.getRemote(HistoryManagerBean.class);
// 			officeManager 	= (OfficeManagerInterface)  ManagerFactory.getRemote(OfficeManagerBean.class);
 			incidentManager = (IncidentManager) 		ManagerFactory.getRemote(IncidentManagerBean.class);
 			/* ------------------------------------------------------- */
 			// load all physicians
 			/* ------------------------------------------------------- */
 			reloadPhysicians();
 			GECAMedLists.addPropertyChangeListener(this, Physician.class);
			/* ------------------------------------------------------- */
			this.incidentHash 		 = new LinkedHashMap<Integer, Incident>();
			this.incidentEntries = new LinkedHashMap<Integer, List<IncidentEntry>>();
			this.extendedEntries = new LinkedHashMap<Integer, Object>();
			/* ------------------------------------------------------- */
 		}
 		catch (Exception p_Exception) {
 			/* ------------------------------------------------------- */
 			logger.warn(p_Exception.getLocalizedMessage());
 			/* ------------------------------------------------------- */
 		}
		/* ================================================== */
	}
	
	
	
	/**
	 * Save the entry in the db
	 * 
	 * @param entry
	 * @return
	 */
	public void saveEntry(IncidentEntry entry) {
		/* ================================================== */
		try {
			/* --------------------------------------------- */
			// save the entry
			/* ------------------------------------------------------- */
			IncidentEntry newEntry = incidentManager.saveEntry(entry, entry.getEntryType().getName());
			/* ------------------------------------------------------- */
			// load the type name
			/* ------------------------------------------------------- */
			newEntry.getEntryType().getName();
			/* ------------------------------------------------------- */
			// get the incident for the entry
			// there must be an incident for each entry because we are not going to
			// save new entries but only existing ones
			/* ------------------------------------------------------- */
			List<IncidentEntry> list = this.incidentEntries.get(newEntry.getIncidentId());
			/* ------------------------------------------------------- */
			// replace the old entry with the new
			/* ------------------------------------------------------- */
			list.set(list.indexOf(entry), newEntry);
			
			loadSpecialEntries();
			/* --------------------------------------------- */
			fireDataChanged();

		} catch (Exception e) {
			/* --------------------------------------------- */
			e.printStackTrace();
			/* --------------------------------------------- */
		}
		/* ================================================== */
	}
	
	
	private void reloadPhysicians ()
	{
		physicians.clear();
		for (Physician p : GECAMedLists.getListReference(Physician.class)) 
			physicians.put(p.getId(), p);
	}
	
	
	/**
	 * Loads the additional entry data objects from the modules.
	 */
	private void loadSpecialEntries() {
		logger.info("loading special history entries");
		
		/* ================================================== */
		// iterate over the types and get the handler that is responsible 
		// for that type
		/* ------------------------------------------------------- */
		for (String typeName : entryTypes) {
			/* ------------------------------------------------------- */
			IEntryTypeHandler handler = EntryTypeRegister.getHandler(typeName);
			if (handler == null) {
				logger.warn("No handler found for IncidentEntryType " + typeName);
				continue;
			}
			/* ------------------------------------------------------- */
			// get all special entries from the handler
			/* ------------------------------------------------------- */
			HashMap<Integer, Object> specialEntries = handler.getSpecialEntries(typeName, patient.getId());
			/* ------------------------------------------------------- */
			// load the objects from the handlers
			/* ------------------------------------------------------- */
			if (specialEntries != null)
				extendedEntries.putAll(specialEntries);
			/* ------------------------------------------------------- */
		}
		/* ================================================== */
		logger.info("special history entries loaded");
	}
	
	/**
	 * @param p
	 */
	public void setPatient(Patient p) {
		/* ================================================== */
		this.patient = p;
//		Thread t = new Thread() {
//			public void run() {
				/* ================================================== */
				reload();
				/* ================================================== */
//			}
//		};
//		
//		t.start();
		/* ================================================== */
	}

	/**
	 * Reloads the patient history from the database
	 */
	public void reload() {
		/* ================================================== */
		// remove old data
		/* ------------------------------------------------------- */
		this.incidentHash.clear();
		this.incidentEntries.clear();
		this.extendedEntries.clear();
		this.entryTypes.clear();
		/* ------------------------------------------------------- */
		// first step. load the incidents with the incident entries.
		/* ------------------------------------------------------- */
		long start = System.currentTimeMillis();
		List<Incident> unsortedIncidents = (List<Incident>) manager.getPatientIncidents(this.patient.getId());
		
		logger.info("loading history incidents and entries took: " + (System.currentTimeMillis()-start));
		
		if (unsortedIncidents == null)
			return;
		
		/* ------------------------------------------------------- */
		// sort the incidents by their date
		/* ------------------------------------------------------- */
		Collections.sort(unsortedIncidents, new IncidentComparator());
		/* ------------------------------------------------------- */
		// dispatch the incidents to the hashmaps
		/* ------------------------------------------------------- */
		for (Incident incident : unsortedIncidents) {
			/* ------------------------------------------------------- */
			// add to the incident hashmap
			/* ------------------------------------------------------- */
			this.incidentHash.put(incident.getId(), incident);
			/* ------------------------------------------------------- */
			// put the entries to the hashmap
			/* ------------------------------------------------------- */
			if (incident.getIncidentEntries() != null) {
				/* ------------------------------------------------------- */
				List<IncidentEntry> unsortedEntries = incident.getIncidentEntries();
				/* ------------------------------------------------------- */
				// sort the entries by their date
				/* ------------------------------------------------------- */
				sortIncidentEntries(unsortedEntries);
				/* ------------------------------------------------------- */
				// add to the entries hashmap
				/* ------------------------------------------------------- */
				this.incidentEntries.put(incident.getId(),  unsortedEntries);
				/* ------------------------------------------------------- */
				// get all entry types
				/* ------------------------------------------------------- */
				filterEntryTypes(unsortedEntries);
				/* ------------------------------------------------------- */
				/* ------------------------------------------------------- */
			}
			/* ------------------------------------------------------- */
		}
		loadSpecialEntries();
		
		logger.info("all "+ this.incidentEntries.size() + " history entries loaded");
		fireDataChanged();
		logger.info("history update finished. History contains " +incidentHash.size()+ " incidents");
		
		/* ================================================== */
	}
	
	
	/**
	 * @param entries
	 */
	private void sortIncidentEntries(List<IncidentEntry> entries) {
		/* ================================================== */
		// sorting:
		//
		// Ac S O A P Me M Rest
		/* ------------------------------------------------------- */
		Collections.sort(entries);
		/* ================================================== */
	}
	
	/**
	 * Iterates through the list and stores the types
	 * of the elements that are in the list
	 * 
	 * @param entries
	 */
	private void filterEntryTypes(List<IncidentEntry> entries) {
		/* ================================================== */
		if (entries == null)
			return;
		/* ------------------------------------------------------- */
		for (IncidentEntry entry : entries) {
			/* ------------------------------------------------------- */
			try {
				/* --------------------------------------------- */
				String name = entry.getEntryType().getName();
				if (name != null)
					this.entryTypes.add(name);
				/* --------------------------------------------- */
			} catch (Exception e) {
				/* --------------------------------------------- */
				/* --------------------------------------------- */
			}
			/* ------------------------------------------------------- */
		}
		/* ================================================== */
	}
	

	/**
	 * @return the physicians
	 */
	public HashMap<Integer, Physician> getPhysicians() {
		/* ================================================== */
		return physicians;
		/* ================================================== */
	}

	/**
	 * @return the extendedEntries
	 */
	public HashMap<Integer, Object> getExtendedEntries() {
		/* ================================================== */
		return extendedEntries;
		/* ================================================== */
	}



	/**
	 * @return the incidents
	 */
	public HashMap<Integer, Incident> getIncidents() {
		/* ================================================== */
		return incidentHash;
		/* ================================================== */
	}
	
	/**
	 * @return
	 */
	public Incident getLatestIncident() {
		/* ================================================== */
		return this.getIncidentByRow(0);
		/* ================================================== */
	}
	
	
	/**
	 * Returns the indcident for a row
	 * 
	 * @param row
	 * @return
	 */
	public Incident getIncidentByRow(int row) {
		/* ================================================== */
		if (row < 0)
			return null;
		/* ------------------------------------------------------- */
		// iterate through all incidents and their entries
		/* ------------------------------------------------------- */
		int count = 0;
		for (Incident incident : incidentHash.values()) {
			/* ------------------------------------------------------- */
			if (row == count) {
				return incident;
			}
			/* ------------------------------------------------------- */
			// iterate through the entries
			/* ------------------------------------------------------- */
			List<IncidentEntry> entries = incidentEntries.get(incident.getId());
			/* ------------------------------------------------------- */
			if ( entries != null && entries.size() > 0) {
				/* ------------------------------------------------------- */
				if (entries.size() == 1 && (incident.getIsAccident() == null || incident.getIsAccident() == false)) {
					count++;
					continue;
				}
				int red = 0;
				if (entries.size() > 1 && (incident.getIsAccident() == null || incident.getIsAccident() == false))
					red = 1;
				for (int i = 0; i < (entries.size()-red); i++) {
					/* ------------------------------------------------------- */
					count++;
					if (row == count) {
						return incident;
					}
					/* ------------------------------------------------------- */
				}
				count++;
				/* ------------------------------------------------------- */
			} else {
				count++;
			}
			/* ------------------------------------------------------- */
		}
		return null;
		/* ================================================== */
	}
	
	
	/**
	 * @param l
	 */
	public void addUpdateListener(IChangeListener l) {
		/* ================================================== */
		this.updateListener.add(l);
		/* ================================================== */
	}
	
	/**
	 * @param l
	 */
	public void removeUpdateListener(IChangeListener l) {
		/* ================================================== */
		this.updateListener.remove(l);
		/* ================================================== */
	}
	
	
	/**
	 * 
	 */
	public void fireDataChanged() {
		
		createCacheData();
		
		/* ================================================== */
		for (IChangeListener l : this.updateListener)
			l.fireEvent();
		/* ================================================== */
	}
	
	/**
	 * @param incidentList
	 */
	private void createCacheData() {
		long		start	= System.currentTimeMillis();
		String		docString;
		Physician	physician;
		
		/* ================================================== */
		this.cacheData.clear();
		/* ------------------------------------------------------- */
		if (this.incidentHash.values() == null)
			return;
		/* ------------------------------------------------------- */
		for (Integer  incidentID : this.incidentHash.keySet()) {
			Incident incident = this.incidentHash.get(incidentID);
			try
			{
				/* ------------------------------------------------------- */
				// create a row for the incident
				HistRow row = null;
				int startAt = 0;
				
				// load the mnemonic of the physician
				physician = null;
				if (incident.getPhysicianId() != null)	{
					physician = getPhysicians().get(incident.getPhysicianId());
					if (physician == null)
					{
						GECAMedLists.resetListAndNotifyListener(Physician.class);
						// listener will be called and physicians will be reloaded
						// reloadPhysicians();
						physician = getPhysicians().get(incident.getPhysicianId());
					}
					
					if (physician != null)
						docString = physician.getMnemonic();
					else
						docString = "xxx";
				} else {
					docString = "";
				}
				
				if (incident.getIsAccident()) {
					/* ------------------------------------------------------- */
					row = new HistRow(incident.getIncidentDate(), docString, createAccident(incident));
					/* ------------------------------------------------------- */
				} else {
					/* ------------------------------------------------------- */
					// we take the first entry for the first line
					/* ------------------------------------------------------- */
					if (incident.getIncidentEntries() != null && incident.getIncidentEntries().size() > 0) {
						/* ------------------------------------------------------- */
						IncidentEntry entryCopy = new IncidentEntry(incident.getIncidentEntries().get(0));
	//					long subStart = System.currentTimeMillis();
						entryCopy = modifyEntryIfPrescription(entryCopy);
						entryCopy = modifyEntryIfMeasurement(entryCopy);
	//					logger.info("modifyEntryIfPrescription took: " + ( System.currentTimeMillis()-subStart));
						
						row = new HistRow(incident.getIncidentDate(), docString, entryCopy);
						/* ------------------------------------------------------- */
					} 
					/* ------------------------------------------------------- */
					// skip the first entry. We used it already
					startAt = 1;
					/* ------------------------------------------------------- */
				}
				if (row != null)
				    cacheData.add(row);
				else {
				    logger.warn("incident " + incident.getId() + ":" + incident + " is not shown in history because it is no accident and has no attached IncidentEntries - this should not happen" );
				}
				/* ------------------------------------------------------- */
				// now iterate over the rest entries
				/* ------------------------------------------------------- */
				int count = 0;
				if (incident.getIncidentEntries() != null) {
					for (IncidentEntry entry : incident.getIncidentEntries()) {
						/* ------------------------------------------------------- */
						if (count == startAt) {
							/* ------------------------------------------------------- */
							IncidentEntry entryCopy = new IncidentEntry(entry);
							
	//						long subStart = System.currentTimeMillis();
							entryCopy = modifyEntryIfPrescription(entryCopy);
							entryCopy = modifyEntryIfMeasurement(entryCopy);
	//						logger.info("modifyEntryIfPrescription took: " + ( System.currentTimeMillis()-subStart));
							/* ------------------------------------------------------- */
							row = new HistRow(incident.getIncidentDate(), docString, entryCopy);
							cacheData.add(row);
							/* ------------------------------------------------------- */
						} else
							count++;
						/* ------------------------------------------------------- */
					}
				}
			}
			catch (Exception e) {
				logger.error("Error while trying to show incident", e);
			}
			/* ------------------------------------------------------- */
		}
		logger.info("creatingCacheData in HistoryDataAgent No." + instance + " of " + instances + " ID" + this.hashCode() + " took: " + ( System.currentTimeMillis()-start));
		if (debug) {
			logger.info(createLog());
		}
		/* ================================================== */
	}
	
	
	public String createLog() {
		StringBuffer sb = new StringBuffer("------------------	HistoryDataAgent cachedData	---------------------------\n");
		for (HistRow histRow : cacheData) {
			sb.append(histRow).append("\n");
		}
		sb.append("----------------------------------------------------------------------------\n");
		return sb.toString();
	}


	/**
	 * Creates an accident entry for the incident
	 * 
	 * @param incident
	 */
	private IncidentEntry createAccident(Incident incident) {
		/* ================================================== */
		
		/* ------------------------------------------------------- */
		IncidentEntry ae = new IncidentEntry();
		
		IncidentEntryType accType = new IncidentEntryType();
		accType.setName("Ac");
		ae.setEntryType(accType);
		ae.setIncidentId(incident.getId());
		
		String s = incident.getAccidentNr();
		
		if (incident.getAccidentDate() != null) {
			s += " " + incident.getAccidentDate();			
		}
		
		ae.setTextContent("<html><span style=\"font-family:Arial;font-size:"
					+ "9" + "px;\">"
					+ s.replaceAll("\n", "<br>"));
		/* ------------------------------------------------------- */
		return ae;
		/* ================================================== */
	}
	
	/**
	 * modify the incident entry if it is a prescription to search over it in the HistoryViewTab. the text content of the prescription entry
	 * is copy to the text content of the incident entry.
	 *  
	 * @param incidentEntry to modify
	 * @return incidentEntry the modified entry
	 */
	private IncidentEntry modifyEntryIfPrescription(IncidentEntry incidentEntry)
	{
		// look if entry is a prescription
		if (incidentEntry.getEntryType().getName().equals(
				PrescriptionManager.ENTRY_TYPE)) {

			if (incidentEntry.getId() != null) {

				try {
					Prescription prescription = (Prescription) getExtendedEntries().get(incidentEntry.getId());
					
					if (prescription != null) 
						incidentEntry.setTextContent(prescription.getTextContent());
					
				} catch (Exception e) {
					logger.warn("Error creating prescription History Entry", e);
				}
			}
		}
		return incidentEntry;
	}
	
	/**
	 * modify the incident entry if it is a measurement to search over it in the HistoryViewTab. 
	 *  
	 * @param incidentEntry to modify
	 * @return incidentEntry the modified entry
	 */
	private IncidentEntry modifyEntryIfMeasurement(IncidentEntry incidentEntry)
	{
		// look if entry is a prescription
		if (incidentEntry.getEntryType().getName().equals(IncidentManager.MEASUREMENT)) {

			if (incidentEntry.getId() != null) {

				try {
					
					String measurementString = (String) getExtendedEntries().get(incidentEntry.getId());
					
					if (measurementString != null) 
						incidentEntry.setTextContent(measurementString);
					
				} catch (Exception e) {
					logger.warn("Error creating MEasurement History Entry", e);
				}
			}
		}
		return incidentEntry;
	}
	
	
	protected void finalize() throws Throwable
	{
		instances--;
	}
	
	
	public void propertyChange (PropertyChangeEvent evt)
	{
		if (evt.getPropertyName().equals(Physician.class.getSimpleName()))
			reloadPhysicians();
	}
	
	public void enableDebug() {
		debug = true;
	}
	
}
