/*******************************************************************************
 * 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.gui.reminder;

import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Account;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Invoice;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.InvoiceStub;
import lu.tudor.santec.gecamed.billing.ejb.session.beans.ReminderInvoiceStubBean;
import lu.tudor.santec.gecamed.billing.ejb.session.beans.SettlementBean;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.InvoiceInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.ReminderInvoiceStubInterface;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.SettlementInterface;
import lu.tudor.santec.gecamed.billing.gui.BillingModule;
import lu.tudor.santec.gecamed.billing.gui.event.invoice.InvoiceChangeEvent;
import lu.tudor.santec.gecamed.billing.gui.event.invoice.InvoiceEventDispatcher;
import lu.tudor.santec.gecamed.billing.gui.event.invoice.InvoiceEventSource;
import lu.tudor.santec.gecamed.billing.gui.event.invoice.InvoiceListener;
import lu.tudor.santec.gecamed.billing.gui.invoice.InvoiceListModel;
import lu.tudor.santec.gecamed.billing.gui.invoice.stub.InvoiceStubUtils;
import lu.tudor.santec.gecamed.billing.utils.BillingAdminSettings;
import lu.tudor.santec.gecamed.core.gui.GECAMedTab;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.PhysicianListener;
import lu.tudor.santec.gecamed.core.gui.utils.SwingWorker;
import lu.tudor.santec.gecamed.core.gui.widgets.TabMenu;
import lu.tudor.santec.gecamed.core.gui.widgets.progress.ProgressDialog;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateCondition;
import lu.tudor.santec.gecamed.core.utils.querybuilder.HibernateOperator;
import lu.tudor.santec.gecamed.core.utils.querybuilder.WhereClause;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.i18n.Relocalizable;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

//***************************************************************************
//* Class Definition and Members                                            *
//***************************************************************************

public class ReminderPanel extends GECAMedTab implements InvoiceEventSource,
													     InvoiceListener,
														 ChangeListener,
														 PropertyChangeListener,
														 ListSelectionListener,
														 PhysicianListener,
														 Relocalizable
{
	private static final long serialVersionUID = 1L;

	private JTabbedPane					m_ReminderTabs;
	
	private ReminderInvoicesPanel		m_OpenInvoicesPanel;
	private ReminderInvoicesPanel		m_UnpaidInvoicesPanel;
	private ReminderInvoicesPanel		m_FirstNoticesPanel;
	private ReminderInvoicesPanel		m_SecondNoticesPanel;
	private ReminderInvoicesPanel		m_PaymentOrdersPanel;

	private ReminderInvoicesPanel		m_CurrentPanel;
	private int							m_CurrentTab;
	
	private Invoice						m_Invoice;
	private InvoiceStub					m_InvoiceStub;
	private Account						m_DefaultAccount;
	
	private int							m_QueryMode;
	private SwingWorker  				m_BulkWorker;
	private ProgressDialog				m_ProgressDialog;
	
	private TabMenu						m_TabMenu;
	
	private Long						m_InvoiceCount;
	private boolean						m_Busy;
	private boolean						m_Aborted;
	private boolean						m_OnlyExpired;
	
	private InvoiceStubUtils			m_InvoiceStubUtils;
	
	private SettlementInterface			m_SettlementInterface;

	private WhereClause					m_SearchCriteria;
	
	private Collection <InvoiceStub>	m_InvoiceStubs;
	private InvoiceEventDispatcher		m_InvoiceListeners;
	
	private int							m_MaximumNumberOfInvoices = 500;
	
	private Collection <Integer>		m_MissingIDs;
	private Collection <Integer>		m_ObsoleteIDs;
	private Collection <Integer>		m_OutdatedIDs;
	
	private static Pattern 
    
    c_EndingZerosPattern = Pattern.compile ("^(.*?)0+?",Pattern.CASE_INSENSITIVE);

	private static Logger m_Logger = Logger.getLogger (ReminderPanel.class.getName());

	private static final String [] m_TabTranslatrixKeys = 
		{
		/* Open */ 		"ReminderPanel.OpenInvoicesTab",
		/* Unpaid */ 	"ReminderPanel.UnpaidInvoicesTab",
		/* First */ 	"ReminderPanel.FirstNoticesTab",
		/* Second */ 	"ReminderPanel.SecondNoticesTab",
		/* Order */ 	"ReminderPanel.PaymentOrdersTab"	
		};

	private static final Collection <Integer> m_Dependencies = new ArrayList <Integer> ();
    
	static 	{
    		m_Dependencies.add(InvoiceInterface.c_PatientDependency);
    		m_Dependencies.add(InvoiceInterface.c_ModifierDependency);
	    	}

	
//***************************************************************************
//* Class Constants				                                            *
//***************************************************************************
	
	public static final int c_OpenInvoicesTab 		= 0;
	public static final int c_UnpaidInvoicesTab 	= 1;
	public static final int c_FirstNoticeTab 		= 2;
	public static final int c_SecondNoticeTab 		= 3;
	public static final int c_DemandsForPaymentTab 	= 4;

	private final static int	c_InvoiceChunkSize 	= 20;
	
	private final static int	c_InterruptWaitTime	= 500;
	private final static int	c_MaxWaitCycles		= 10;
	
	private final static String c_Columns= "fill:pref:grow";		
	private final static String c_Rows=    "fill:pref:grow";

