/*******************************************************************************
 * 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.billing.ejb.session.beans;

import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.NoResultException;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Act;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.HospitalizedInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.InvoiceInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.NomenclatureInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.RuleInterface;
import lu.tudor.santec.gecamed.billing.utils.InvoiceWorkflow;
import lu.tudor.santec.gecamed.core.ejb.session.beans.GECAMedSessionBean;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Hospitalisation;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.HospitalisationClass;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.HospitalisationPeriod;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Passage;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.interfaces.LoginInterface;

//***************************************************************************
//* Interface Definition and Members                                        *
//***************************************************************************

/**
 * The HospitalizedBean manages all aspects of invoices related to hospital
 * visits. The bean actually represents the link between hospital passages
 * and resulting billing data.
 * @author nico.mack@tudor.lu
 */

@Stateless
@Remote (HospitalizedInterface.class)
public class HospitalizedBean  extends GECAMedSessionBean implements HospitalizedInterface
	{
//	private static final long serialVersionUID = 1L;
	
	@EJB
	private InvoiceInterface 	m_InvoiceInterface;
	
	@EJB
	private RuleInterface		m_RuleInterface;
	
	@EJB
	private LoginInterface		m_LoginInterface;
	
	@EJB
	private NomenclatureInterface	m_NomenclatureInterface;
	
//	@PersistenceContext (unitName="gecam")
//	EntityManager m_EntityManager;
	
//***************************************************************************
//* Constructor(s)                                                          *
//***************************************************************************

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Primitives                                                        *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * Returns the state of the Split Billing Setting. Billing module has two
 * modes of billing. Either every physician issues his own invoices, with
 * his own UCM code or all invoices bear the UCM code of the medical office.
 * The Split Billing Setting decides which billing mode should be used.
 * @return <code>true</code> if split billing mode should be used, i.e. every
 * physician issues his own invoices or, <code>false</code> if common billing
 * mode should be used. If setting can not be found or hasn't been set, the
 * method returns <code>false</code> by default.
 */
//---------------------------------------------------------------------------
	
private boolean getSplitBilling ()
	{
	Boolean l_SplitBilling;
	
	l_SplitBilling = (Boolean)m_LoginInterface.getAdminSettingValue("Billing", "BillingPerPhysician");
	
	if (l_SplitBilling != null) 
		 return l_SplitBilling.booleanValue();
	else return false;
	}

//---------------------------------------------------------------------------
/**
 * Given a collection of invoices, the buildInvoiceLookupTable method creates
 * a hashtable to avoid iterating over invoice collection every time a
 * specific invoice is required. The method uses keys generated by 
 * generateInvoiceKey method for lookup. 
 * @param p_Invoices specifies the collection of invoices to build the
 * lookup table for.
 * @return A hashtable using Strings generated by generateInvoiceKey method
 * as keys and invoices specified by p_Invoices as values.
 * @see #Invoice.generateInvoiceKey for more information regarding format of keys.
 */
//---------------------------------------------------------------------------

private Hashtable <String,Invoice> buildInvoiceLookupTable (Collection <Invoice> p_Invoices)
	{
	Iterator <Invoice>			l_InvoiceIterator;
	Invoice						l_Invoice;
	Hashtable <String,Invoice>	l_LookupTable;
	boolean						l_SplitBilling;
	
	l_LookupTable = new Hashtable <String,Invoice> ();
	
	if (p_Invoices == null) return l_LookupTable;
	
	l_SplitBilling = this.getSplitBilling();
	
	l_InvoiceIterator = p_Invoices.iterator();
	while (l_InvoiceIterator.hasNext())
		{
		l_Invoice = l_InvoiceIterator.next();
		l_LookupTable.put(l_Invoice.generateInvoiceKey(l_SplitBilling), l_Invoice);
		}
	
	return l_LookupTable;
	}

//---------------------------------------------------------------------------
/**
 * The method returns a new invoice object, initialized with the specified
 * data.
 * @param p_Class specifies the hospitalisation class for the new invoice.
 * @param p_Physician specifies the physician this invoice is issued for.
 * @param p_Patient specifies the patient the new invoice is for.
 * @param p_InvoiceDate specifies the creation date of the invoice.
 * @return A newly created and initialized invoice object. The invoice object
 * has NOT been persisted, i.e. stored in the database. 
 */
//---------------------------------------------------------------------------

private Invoice newInvoice (HospitalisationClass	p_Class,
							Physician				p_Physician,
							Patient					p_Patient,
							Date					p_InvoiceDate)
	{
	Invoice l_Invoice;
	
	l_Invoice = new Invoice ();
	l_Invoice.setState(InvoiceWorkflow.c_NewState);
	l_Invoice.setHospitalisationClass(p_Class);
	l_Invoice.setPatient(p_Patient);
	if (p_Patient != null) l_Invoice.setHealthInsurance(p_Patient.getInsurance());
	l_Invoice.setPhysician(p_Physician);
	l_Invoice.setInvoiceDate(p_InvoiceDate);
	
	return l_Invoice;
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body		                                                        *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * Returns a very specific hospital passage identified by the specified Id.
 * @param p_PassageId specifies the Id of the hospital passage to get from
 * database.
 * @return The passage object with the specified Id, <code>null</code> if
 * no such passage exists.
 */
//---------------------------------------------------------------------------

public Passage getPassageById (Integer p_PassageId) throws Exception
	{
	Passage l_Passage = null;
	
	if (p_PassageId == null) return null;
	
	try	{
		l_Passage = m_EntityManager.find (Passage.class, p_PassageId);
		}
	catch (NoResultException p_Exception)
		{	
		l_Passage = null;
		}
	
	return l_Passage;
	}

//---------------------------------------------------------------------------
/**
 * Returns the billing act corresponding to the specifed hospital passage.
 * @param p_Passage specifies the hospital passage to get corresponding
 * billing act for.
 * @return The corresponding billing act if it can be found, <code>null</code>
 * otherwise.
 */
//---------------------------------------------------------------------------

public Act getActToWithdraw (Passage p_Passage) throws Exception 
	{
	Act	l_Act = null;
	
	if (p_Passage == null) return l_Act;
		
	try	{ 
		l_Act = (Act) m_EntityManager.createNamedQuery(Act.c_ActByAccessionNumber)
								 	 .setParameter ("accessionNumber",p_Passage.getAccessionNumber())
								 	 .setMaxResults(1)
								 	 .getSingleResult();
		}
	catch (NoResultException p_Exception)
		{	
		l_Act = null;
		}

	return l_Act;
	}

//---------------------------------------------------------------------------
/**
 * Returns the invoice containing the billing act corresponding to the 
 * specified hospital passage.
 * @param p_Passage specifies the hospital passage to get corresponding
 * invoice for.
 * @return The corresponding invoice if it can be found, <code>null</code>
 * otherwise.
 */
//---------------------------------------------------------------------------

public Invoice getInvoiceToWithdrawActFrom (Passage p_Passage) throws Exception 
	{
	Act		l_Act     = null;
	Invoice	l_Invoice = null;
	
	l_Act = this.getActToWithdraw (p_Passage);
	if (l_Act != null)
		{
		try	{
			l_Invoice = getInvoiceByID(l_Act.getInvoiceId());
			}
		catch (NoResultException p_Exception)
			{	
			l_Invoice = null;
			}
		}
	return l_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The withdrawAct method removes the specified act from the specified invoice.
 * The method checks whether the specified act really is on the specified invoice
 * before deleting it. After removing the specified act, the method reapplies
 * the billing rules to the invoice and saves the invoice back into the database.
 * @param p_Act specifies the act to withdraw, i.e. to delete.
 * @param p_Invoice specifies the invoice to remove it from.
 * @return the modified invoice.
 */
//---------------------------------------------------------------------------

public Invoice withdrawAct (Act p_Act, Invoice p_Invoice) throws Exception
	{
	Set <Act>	l_Acts;
	
	if ((p_Act == null) || (p_Invoice == null)) return p_Invoice;
	
	l_Acts = p_Invoice.getActs();
	if ((l_Acts != null) && (l_Acts.contains(p_Act)))
		{
		l_Acts.remove(p_Act);
		p_Invoice.setActs(l_Acts);
		p_Invoice = m_InvoiceInterface.saveActs(p_Invoice, l_Acts);
		p_Invoice = m_RuleInterface.applyRules(p_Invoice, null);
		p_Invoice.monetize();
		p_Invoice = m_InvoiceInterface.saveInvoice (p_Invoice);
		}
	
	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * Returns the billing act corresponding to the specified passage to be
 * restored. Under normal circumstances, returned billing act is a new
 * act, initialized with data from specified passage and which is not
 * persistent, i.e. has not been saved yet in the database.
 * @param p_Passage specifies the hospital passage to get corresponding
 * billing act for.
 * @return The corresponding billing act.
 */
//---------------------------------------------------------------------------

public Act getActToRestore (Passage p_Passage) throws Exception 
	{
//	Rate	l_Rate	= null;
	Act		l_Act 	= null;
	
	if (p_Passage == null) return null;

	try	{
		l_Act = (Act) m_EntityManager.createNamedQuery(Act.c_ActByAccessionNumber)
	 							 	 .setParameter ("accessionNumber",p_Passage.getAccessionNumber())
	 							 	 .setMaxResults(1)
	 							 	 .getSingleResult();
		}
	catch (NoResultException p_Exception)
		{
		// Not finding an Act ain't an error. Query actually is a safeguard to avoid
		// acts with same accession number. So, getting a NoResultException is actually
		// the expected result.
		
		l_Act = null;
		}
		
	if (l_Act == null) 
		{
//		l_Rate = (Rate) m_EntityManager.createNamedQuery(Rate.c_RateByCodeAndPerformedDate)
//		 							   .setParameter ("code",p_Passage.getTreatUCMCode())
//		 							   .setParameter ("date",p_Passage.getDate())
//		 							   .setMaxResults(1)
//		 							   .getSingleResult();
		
//		if (l_Rate != null)
//			{
			l_Act = new Act ();
			l_Act.setPerformedDate(p_Passage.getDate());
			l_Act.setAccessionNumber(p_Passage.getAccessionNumber());
			
			l_Act = m_NomenclatureInterface.initializeAct(p_Passage.getTreatUCMCode(), l_Act);
			
//			l_Rate.initializeAct(l_Act);
			}			
//		}
	return l_Act;
	}

//---------------------------------------------------------------------------
/**
 * Returns the invoice to restore the act corresponding to specifies passage
 * to. To do so, the method goes through all invoices for hospitalisation
 * that specified passage refers to and looks for the best match. Best match
 * depends on hospitalisation class of current hospitalisation period,
 * on treating physician as well as passage date.
 * @param p_Passage specifies the hospital passage to get elligible
 * invoice for.
 * @return The elligible invoice to restore act to if it can be found, 
 * <code>null</code> otherwise.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Invoice getInvoiceToRestoreActTo (Passage p_Passage) throws Exception 
	{
	Hospitalisation				l_Hospitalisation;
	HospitalisationPeriod		l_Period;
	Collection <Invoice>		l_Invoices;
	Hashtable <String,Invoice>	l_AvailableKeys;
	Physician					l_Physician	= null;
	String						l_RequiredKey;
	Invoice						l_Invoice = null;
	boolean						l_SplitBilling;
	
	if (p_Passage == null) return null;

	l_Period = (HospitalisationPeriod) m_EntityManager.find(HospitalisationPeriod.class, p_Passage.getHospperiodId());
	if (l_Period != null)
		{
		l_Hospitalisation = l_Period.getHospitalisation();
		if (l_Hospitalisation != null)
			{
			l_Invoices = m_EntityManager.createNamedQuery(Invoice.c_InvoicesByHospitalisation)
			   							.setParameter ("hospitalisation",l_Hospitalisation)
									    .getResultList();
			if (l_Invoices != null)
				{
				l_AvailableKeys = this.buildInvoiceLookupTable(l_Invoices);
				l_SplitBilling = this.getSplitBilling();
				if (l_SplitBilling) l_Physician = m_EntityManager.find (Physician.class, p_Passage.getPhysicianId());
				
				l_RequiredKey = Invoice.generateInvoiceKey(l_Period.getHospitalisationClass(),
														   l_Physician,
														   p_Passage.getDate());
				
				if (l_AvailableKeys.containsKey (l_RequiredKey))
					{
					l_Invoice = l_AvailableKeys.get(l_RequiredKey);
					}
				}
			}
		}
		
	return l_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The restoreAct method adds the specified act to the specified invoice.
 * The method checks whether the specified act is not yet on the specified invoice
 * before adding it. After adding the specified act, the method reapplies
 * the billing rules to the invoice and saves the invoice back into the database.
 * @param p_Act specifies the act to restore, i.e. to add.
 * @param p_Invoice specifies the invoice to add it to.
 * @return the modified invoice.
 */
//---------------------------------------------------------------------------

public Invoice restoreAct (Act p_Act, Invoice p_Invoice) throws Exception
	{
	Set <Act>	l_Acts;
	
	if ((p_Act == null) || (p_Invoice == null)) return p_Invoice;
	
	l_Acts = p_Invoice.getActs();
	if (l_Acts == null) 
		l_Acts = new HashSet <Act> ();
	
	if (!l_Acts.contains(p_Act))
		{
		l_Acts.add(p_Act);
		p_Invoice.setActs(l_Acts);
		p_Invoice = m_InvoiceInterface.saveActs(p_Invoice, l_Acts);
		p_Invoice = m_RuleInterface.applyRules(p_Invoice, null);
		p_Invoice.monetize();
		p_Invoice = m_InvoiceInterface.saveInvoice (p_Invoice);
		}
			
	return p_Invoice;
	}

//---------------------------------------------------------------------------
/**
 * The newInvoiceForPassage creates a new invoice for the specified
 * passage. The method uses data of the specified passage to initialize
 * newly create invoice.
 * @param p_Passage specifies the passage to use data from to initialize the newly
 * created invoice. 
 * @return A newly created and initialized invoice object. The invoice object
 * has NOT been persisted, i.e. stored in the database. 

 */
//---------------------------------------------------------------------------

public Invoice newInvoiceForPassage (Passage p_Passage) throws Exception
	{
	HospitalisationPeriod		l_Period	= null;
	Physician					l_Physician	= null;
	Patient						l_Patient   = null;
	Invoice						l_Invoice   = null;
	
	if (p_Passage == null) return null;

	l_Period = (HospitalisationPeriod) m_EntityManager.find(HospitalisationPeriod.class, p_Passage.getHospperiodId());
	if (l_Period != null)
		{
		l_Patient   = m_EntityManager.find (Patient.class,   p_Passage.getPatientId());
		l_Physician = m_EntityManager.find (Physician.class, p_Passage.getPhysicianId());
	
		l_Invoice = this.newInvoice(l_Period.getHospitalisationClass(), 
								 	l_Physician, 
								 	l_Patient, 
								 	p_Passage.getDate()); 
		
		l_Invoice.setHospitalisation(l_Period.getHospitalisation());
		}
	
	return l_Invoice;
	}

	private  Invoice getInvoiceByID (Integer p_ID) throws Exception
	{
	Invoice l_Invoice;
	
	try	{
		l_Invoice = (Invoice) m_EntityManager.createNamedQuery(Invoice.c_InvoiceById)
								   			 .setParameter (Invoice.c_IdParameter,p_ID)
								   			 .getSingleResult();
		l_Invoice.updateFirstClassRequired();
		}
	catch (NoResultException p_Exception)
		{
		l_Invoice = null;
		}
	
	return l_Invoice;
	}

//***************************************************************************
//* End of Class															*
//***************************************************************************
}
