/*******************************************************************************
 * 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.ArrayList;
import java.util.Collection;
import java.util.Date;

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

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Ledger;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Settlement;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Transaction;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.AccountingInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.InvoiceInterface;
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.usermanagement.ejb.entity.beans.GecamedUser;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.interfaces.LoginInterface;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;

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

/**
 * The AccountingBean implements the methods required to access accounting
 * data in the database. At this stage, accounting data is limited to the
 * Ledger Bean, thus the AccountingBean manages access to Ledger data in
 * database.
 */

@Stateless
@Remote (AccountingInterface.class)
public class AccountingBean extends GECAMedSessionBean implements AccountingInterface
	{
	
	/**
	 * static logger for this class
	 */
	private static Logger m_Logger = Logger.getLogger(AccountingBean.class.getName());
	
	@EJB
	InvoiceInterface m_InvoiceBean;
	
	@EJB
	LoginInterface   m_Login;
	
	private static Collection<Integer> dependencies = new ArrayList<Integer>(); 
	static {
		dependencies.add(InvoiceInterface.c_PatientDependency);
	}

//***************************************************************************
//* Class Primitives                                                        *
//***************************************************************************

//***************************************************************************
//* Class Body                                                              *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * Fetches all the ledgers associated with the specified invoice.
 * @param p_Invoice specifies the invoice to get ledgers of.
 * @return A collection holding all the ledgers associated with the
 * specified invoice, <code>null</code> if none can be found.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Ledger> getLedgersByInvoice (Invoice p_Invoice) throws Exception 
	{
	Collection<Ledger> 	l_Ledgers = null;

	try	{
		l_Ledgers = m_EntityManager.createNamedQuery (Ledger.c_LedgersByInvoice)
								   .setParameter("invoice", p_Invoice)
								   .getResultList();
		}
	catch (NoResultException p_Exception)
		{	
		l_Ledgers = null;
		}

	return l_Ledgers;
	}

//---------------------------------------------------------------------------
/**
 * Fetches all the ledgers associated with the specified cashier.
 * @param p_Cashier specifies the GECAMed user to get ledgers of.
 * @return A collection holding all the ledgers associated with the
 * specified user, <code>null</code> if none can be found.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Ledger> getAllLedgersByCashier (GecamedUser p_Cashier) throws Exception 
	{
	Collection<Ledger>  	l_Ledgers = null;

	try	{
		l_Ledgers = m_EntityManager.createNamedQuery (Ledger.c_LedgersByCashier)
								   .setParameter("cashier", p_Cashier)
								   .getResultList();
		}
	catch (NoResultException p_Exception)
		{	
		l_Ledgers = null;
		}

	return l_Ledgers;
	}

//---------------------------------------------------------------------------
/**
 * Fetches all the ledgers associated with the specified cashier and where the
 * transaction was executed on the specified date.
 * @param p_Cashier specifies the GECAMed user to get ledgers of.
 * @param p_TransactionDate specifies the transaction date of interest
 * @return A collection holding all the ledgers associated with the
 * specified user and being execute on the specified date, <code>null</code> 
 * if none can be found.
 */
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Ledger> getDailyLedgersByCashier (GecamedUser p_Cashier, Date p_TransactionDate) throws Exception
{
	Collection<Ledger>  	l_Ledgers = null;

	try	{
		l_Ledgers = m_EntityManager.createNamedQuery (Ledger.c_DailyLedgersByCashier)
	   						       .setParameter("cashier", p_Cashier)
	   						       .setParameter("date",    p_TransactionDate)
	   						       .getResultList();
		}
	catch (NoResultException p_Exception)
		{	
		l_Ledgers = null;
		}

	return l_Ledgers;
	}

//---------------------------------------------------------------------------
/**
 * The saveLedger method saves the specified Ledger into the database.
 * @param p_Ledger specifies the Ledger to be saved.
 * @return the saved (or persisted) Ledger. If specified Ledger was <code>null</code>,
 * the method does nothing and returns <code>null</code> as well.
 */
//---------------------------------------------------------------------------

public Ledger saveLedger (Ledger p_Ledger) throws Exception
	{
	if (p_Ledger != null)  
		 return m_EntityManager.merge (p_Ledger);
	else return null;
	}

//---------------------------------------------------------------------------
/**
 * Returns a list of executed transactions for the specified physician,
 * executed during the specified period and using the specified settlement
 * methods.
 * @param p_Physician specifies the physician to get transactions for, i.e.
 * transactions are limited to invoices issued for the specified physician.
 * @param p_FromDate specifies the start date of period that transaction's
 * settlement date has to comply with to be taken into account.
 * @param p_UntilDate specifies the end date of period that transaction's
 * settlement date has to comply with to be taken into account.
 * @param p_Cashier specifies the cashier you want to get transactions of.
 * Specifiy <code>null</code> to ignore cashier
 * @param p_SettlementIds specifies the accepted settlement methods for
 * transactions to be taken into account.
 * @return A collection holding all transactions matching the specified
 * criteria, <code>null</code> if non can be found.
*/
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Transaction> getTransactionsForPhysician (Physician p_Physician, 
															 Date p_FromDate, Date p_UntilDate, 
															 GecamedUser p_Cashier,
															 Collection <Integer> p_SettlementIds) throws Exception
	{
	Collection <Transaction> l_Transactions;
	String					 l_NamedQuery;
	Query					 l_Query;
	
	if (p_Cashier == null)
		{
		l_NamedQuery = Transaction.c_TransactionsForPhysician;
		}
	else
		{
		l_NamedQuery = Transaction.c_TransactionsForPhysicianByCashier;		
		}
		
	try	{
		l_Query = m_EntityManager.createNamedQuery(l_NamedQuery);
		l_Query.setParameter("physicianId",   p_Physician.getId());
		l_Query.setParameter("fromDate",      p_FromDate);
		l_Query.setParameter("untilDate",     p_UntilDate);
		l_Query.setParameter("settlementIds", p_SettlementIds);
		if (p_Cashier != null) l_Query.setParameter("cashierId", p_Cashier.getId());
			
		l_Transactions = l_Query.getResultList();
		}
	catch (NoResultException p_Exception)
		{	
		l_Transactions = null;
		}

	return l_Transactions;
	}

//---------------------------------------------------------------------------
/**
 * Returns a list of executed transactions for the whole office,
 * executed during the specified period and using the specified settlement
 * methods.
 * @param p_FromDate specifies the start date of period that transaction's
 * settlement date has to comply with to be taken into account.
 * @param p_UntilDate specifies the end date of period that transaction's
 * settlement date has to comply with to be taken into account.
 * @param p_Cashier specifies the cashier you want to get transactions of.
 * Specifiy <code>null</code> to ignore cashier
 * @param p_SettlementIds specifies the accepted settlement methods for
 * transactions to be taken into account.
 * @return A collection holding all transactions matching the specified
 * criteria, <code>null</code> if non can be found.
*/
//---------------------------------------------------------------------------

@SuppressWarnings("unchecked")
public Collection <Transaction> getTransactionsForOffice (Date p_FromDate, Date p_UntilDate, 
														  GecamedUser p_Cashier,
														  Collection <Integer> p_SettlementIds) throws Exception
	{
	Collection <Transaction> l_Transactions;
	String					 l_NamedQuery;
	Query					 l_Query;
	
	if (p_Cashier == null)
		{
		l_NamedQuery = Transaction.c_TransactionsForOffice;
		}
	else
		{
		l_NamedQuery = Transaction.c_TransactionsForOfficeByCashier;		
		}
	
	
	try	{
		l_Query = m_EntityManager.createNamedQuery(l_NamedQuery);
		l_Query.setParameter("fromDate",      p_FromDate);
		l_Query.setParameter("untilDate",     p_UntilDate);
		l_Query.setParameter("settlementIds", p_SettlementIds);
		if (p_Cashier != null) l_Query.setParameter("cashierId", p_Cashier.getId());
			
		l_Transactions = l_Query.getResultList();
		}
	catch (NoResultException p_Exception)
		{	
		l_Transactions = null;
		}

	return l_Transactions;
	}


//---------------------------------------------------------------------------
/**
 * 
 * 
 * 
 * 
 * 
 * 
*/
//---------------------------------------------------------------------------
	public Invoice registerTransaction(Invoice p_Invoice, Settlement p_Method, Date p_Date, Double p_PaidAmount, Double p_ReturnedAmount, Double p_Deduction, boolean p_Revoke) throws Exception {

		Ledger l_Transaction;
		double l_Paid;
		double l_Returned;
		double l_Deduct;
		
		// no Invoice -> Return
		if (p_Invoice == null)
			return null;

		m_Logger.info((p_Revoke?"Revoke":"Payment") + " for Invoice No: "+ p_Invoice + " via " + p_Method + " Invoice Balance:" + p_Invoice.getBalance() + " Payed:"+p_PaidAmount+ " Returned:"+p_ReturnedAmount+ " Deduction:"+p_Deduction);
		
		// if Null -> 0.0 
		l_Paid = (p_PaidAmount != null) ? p_PaidAmount.doubleValue() : 0d;
		// if Null -> 0.0
		l_Returned = (p_ReturnedAmount != null) ? p_ReturnedAmount.doubleValue() : 0d;
		// if Null -> 0.0 
		l_Deduct = (p_Deduction != null) ? p_Deduction.doubleValue() : 0d;


		Double l_AlreadyPaid = p_Invoice.getPayment();
		
//		Double l_OldBalance = p_Invoice.getBalance();
//		Double l_NewBalance = calculateNewBalance(l_OldBalance, p_PaidAmount, p_Deduction);		
		
		Double l_RealPayed =  l_Paid - l_Returned;
			
		if (p_Revoke) {
			// change Invoice
			p_Invoice.setPayment 		(l_AlreadyPaid + l_RealPayed);
			p_Invoice.setDeduction		(l_Deduct);
			p_Invoice.setSettlementDate	(null);
			p_Invoice.setSettlement		(null);
		} else {
			// change Invoice
			p_Invoice.setPayment 		(l_AlreadyPaid + l_RealPayed);
			p_Invoice.setDeduction		(l_Deduct);
			p_Invoice.setSettlementDate	(p_Date);
			p_Invoice.setSettlement		(p_Method);
		}
		
		// Monetize and save Invoice
		p_Invoice.monetize();
		p_Invoice = m_InvoiceBean.saveInvoice(p_Invoice);
		p_Invoice = m_InvoiceBean.fetchLazyDependencies(p_Invoice, dependencies);
		

		
		if ((l_Paid == 0) && (l_Returned == 0)) {
			m_Logger.warn("Attempt to register transaction with both payed and returned set to 0 for invoice with ID " + p_Invoice.getId());
		} else {
			// create Ledger Entry
			l_Transaction = new Ledger();
			l_Transaction.setInvoice(p_Invoice);
			l_Transaction.setSettlement(p_Method);
			l_Transaction.setSettlementDate(p_Date);
			l_Transaction.setCashier(m_Login.getCurrentUser());
			l_Transaction.setCredit(p_PaidAmount);
			l_Transaction.setDebit(p_ReturnedAmount);
			l_Transaction = saveLedger(l_Transaction);			
		}

		return p_Invoice;
	}

///**
// * @param p_Payment
// * @param p_Deduction
// * @param p_Invoice
// * @return
// */
//private double calculateNewBalance (Double p_OldBalance, Double p_Payment, Double p_Deduction)
//{
//	BigDecimal l_Payment;
//	BigDecimal l_Deduction;
//	BigDecimal l_Balance;
//	
//	l_Payment 	= new BigDecimal(p_Payment).setScale (2,RoundingMode.HALF_DOWN);
//	l_Deduction = new BigDecimal(p_Deduction).setScale (2,RoundingMode.HALF_DOWN);
//	l_Balance   = new BigDecimal(p_OldBalance).setScale (2,RoundingMode.HALF_DOWN);
//	
//	l_Balance   = l_Balance.subtract(l_Payment.subtract(l_Deduction));
//	
//	return l_Balance.doubleValue();
//}

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