//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor					                                            *
//***************************************************************************
//---------------------------------------------------------------------------
	
public ReminderPanel ()
	{
	CellConstraints	l_Constraints;
	FormLayout		l_Layout;
	MouseAdapter	l_MouseAdapter;
	String[]		l_Filler;
	
	Invoice.setExpiryPeriod ((Integer)BillingModule.getSetting(BillingAdminSettings.c_DuePeriodSetting));
	
	l_Constraints  	= new CellConstraints();
	l_Layout		= new FormLayout(c_Columns, c_Rows);

	this.setLayout(l_Layout);
	this.setOpaque(false);

	m_InvoiceListeners = new InvoiceEventDispatcher ();
	
	m_MissingIDs = new Vector <Integer> ();
	m_ObsoleteIDs = new Vector <Integer> ();
	m_OutdatedIDs = new Vector <Integer> ();
	
	m_ReminderTabs = new JTabbedPane ();
	m_ReminderTabs.addChangeListener(this);
	
   	//-----------------------------------------------------------------------
   	// Setup a MouseAdapter to intercept double-clicks on invoice. Double-click
   	// on invoice ought to open invoice in Editor Panel.
    //-----------------------------------------------------------------------
  	
   	l_MouseAdapter = new MouseAdapter()
    		{
    		public void mouseClicked(MouseEvent p_Event)
    			{
    			if (p_Event.getClickCount() == 2)
    				{
    				editSelectedInvoice (); 
    				}
    			}
    		};
	
	m_OpenInvoicesPanel = new ReminderInvoicesPanel (c_OpenInvoicesTab);
	m_OpenInvoicesPanel.addListSelectionListener(this);
	m_OpenInvoicesPanel.addPropertyChangeListener(this);
	m_OpenInvoicesPanel.addMouseListener (l_MouseAdapter);
	
	m_UnpaidInvoicesPanel = new ReminderInvoicesPanel (c_UnpaidInvoicesTab);
	m_UnpaidInvoicesPanel.addListSelectionListener(this);
	m_UnpaidInvoicesPanel.addPropertyChangeListener(this);
	m_UnpaidInvoicesPanel.addMouseListener (l_MouseAdapter);
	
	m_FirstNoticesPanel = new ReminderInvoicesPanel (c_FirstNoticeTab);
	m_FirstNoticesPanel.addListSelectionListener(this);
	m_FirstNoticesPanel.addPropertyChangeListener(this);
	m_FirstNoticesPanel.addMouseListener (l_MouseAdapter);
	
	m_SecondNoticesPanel = new ReminderInvoicesPanel (c_SecondNoticeTab);
	m_SecondNoticesPanel.addListSelectionListener(this);
	m_SecondNoticesPanel.addPropertyChangeListener(this);
	m_SecondNoticesPanel.addMouseListener (l_MouseAdapter);
	m_SecondNoticesPanel.setDefaultAccount(this.getDefaultAccount());
	
	m_PaymentOrdersPanel = new ReminderInvoicesPanel (c_DemandsForPaymentTab);
	m_PaymentOrdersPanel.addListSelectionListener(this);
	m_PaymentOrdersPanel.addPropertyChangeListener(this);
	m_PaymentOrdersPanel.addMouseListener (l_MouseAdapter);
	
	l_Filler = new String [2];
	l_Filler [0] = "-";
	l_Filler [1] = "-";
	
	m_ReminderTabs.add(Translatrix.getTranslationString(m_TabTranslatrixKeys[c_OpenInvoicesTab],l_Filler), m_OpenInvoicesPanel);
	m_ReminderTabs.add(Translatrix.getTranslationString(m_TabTranslatrixKeys[c_UnpaidInvoicesTab],l_Filler), m_UnpaidInvoicesPanel);
	m_ReminderTabs.add(Translatrix.getTranslationString(m_TabTranslatrixKeys[c_FirstNoticeTab],l_Filler), m_FirstNoticesPanel);
	m_ReminderTabs.add(Translatrix.getTranslationString(m_TabTranslatrixKeys[c_SecondNoticeTab],l_Filler), m_SecondNoticesPanel);
	m_ReminderTabs.add(Translatrix.getTranslationString(m_TabTranslatrixKeys[c_DemandsForPaymentTab],l_Filler), m_PaymentOrdersPanel);
	
	if ((Boolean)BillingModule.getUserSetting("General.ShowTabIcons"))
		{
		m_ReminderTabs.setIconAt(c_OpenInvoicesTab, BillingModule.getIconResource ("open_tab.png"));
		m_ReminderTabs.setIconAt(c_UnpaidInvoicesTab, BillingModule.getIconResource ("unpaid_tab.png"));
		m_ReminderTabs.setIconAt(c_FirstNoticeTab, BillingModule.getIconResource ("first_notice_tab.png"));
		m_ReminderTabs.setIconAt(c_SecondNoticeTab, BillingModule.getIconResource ("second_notice_tab.png"));
		m_ReminderTabs.setIconAt(c_DemandsForPaymentTab, BillingModule.getIconResource ("payment_order_tab.png"));
		}
	
	this.add (m_ReminderTabs, l_Constraints.xywh(1, 1, 1, 1));
	
	m_CurrentTab = -1;
	m_QueryMode  = -1;
		
	m_MaximumNumberOfInvoices = (Integer) BillingModule.getSetting(BillingAdminSettings.c_MaxInvoicesSetting);
	m_Busy = false;
	m_ProgressDialog = new ProgressDialog ();
	
	this.buildTabMenu();
	
	BillingModule.getInstance().addInvoiceListener(this);
	}
		
//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives                                       						*
//***************************************************************************
//---------------------------------------------------------------------------

public void buildTabMenu ()
	{
	Action	l_Action;
	
	m_TabMenu = new TabMenu (m_ReminderTabs,TabMenu.c_AllTabs);
	
	l_Action = new AbstractAction( Translatrix.getTranslationString("ReminderPanel.Reload"),
								   BillingModule.getIconResource("reload_tiny.png")) 
		{
		private static final long serialVersionUID = 1L;

		public void actionPerformed(ActionEvent p_Event) 
			{
			updateTabCounts();
			updateListing (m_QueryMode);
			}
		};
	
	m_TabMenu.add(l_Action);

	if ((Boolean) BillingModule.getSetting(BillingAdminSettings.c_OnlyExpiredInvoicesSetting))
		{
		l_Action = new AbstractAction( Translatrix.getTranslationString("ReminderPanel.LoadAll"),
								   	   BillingModule.getScaledIcon("load_invoices.png", 16)) 
			{
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent p_Event) 
				{
				updateListing (m_QueryMode,false);
				}
			};
		}
	else
		{
		l_Action = new AbstractAction( Translatrix.getTranslationString("ReminderPanel.OnlyExpired"),
								   	   BillingModule.getScaledIcon("expired.png", 16)) 
			{
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent p_Event) 
				{
				updateListing (m_QueryMode,true);
				}
			};
		
		}
			
	m_TabMenu.add(l_Action);
	}

//---------------------------------------------------------------------------
/**
* The private getSettlementInterface returns an instance of the SettlementBean
* session bean. On the first call, the SettlementBean will actualy be looked up
* via JNDI. Once it has been found, the reference to the bean will be stored
* in a private data member. Doing so avoids JNDI lookups on later calls.
* @return an instance of the SettlementBean session bean.
*/
//---------------------------------------------------------------------------

private SettlementInterface getSettlementInterface ()
	{
	if (m_SettlementInterface != null) return m_SettlementInterface;

	try {
		m_SettlementInterface = (SettlementInterface) ManagerFactory.getRemote(SettlementBean.class);
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.log(Level.ERROR,"Failed to get Settlement Interface",p_Exception);
		}

	return m_SettlementInterface;
	}

//---------------------------------------------------------------------------

private ReminderInvoiceStubInterface getInvoiceStubInterface ()
	{
	ReminderInvoiceStubInterface l_InvoiceStubInterface = null;
	
	try {
		l_InvoiceStubInterface = (ReminderInvoiceStubInterface) ManagerFactory.getStatefulRemote(ReminderInvoiceStubBean.class);
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.log(Level.ERROR,"Failed to get InvoiceStub Interface",p_Exception);
		}

	return l_InvoiceStubInterface;
	}

//---------------------------------------------------------------------------

private void releaseInvoiceStubInterface (ReminderInvoiceStubInterface p_Interface)
	{
	if (p_Interface == null) return;
	
	try {
		p_Interface.remove();
		}
	catch (Exception p_Exception)	
		{
		m_Logger.log(Level.INFO,"ReminderInvoiceStubBean Removed!",p_Exception);
		}
	}

//---------------------------------------------------------------------------

private Invoice fetchInvoiceForStub (InvoiceStub p_InvoiceStub)
	{
	if (m_InvoiceStubUtils == null) m_InvoiceStubUtils = new InvoiceStubUtils ();
	
	return m_InvoiceStubUtils.fetchInvoiceForStub (p_InvoiceStub, m_Dependencies);
	}

//---------------------------------------------------------------------------

private Account	getDefaultAccount ()
	{
	SettlementInterface	l_SettlementInterface;
		
	if (m_DefaultAccount != null) return m_DefaultAccount;
	
	l_SettlementInterface = this.getSettlementInterface();
	if (l_SettlementInterface == null) return null;
	
	try	{
		m_DefaultAccount = l_SettlementInterface.getAccountByID ((Integer)BillingModule.getSetting(BillingAdminSettings.c_DefaultAccountIdSetting));
		}
	catch (Exception p_Exception)
		{
		m_Logger.log(Level.ERROR,"Failed to fetch default Account!",p_Exception);
		}
	
	return m_DefaultAccount;
	}

//---------------------------------------------------------------------------

private Collection <Long> getAllInvoiceCounts () 
	{
	ReminderInvoiceStubInterface 	l_InvoiceStubInterface = null;
	Collection <Long>				l_AllInvoiceCounts	   = null;
	
	l_InvoiceStubInterface = this.getInvoiceStubInterface();
	
	if (l_InvoiceStubInterface == null) return null;
	
	try {
		if ((Boolean)BillingModule.getSetting(BillingAdminSettings.c_SplitBillingSetting))
			l_InvoiceStubInterface.setPhysician(BillingModule.getCurrentPhysician());
		
		l_AllInvoiceCounts = l_InvoiceStubInterface.getAllInvoiceStubCounts ();
		} 
	catch (Exception p_Exception) 
		{
		l_InvoiceStubInterface.close();
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}

	
	return l_AllInvoiceCounts;
	}

//---------------------------------------------------------------------------

private Long getCountOfInvoices (int p_QueryMode, boolean p_OnlyExpired) 
	{
	ReminderInvoiceStubInterface 	l_InvoiceStubInterface = null;
	Long					 		l_Count				   = 0L;
	
	l_InvoiceStubInterface = this.getInvoiceStubInterface();
	
	if (l_InvoiceStubInterface == null) return l_Count;
	
	try {
		if ((Boolean)BillingModule.getSetting(BillingAdminSettings.c_SplitBillingSetting))
			l_InvoiceStubInterface.setPhysician(BillingModule.getCurrentPhysician());
		
		l_InvoiceStubInterface.setOnlyExpired (p_OnlyExpired);		
//		l_InvoiceStubInterface.setCriterions(m_Criterions);
		l_InvoiceStubInterface.setWhereClause(m_SearchCriteria);
	
		
		l_Count = l_InvoiceStubInterface.getInvoiceStubCount (p_QueryMode);
		} 
	catch (Exception p_Exception) 
		{
		l_InvoiceStubInterface.close();
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	
	this.releaseInvoiceStubInterface(l_InvoiceStubInterface);

	return l_Count;
	}

//---------------------------------------------------------------------------

private Collection <Integer> filterByQueryMode (int p_QueryMode, Collection <Integer> p_SubSetIDs)
	{
	ReminderInvoiceStubInterface 	l_InvoiceStubInterface = null;
	Collection <Integer>			l_FilteredIDs = null;
	
	if ((p_SubSetIDs == null) || (p_SubSetIDs.isEmpty())) return p_SubSetIDs;
	
	l_InvoiceStubInterface = this.getInvoiceStubInterface();

	if (l_InvoiceStubInterface == null) return p_SubSetIDs;

	try {
		l_FilteredIDs = l_InvoiceStubInterface.filterByQueryMode(p_QueryMode, p_SubSetIDs);
		}
	catch (Exception p_Exception) 
		{
		l_InvoiceStubInterface.close();
		m_Logger.log(Level.ERROR,"Failed to filter IDs for Query mode!",p_Exception);
		}
	
	return l_FilteredIDs;
	}

//---------------------------------------------------------------------------

private Collection <InvoiceStub> getInvoiceStubsByIDs (Collection <Integer> p_InvoiceIDs)
	{
	ReminderInvoiceStubInterface 	l_InvoiceStubInterface = null;
	Collection <InvoiceStub>		l_InvoiceStubs = null;
	
	if ((p_InvoiceIDs == null) || (p_InvoiceIDs.isEmpty())) return l_InvoiceStubs;
	
	l_InvoiceStubInterface = this.getInvoiceStubInterface();

	if (l_InvoiceStubInterface == null) return l_InvoiceStubs;

	try {
		l_InvoiceStubs = l_InvoiceStubInterface.getInvoiceStubsByIds(p_InvoiceIDs);
		}
	catch (Exception p_Exception) 
		{
		l_InvoiceStubInterface.close();
		m_Logger.log(Level.ERROR,"Failed to get Invoice Stubs by IDs!",p_Exception);
		}
	
	return l_InvoiceStubs;
	}

//---------------------------------------------------------------------------

public void reload() {
	reflectTabSelection(true);
}

public boolean reflectTabSelection () {
	return reflectTabSelection(false);
}

public boolean reflectTabSelection (boolean force)
	{
	int	l_SelectedTab;
	int	l_Query = -1;

	l_SelectedTab = m_ReminderTabs.getSelectedIndex();
	if (!force && l_SelectedTab == m_CurrentTab) return false;
		
//	m_Criterions = null;
	m_SearchCriteria = null;
	m_Invoice    = null;
	
	switch (l_SelectedTab)
		{
		case c_OpenInvoicesTab: 	l_Query = ReminderInvoiceStubBean.c_OpenInvoicesQuery;
							    	break;
			
		case c_UnpaidInvoicesTab: 	l_Query = ReminderInvoiceStubBean.c_UnpaidInvoicesQuery;
	    							break;
	    							
		case c_FirstNoticeTab: 		l_Query = ReminderInvoiceStubBean.c_FirstNoticeInvoicesQuery;
	    							break;
	    							
		case c_SecondNoticeTab: 	l_Query = ReminderInvoiceStubBean.c_SecondNoticeInvoicesQuery;
	    							break;
	    							
		case c_DemandsForPaymentTab: 	l_Query = ReminderInvoiceStubBean.c_DemandOfPaymentInvoicesQuery;
	    							break;
		}
	
	this.updateListing(l_Query);
	
	return true;
	}

//---------------------------------------------------------------------------

public void reflectSelectionChange () 
	{
	InvoiceStub			l_InvoiceStub;
	InvoiceChangeEvent	l_Event;

	l_InvoiceStub = m_CurrentPanel.getSelectedInvoiceStub();
	if (l_InvoiceStub == null) return;
	
	m_Invoice = this.fetchInvoiceForStub(l_InvoiceStub);
	if (m_Invoice == null) return;
	
	m_InvoiceStub = l_InvoiceStub;
    l_Event = new InvoiceChangeEvent (this, InvoiceChangeEvent.c_InvoiceSelectionChanged,m_Invoice);
    m_InvoiceListeners.notifyInvoiceListeners(l_Event);
	}

//---------------------------------------------------------------------------

private boolean isFullySettled (Invoice p_Invoice)
	{
	return (p_Invoice.getBalance() == 0.0);
	}

//---------------------------------------------------------------------------

public void updateTabCounts ()
	{
	Collection <Long>	l_AllInvoiceCounts;
	Iterator   <Long>	l_CountIterator;
	
	Long		l_ExpiredCount;
	Long		l_TotalCount;
	String[]	l_Filler;
	int			l_Tab;
	
	l_Filler = new String [2];

	l_AllInvoiceCounts = this.getAllInvoiceCounts();
	if (l_AllInvoiceCounts != null)
		{
		l_CountIterator = l_AllInvoiceCounts.iterator();
		for (l_Tab = c_OpenInvoicesTab; l_Tab <= c_DemandsForPaymentTab; l_Tab++)
			{
			l_ExpiredCount = (l_CountIterator.hasNext())?l_CountIterator.next():0L;	
			l_TotalCount   = (l_CountIterator.hasNext())?l_CountIterator.next():0L;	
			l_Filler [0] = l_ExpiredCount.toString();
			l_Filler [1] = l_TotalCount.toString();
		
			m_ReminderTabs.setTitleAt(l_Tab, Translatrix.getTranslationString(m_TabTranslatrixKeys[l_Tab],l_Filler));
			}	
		}
	}

//---------------------------------------------------------------------------

private void determineChanges (Collection <Integer> p_ChangedIDs)
	{
	Collection <Integer>		l_FilteredIDs;
	Collection <Integer>		l_CurrentIDs;

	l_FilteredIDs = this.filterByQueryMode (m_QueryMode, p_ChangedIDs);
	l_CurrentIDs  = m_CurrentPanel.getInvoiceIDs();
		
	// Determine Missing IDs
	
	m_MissingIDs.clear();
	m_MissingIDs.addAll (l_FilteredIDs);
	m_MissingIDs.removeAll (l_CurrentIDs);
	
	// Determine Obsolete IDs
	
	m_ObsoleteIDs.clear();
	m_ObsoleteIDs.addAll (p_ChangedIDs);
	m_ObsoleteIDs.removeAll (l_FilteredIDs);
	
	// Determine IDs requiring an Update
	
	m_OutdatedIDs.clear();
	m_OutdatedIDs.addAll (l_FilteredIDs);
	m_OutdatedIDs.retainAll (l_CurrentIDs);
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Swing Worker Thread                                                     *
//***************************************************************************
//---------------------------------------------------------------------------

Object InvoiceBulkOperation ()
	{
	this.fetchInvoiceStubs();
	return "";
	}

//---------------------------------------------------------------------------

private synchronized void fetchInvoiceStubs ()
	{
	ReminderInvoiceStubInterface	l_InvoiceStubInterface  = null;
	Collection <InvoiceStub>		l_InvoiceStubs		 	= null;
	String []						l_Filler;
	
	l_InvoiceStubInterface = getInvoiceStubInterface ();

	if (l_InvoiceStubInterface == null) return;

	m_InvoiceStubs = new LinkedHashSet <InvoiceStub> ();

	try {	
		if ((Boolean)BillingModule.getSetting(BillingAdminSettings.c_SplitBillingSetting)) {
			l_InvoiceStubInterface.setPhysician(BillingModule.getCurrentPhysician());
		}
		
		l_InvoiceStubInterface.setOnlyExpired(m_OnlyExpired);
			
		m_MaximumNumberOfInvoices = (Integer) BillingModule.getSetting(BillingAdminSettings.c_MaxInvoicesSetting);
				
		l_InvoiceStubInterface.setWhereClause(m_SearchCriteria);
		l_InvoiceStubInterface.buildInvoiceStubQuery (m_QueryMode);
		l_InvoiceStubInterface.setFirstInvoiceStub(0);
		l_InvoiceStubInterface.setNumberOfInvoiceStubs (c_InvoiceChunkSize);
		l_InvoiceStubInterface.setCeiling((Integer) BillingModule.getSetting(BillingAdminSettings.c_MaxInvoicesSetting));
		
		m_InvoiceCount = Math.min(m_InvoiceCount, m_MaximumNumberOfInvoices);
		
		if (m_InvoiceCount >= c_InvoiceChunkSize)
			{	
			l_Filler = new String[1];
			l_Filler [0] = m_InvoiceCount.toString();
			
			m_ProgressDialog.setCancelable(true);
			m_ProgressDialog.setProgressBarSpan(0,m_InvoiceCount.intValue());
		
			m_ProgressDialog.setTitle("ReminderInvoicesPanel.FetchingInvoicesTitle");
			m_ProgressDialog.setMessage("ReminderInvoicesPanel.FetchingInvoicesMessage",null);
			m_ProgressDialog.setTask("ReminderInvoicesPanel.FetchingInvoicesTask", l_Filler);
			m_ProgressDialog.pack();
			
			MainFrame.showDialogCentered (m_ProgressDialog);
			}
		
		l_Filler = new String [2];
		
		do	{
			l_InvoiceStubs = l_InvoiceStubInterface.getNextInvoiceStubs();
			if (l_InvoiceStubs != null) 
				{
				m_InvoiceStubs.addAll(l_InvoiceStubs);
				
				if (m_InvoiceCount.intValue() > c_InvoiceChunkSize)
					{					
					l_Filler [0] = Integer.valueOf (m_InvoiceStubs.size()).toString();
					l_Filler [1] = m_InvoiceCount.toString();
					
					m_ProgressDialog.setProgress(m_InvoiceStubs.size(), 
												 "ReminderInvoicesPanel.FetchingInvoicesProgress",
												 l_Filler);
					}
				
				m_Aborted = m_ProgressDialog.wasCanceled();
				}
			} 
		while (	   (m_Aborted == false)
				&& (l_InvoiceStubs != null) 
				&& (l_InvoiceStubs.size() == c_InvoiceChunkSize)); 
		
		if (m_Aborted) 
			{
			l_InvoiceStubInterface.close();
			}
		m_Busy = false;
		}
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		l_InvoiceStubInterface.close();
		}

	this.releaseInvoiceStubInterface (l_InvoiceStubInterface);

	m_ProgressDialog.setVisible(false);
	m_ProgressDialog.reset();
	
	if ((m_InvoiceStubs != null) && (m_Aborted == false)) 
		{
		l_Filler = new String[1];
		l_Filler [0] = m_InvoiceCount.toString();
				
		MainFrame.getInstance().showMessage(Translatrix.getTranslationString("StatementInvoicesPanel.InvoicesFetched",l_Filler));		
		}
	
	MainFrame.getInstance().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

//---------------------------------------------------------------------------

private void startBulkOperation (int p_QueryMode)
	{		
	//m_QueryMode = new Integer (p_QueryMode);
	m_QueryMode = p_QueryMode;
	
	m_BulkWorker = new SwingWorker() 
		{
		public Object construct() 
			{
			return InvoiceBulkOperation ();
			}
		public void start ()
			{
			m_Busy 	  = true;
			MainFrame.getInstance().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			m_Aborted = false;
			super.start();
			}
		public void finished ()
			{
			MainFrame.getInstance().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			m_Aborted = false;
			m_Busy    = false;
			
			m_CurrentPanel.setInvoiceStubs (m_InvoiceStubs);
			m_CurrentPanel.selectFirstInvoiceStub();
			m_CurrentPanel.repaint();
			}
		public void interrupt ()
			{
			m_Aborted = true;
			MainFrame.getInstance().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			super.interrupt();
			}
		};

	m_BulkWorker.start ();  	
	}

//---------------------------------------------------------------------------

private void abortBulkOperation ()
{
if (m_BulkWorker != null) m_BulkWorker.interrupt();
}

//---------------------------------------------------------------------------

private synchronized boolean waitUntilNoLongerBusy ()
{
int	l_WaitCycles = 0;

while ((m_Busy) && (l_WaitCycles < c_MaxWaitCycles))
	{
	try	{
		this.wait (c_InterruptWaitTime);
		}
	catch (Exception p_Exception)
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	l_WaitCycles ++;		
	}

if (l_WaitCycles < c_MaxWaitCycles) 
	 return true;
else return false;
}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body                                       						*
//***************************************************************************
//---------------------------------------------------------------------------

public void preparetoShowup()
	{
//	Invoice l_Invoice;
	
	this.updateTabCounts();
	if (m_CurrentPanel == null) this.reflectTabSelection ();
	
	InvoiceChangeEvent event = new InvoiceChangeEvent(m_CurrentPanel, 
			InvoiceChangeEvent.c_InvoiceSelectionChanged, getSelectedInvoice());
	BillingModule.getInstance().invoiceChanged(event);
	
//	if (this.reflectTabSelection () == false)
//		{
//		if (m_InvoiceStub != null)
//			{
//			l_Invoice = this.fetchInvoiceForStub(m_InvoiceStub);
//			if ((l_Invoice != null) && this.isFullySettled(l_Invoice))
//				{
//				m_CurrentPanel.removeInvoiceStub(m_InvoiceStub);
//				m_CurrentPanel.selectFirstInvoiceStub();
//				}
//			}
//		}
	}

//---------------------------------------------------------------------------

public void preparetoHide ()
	{
//	Patient l_Patient;
//
//	if (this.isShowing() && (m_Invoice != null))
//		{
//		l_Patient = m_Invoice.getPatient();
//		if (l_Patient != null) BillingModule.setCurrentPatient (l_Patient);
//		}
	}
//---------------------------------------------------------------------------

public void addInvoiceListener(InvoiceListener p_Listener) 
	{
	m_InvoiceListeners.addInvoiceListener(p_Listener);
	}

//---------------------------------------------------------------------------

public void removeInvoiceListener(InvoiceListener p_Listener) 
	{
	m_InvoiceListeners.removeInvoiceListener(p_Listener);
	}

//---------------------------------------------------------------------------

public void relocalize() 
	{
	String [] l_Filler;
	
	l_Filler = new String [2];
	l_Filler [0] = "-";
	l_Filler [1] = "-";

	if (m_ReminderTabs != null)
		{
		m_ReminderTabs.setTitleAt(c_OpenInvoicesTab, 	Translatrix.getTranslationString("ReminderPanel.OpenInvoicesTab",l_Filler));
		m_ReminderTabs.setTitleAt(c_UnpaidInvoicesTab, 	Translatrix.getTranslationString("ReminderPanel.UnpaidInvoicesTab",l_Filler));
		m_ReminderTabs.setTitleAt(c_FirstNoticeTab, 	Translatrix.getTranslationString("ReminderPanel.FirstNoticesTab",l_Filler));
		m_ReminderTabs.setTitleAt(c_SecondNoticeTab, Translatrix.getTranslationString("ReminderPanel.SecondNoticesTab",l_Filler));
		m_ReminderTabs.setTitleAt(c_DemandsForPaymentTab, 	Translatrix.getTranslationString("ReminderPanel.PaymentOrdersTab",l_Filler));
		}
	}

//---------------------------------------------------------------------------

public void updateListing (int p_QueryMode, boolean p_OnlyExpired)
	{
	if (p_QueryMode < 0) return;
	
	if (m_Busy)
		{
		this.abortBulkOperation();
		this.waitUntilNoLongerBusy();
		}

	m_OnlyExpired	= p_OnlyExpired;
	m_CurrentTab 	= m_ReminderTabs.getSelectedIndex();	
	m_CurrentPanel 	= (ReminderInvoicesPanel)m_ReminderTabs.getSelectedComponent();
	
	m_InvoiceCount = this.getCountOfInvoices(p_QueryMode,m_OnlyExpired);
	this.startBulkOperation (p_QueryMode);
	}

//---------------------------------------------------------------------------



public void updateListing (int p_QueryMode)
	{
	boolean	l_OnlyExpired;
	
	l_OnlyExpired  = (Boolean) BillingModule.getSetting(BillingAdminSettings.c_OnlyExpiredInvoicesSetting);

	this.updateListing (p_QueryMode,l_OnlyExpired); 
	}

//---------------------------------------------------------------------------

public Invoice getSelectedInvoice ()
	{
	return m_Invoice;
	}

//---------------------------------------------------------------------------

private void editSelectedInvoice ()
	{
	if (m_Invoice != null) BillingModule.getInstance().editInvoice(m_Invoice);
	}

//---------------------------------------------------------------------------

public void removeSelectedInvoice (Invoice p_Invoice)
	{
	if (p_Invoice != null)
		{
		if (p_Invoice.equals (m_Invoice) && this.isFullySettled(p_Invoice))  
			{
			m_CurrentPanel.removeInvoiceStub(m_InvoiceStub);
			m_CurrentPanel.selectFirstInvoiceStub();
			}
		} 
	}

//---------------------------------------------------------------------------

public void stateChanged(ChangeEvent p_Event) 
	{
	if (p_Event.getSource() == m_ReminderTabs)
		{
		this.reflectTabSelection ();
		}
	}

//---------------------------------------------------------------------------

public void valueChanged (ListSelectionEvent p_Event) 
	{
	if (!p_Event.getValueIsAdjusting())
		{
		if (m_CurrentPanel != null) this.reflectSelectionChange();
		}
	}

//---------------------------------------------------------------------------

public void propertyChange(PropertyChangeEvent p_Event) 
	{
	String 	l_Column;
	Object 	l_Criterion;
	Matcher	l_SSNMatcher;
	HibernateCondition	l_Condition;
	
	if (p_Event.getSource() == m_CurrentPanel)
		{		
		if (ReminderInvoicesPanel.c_SearchCriterionChanged.equals(p_Event.getPropertyName()))
			{
			l_Column 	 = (String) p_Event.getOldValue();
			l_Criterion = 			p_Event.getNewValue();		
		
			if (l_Criterion == null) 
				{
				m_SearchCriteria = null;
				this.updateListing(m_QueryMode);				
				return;
				}
			else 
				{
				m_SearchCriteria = new WhereClause ();
				m_SearchCriteria.setOperator (HibernateOperator.c_AndOperator);
				}
				
			
			if (InvoiceStubListModel.c_PatientNameHeader.equals (l_Column))
				{	
				l_Condition   = new HibernateCondition ("patientName",
				  					    				HibernateOperator.c_LikeOperator,
				  					    				((String)l_Criterion) + "%");
				
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			else if (InvoiceStubListModel.c_PatientSSNHeader.equals (l_Column))
				{	
				l_SSNMatcher = c_EndingZerosPattern.matcher((String)l_Criterion);
				if (l_SSNMatcher.matches())
					{
					l_Criterion = l_SSNMatcher.group(1) + "%";
					}
				
				l_Condition   = new HibernateCondition ("patientSSN",
				  					    				HibernateOperator.c_LikeOperator,
				  					    				((String)l_Criterion));
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			else if (InvoiceStubListModel.c_InvoiceNumberHeader.equals (l_Column))
				{	
				l_Condition   = new HibernateCondition ("id",
				  					    				HibernateOperator.c_EqualOperator,
				  					    				((Integer)l_Criterion));
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			else if (InvoiceListModel.c_InvoiceDateHeader.equals (l_Column))
				{
				l_Condition   = new HibernateCondition ("invoiceDate",
				  					    				HibernateOperator.c_EqualOperator,
				  					    				"invoiceDate",
				  					    				((Date)l_Criterion));
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			else if (InvoiceListModel.c_DueDateHeader.equals (l_Column))
				{
				l_Condition   = new HibernateCondition ("dueDate",
				  					    				HibernateOperator.c_EqualOperator,
				  					    				"dueDate",
				  					    				((Date)l_Criterion));
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			else if (InvoiceListModel.c_ReminderDateHeader.equals (l_Column))
				{
				l_Condition   = new HibernateCondition ("reminderDate",
				  					    				HibernateOperator.c_EqualOperator,
				  					    				"reminderDate",
				  					    				((Date)l_Criterion));
				m_SearchCriteria.addCondition (l_Condition);
				this.updateListing(m_QueryMode);
				}
			}
		else if (ReminderInvoicesPanel.c_InvoicesChanged.equals(p_Event.getPropertyName()))
			{
			this.updateTabCounts();
			this.updateListing(m_QueryMode);
			}
		}
	}

//---------------------------------------------------------------------------

public void physicianChanged (Physician physician) 
	{
	if ((Boolean)BillingModule.getSetting(BillingAdminSettings.c_SplitBillingSetting))
		{
		this.updateTabCounts();
		this.updateListing(m_QueryMode);
		}
	}

//---------------------------------------------------------------------------

public void invoiceChanged (InvoiceChangeEvent p_Event) 
	{
	Collection <Integer>	l_AffectedIDs;
	Invoice					l_Invoice;
	
	// If no Panel has been opened yet, then invoice changes can be ignored.
	
	if ((m_CurrentPanel == null) || (m_Busy)) return;

	if (p_Event.isMultiple())
		{
		l_AffectedIDs  = new Vector <Integer> (p_Event.getInvoiceIDs());
		this.processInvoiceChangeEvent(p_Event.getType(), l_AffectedIDs);
		this.updateTabCounts();
		}
	else
		{
		l_Invoice = p_Event.getInvoice();
		if ((l_Invoice != null) && (l_Invoice.isPersistent()))
			{
			l_AffectedIDs = new LinkedHashSet <Integer> ();
			l_AffectedIDs.add(l_Invoice.getId());
			this.processInvoiceChangeEvent(p_Event.getType(), l_AffectedIDs);
			this.updateTabCounts();
			}
		}
	}

//---------------------------------------------------------------------------

private void processInvoiceChangeEvent (int p_Type, Collection <Integer> p_InvoiceIDs)
	{
	switch (p_Type)
		{
		case InvoiceChangeEvent.c_InvoiceStatusChanged:
		
			processInvoiceStatusChangedEvent (p_InvoiceIDs);
			break;
			
		case InvoiceChangeEvent.c_InvoiceUpdated:	
			
			processInvoiceUpdatedEvent (p_InvoiceIDs);
			break;
			
		case InvoiceChangeEvent.c_InvoiceDeleted:
			
			processInvoiceDeletedEvent (p_InvoiceIDs);
			break;
		}
	}

//---------------------------------------------------------------------------

private void processInvoiceStatusChangedEvent (Collection <Integer> p_InvoiceIDs)
	{
	Collection <InvoiceStub>	l_InvoiceStubs;
	
	this.determineChanges (p_InvoiceIDs);

	l_InvoiceStubs = this.getInvoiceStubsByIDs (m_MissingIDs);
	m_CurrentPanel.addInvoiceStubs (l_InvoiceStubs);
	
	m_CurrentPanel.removeInvoiceStubs (m_ObsoleteIDs);
	}

//---------------------------------------------------------------------------

private void processInvoiceUpdatedEvent (Collection <Integer> p_InvoiceIDs)
	{
	Collection <InvoiceStub>	l_InvoiceStubs;
	
	this.determineChanges (p_InvoiceIDs);

	l_InvoiceStubs = this.getInvoiceStubsByIDs (m_MissingIDs);
	m_CurrentPanel.addInvoiceStubs (l_InvoiceStubs);
	
	l_InvoiceStubs = this.getInvoiceStubsByIDs (m_OutdatedIDs);
	m_CurrentPanel.updateInvoiceStubs(l_InvoiceStubs);
	
	m_CurrentPanel.removeInvoiceStubs (m_ObsoleteIDs);
	}

//---------------------------------------------------------------------------

private void processInvoiceDeletedEvent (Collection <Integer> p_InvoiceIDs)
	{
	this.determineChanges (p_InvoiceIDs);
	
	m_CurrentPanel.removeInvoiceStubs (m_ObsoleteIDs);
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* End Of Class                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
}
