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

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ejb.EJBException;
import javax.persistence.EntityExistsException;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableColumn;
import javax.swing.tree.TreePath;

import lu.tudor.santec.gecamed.billing.ejb.entity.beans.KeyValue;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.Rate;
import lu.tudor.santec.gecamed.billing.ejb.entity.beans.RateIndex;
import lu.tudor.santec.gecamed.billing.ejb.session.beans.NomenclatureBean;
import lu.tudor.santec.gecamed.billing.ejb.session.interfaces.NomenclatureInterface;
import lu.tudor.santec.gecamed.billing.gui.BillingModule;
import lu.tudor.santec.gecamed.billing.gui.event.keyvalue.KeyValueChangeEvent;
import lu.tudor.santec.gecamed.billing.gui.event.keyvalue.KeyValueListener;
import lu.tudor.santec.gecamed.core.gui.GECAMedColors;
import lu.tudor.santec.gecamed.core.gui.GECAMedIconNames;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.widgets.GECAMedBaseDialogImpl;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.i18n.Relocalizable;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.decorator.HighlighterFactory;

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

public class RatesPanel extends JPanel implements ActionListener,
											      TreeSelectionListener,
											      KeyValueListener,
												  Relocalizable
	{
	private static final long serialVersionUID = 1L;
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(RatesPanel.class.getName());
	
	private RateTreeTableModel		m_Rates;
	private JXTreeTable				m_RatesTable;
	private JScrollPane				m_RatesScroller;
	private RateRenderer			m_RateRenderer;
	private RateTreeNode			m_RootNode;
	
	private JButton					m_AddRateButton;
	private JButton					m_RemoveRateButton;
	private JButton					m_ReloadButton;
	
	private RateDialog				m_RateDialog;
	
	private NomenclatureInterface	m_NomenclatureInterface;

	private RateTreeNode m_AllCodesNode;

	private ArrayList<RateTreeNode> m_AllRates;

	private ArrayList<RateTreeNode> m_AllRateNodeList;
	
	private static Logger m_Logger = Logger.getLogger (RatesPanel.class.getName());

//---------------------------------------------------------------------------
//***************************************************************************
//* Constants	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

	private final static String c_Columns= 	"3dlu,fill:pref:grow,3dlu,fill:max(50dlu;pref)";
	
	private final static String c_Rows=    	"3dlu,fill:max(10dlu;pref),3dlu,fill:max(10dlu;pref)," +
											"3dlu,fill:max(10dlu;pref),fill:pref:grow";
     
      private static final int c_TOCColumnWidth 	= 60;
      private static final int c_LabelColumnWidth 	= 45;
      private static final int c_CATColumnWidth 	=  4;
      private static final int c_CACColumnWidth 	=  4;
      private static final int c_APCMColumnWidth 	=  4;
      private static final int c_ACMColumnWidth 	=  4;
      private static final int c_CoefficientWidth	=  8;
      private static final int c_ApplicabilityWidth	=  8;
      private static final int c_AmountColumnWidth	=  8;
      
 
      static final public int c_ColumnWidths [] = { c_TOCColumnWidth,
    	  											c_LabelColumnWidth,
    	  											c_CATColumnWidth,
    	  											c_CACColumnWidth,
    	  											c_APCMColumnWidth,
    	  											c_ACMColumnWidth,
    	  											c_CoefficientWidth,
    	  											c_ApplicabilityWidth,
    	  											c_AmountColumnWidth   
    	  											
      };

	private static final String c_AddRateButton    		= "RatesPanel.AddRateButton";
	private static final String c_RemoveRateButton 		= "RatesPanel.RemoveRateButton";

	private static final String c_AddRateTip    		= "RatesPanel.AddRateTip";
	private static final String c_RemoveRateTip 		= "RatesPanel.RemoveRateTip";

	private static final String c_CodeExistsTitle    	= "RatesPanel.CodeExistsTitle";
	private static final String c_CodeExistsMessage 	= "RatesPanel.CodeExistsMessage";

//	private static final String c_ProtectedRateTitle    = "RatesPanel.ProtectedRateTitle";
//	private static final String c_ProtectedRateMessage 	= "RatesPanel.ProtectedRateMessage";
	
	private static final RateTreeComparator RATE_TREE_COMPARATOR	= new RateTreeComparator();

//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public RatesPanel ()
	{
	CellConstraints			l_Constraints;
	FormLayout				l_Layout;	
	MouseAdapter			l_MouseAdapter;
	ImageIcon				l_Icon;
//	HighlightPredicate 		l_OddRowPredicate;
//	HighlightPredicate 		l_EvenRowPredicate;
//	Highlighter				l_OddRowHighlighter;
//	Highlighter				l_EvenRowHighlighter;
	int						l_Count;
	
	l_Constraints  	= new CellConstraints();
	l_Layout		= new FormLayout(c_Columns, c_Rows);

	this.setLayout(l_Layout);

    this.setOpaque(false);
	
    m_Rates       = new RateTreeTableModel(); 
    m_Rates.setKeyValues(getKeyValues());
    m_Rates.setRoot(this.buildTree());

    m_RatesTable  = new JXTreeTable();
    m_RatesTable.setTreeTableModel(m_Rates);
	m_RatesTable.setShowGrid(false, true);
    m_RatesTable.setRootVisible(true);
    m_RatesScroller = new JScrollPane (m_RatesTable);
	
	m_RatesTable.setTreeCellRenderer(new RateTreeCellRenderer());
 	  
	m_RateRenderer = new RateRenderer (m_RatesTable);
	
	for (l_Count=0; l_Count < m_Rates.getColumnCount(); l_Count++)
		{
    	switch (l_Count)
    		{
			case RateTreeTableModel.c_LabelColumn:
			case RateTreeTableModel.c_CATColumn:
			case RateTreeTableModel.c_CACColumn:
			case RateTreeTableModel.c_APCMColumn:
			case RateTreeTableModel.c_ACMColumn:
			case RateTreeTableModel.c_ApplicabilityColumn:
			case RateTreeTableModel.c_CoefficientColumn:
			case RateTreeTableModel.c_AmountColumn:
				
				if (m_Rates.getColumnClass(l_Count) != null)	
					m_RatesTable.setDefaultRenderer (m_Rates.getColumnClass(l_Count), m_RateRenderer);
    		}
		}
	
	m_RatesTable.setHighlighters(HighlighterFactory.createAlternateStriping(
    							 GECAMedColors.c_EvenLineBackground, 
    							 GECAMedColors.c_OddLineBackground));
	
//	l_EvenRowPredicate = new CoefficientPredicate(false);
//	l_OddRowPredicate  = new CoefficientPredicate(true);
		
//	l_OddRowHighlighter  = new ColorHighlighter (GECAMedColors.c_GreenEvenLineBackground,null,l_EvenRowPredicate);
//	l_EvenRowHighlighter = new ColorHighlighter (GECAMedColors.c_GreenOddLineBackground,null,l_OddRowPredicate);

//	m_RatesTable.addHighlighter(l_OddRowHighlighter);
//	m_RatesTable.addHighlighter(l_EvenRowHighlighter);
	
	m_RatesTable.addTreeSelectionListener (this);
	
	l_MouseAdapter = new MouseAdapter()
		{
    	public void mouseClicked(MouseEvent p_Event)
    		{
    		if (p_Event.isPopupTrigger() || (p_Event.getButton() == MouseEvent.BUTTON3))
    			{
     			}
    		else if (p_Event.getClickCount() == 2)
    			{
    			editSelectedRate (); 
    			}
    		}
    	};

	m_RatesTable.addMouseListener(l_MouseAdapter);
	
	l_Icon = BillingModule.getButtonIcon ("add_rate.png");
	m_AddRateButton = new JButton (l_Icon);
	m_AddRateButton.setHorizontalAlignment(SwingConstants.LEFT);
	m_AddRateButton.setVerticalTextPosition(AbstractButton.CENTER);
	m_AddRateButton.setHorizontalTextPosition(AbstractButton.TRAILING); 
	m_AddRateButton.setEnabled(true);
	m_AddRateButton.addActionListener(this);
	
	l_Icon = BillingModule.getButtonIcon ("remove_rate.png");
	m_RemoveRateButton = new JButton (l_Icon);
	m_RemoveRateButton.setHorizontalAlignment(SwingConstants.LEFT);
	m_RemoveRateButton.setVerticalTextPosition(AbstractButton.CENTER);
	m_RemoveRateButton.setHorizontalTextPosition(AbstractButton.TRAILING); 
	m_RemoveRateButton.setEnabled(true);
	m_RemoveRateButton.addActionListener (this);
	
	l_Icon = GECAMedModule.getIcon(GECAMedIconNames.RELOAD);
	m_ReloadButton = new JButton (l_Icon);
	m_ReloadButton.setHorizontalAlignment(SwingConstants.LEFT);
	m_ReloadButton.setVerticalTextPosition(AbstractButton.CENTER);
	m_ReloadButton.setHorizontalTextPosition(AbstractButton.TRAILING); 
	m_ReloadButton.setEnabled(true);
	m_ReloadButton.addActionListener (this);
		
	this.relocalize();
	
	//Disable buttons if demo mode
		if (GECAMedUtils.isDemo() && !MainFrame.isAdmin()) {
		   	// disables all tabs
			m_AddRateButton.setEnabled(false);
			m_RemoveRateButton.setEnabled(false);
			m_ReloadButton.setEnabled(false);
			
		} else {
			m_AddRateButton.setEnabled(true);
			m_RemoveRateButton.setEnabled(true);
			m_ReloadButton.setEnabled(true);
			
		}
		
	this.add (m_RatesScroller,    l_Constraints.xywh(2, 2, 1, 6));
	this.add (m_AddRateButton, 	  l_Constraints.xywh(4, 2, 1, 1));
	this.add (m_RemoveRateButton, l_Constraints.xywh(4, 4, 1, 1));	
	this.add (m_ReloadButton, 	  l_Constraints.xywh(4, 6, 1, 1));
	}
		
//---------------------------------------------------------------------------

private RateTreeNode buildTree ()
	{
	Collection <RateIndex>	  l_IndexRoots;
	Iterator <RateIndex>	  l_RootIterator;
	RateIndex				  l_Root;

	m_RootNode = new RateTreeNode ();
	
	m_AllCodesNode = new RateTreeNode ();
	m_AllRateNodeList = new ArrayList<RateTreeNode>();
	RateIndex allCodes = new RateIndex();
	allCodes.setLabel(Translatrix.getTranslationString("RatesPanel.allRatesByName"));
	m_AllCodesNode.setRateIndex(allCodes);
	
	l_IndexRoots = this.getRateIndexRoots();
	if (l_IndexRoots != null)
		{
		l_RootIterator = l_IndexRoots.iterator();
		while (l_RootIterator.hasNext())
			{
			l_Root = l_RootIterator.next();
			l_Root = this.fetchLazyDependencies(l_Root);
			
			m_RootNode.add(this.buildTreeNode(l_Root));
			}
		}
	
	addTreeNodesForChapterlessRates(m_RootNode);
	
	Collections.sort(m_AllRateNodeList, new Comparator<RateTreeNode>() {
		public int compare(RateTreeNode o1, RateTreeNode o2) {
			try {
				return o1.getRate().getCode().compareTo(o2.getRate().getCode());				
			} catch (Exception e) {
				e.printStackTrace();
			}
			return 0;
		}
	});
	for (RateTreeNode node : m_AllRateNodeList) {
		m_AllCodesNode.add(node);
	}
	m_RootNode.add (m_AllCodesNode);
	
	return 	m_RootNode;
	}

//---------------------------------------------------------------------------
private RateTreeNode addTreeNodesForChapterlessRates (RateTreeNode m_RootNode)
{
RateTreeNode l_Leaf		= null;
Collection<Rate>				l_RatesWithoutChapter;

	try {
		l_RatesWithoutChapter	= getNomenclatureInterface().getRatesWithoutChapters();
		l_RatesWithoutChapter	= sortNodes(l_RatesWithoutChapter);
		RateTreeNode l_FirstCodeNode = null;
		for (Rate l_Rate : l_RatesWithoutChapter)
			{
			l_Leaf = new RateTreeNode ();
			l_Leaf.setRate(l_Rate);
			if (l_FirstCodeNode == null || ! l_FirstCodeNode.getRate().getCode().equals(l_Rate.getCode())) {
				m_RootNode.add (l_Leaf);
				m_AllRateNodeList.add (l_Leaf);
				l_FirstCodeNode = l_Leaf;
			} else {
				l_FirstCodeNode.add (l_Leaf);
			}
			}
		} 
	catch (Exception e)
		{
		m_Logger.log(Level.ERROR, "Error while fetching rates without chapter\n" + e.getMessage());
		e.printStackTrace();
		}
return (m_RootNode);
}




private RateTreeNode buildTreeNode (RateIndex p_EnclosingChapter)
	{
	RateTreeNode l_Node 	= null;	
	RateTreeNode l_Leaf		= null;
	RateTreeNode l_alphaLeaf= null;
	Iterator <RateIndex>	l_SubChapterIterator;
	RateIndex				l_SubChapter;
	Set<Rate>				l_RatesOfChapter;
	
	if (p_EnclosingChapter == null) return null;
		
	l_Node = new RateTreeNode ();
	l_Node.setRateIndex(p_EnclosingChapter);
	
	if (p_EnclosingChapter.getSubChapters() != null) {
		l_SubChapterIterator = sortNodes(p_EnclosingChapter.getSubChapters()).iterator();
		while (l_SubChapterIterator.hasNext()) {
			l_SubChapter = 	l_SubChapterIterator.next();
			l_Node.add (this.buildTreeNode(l_SubChapter));
		}
	}
	if (p_EnclosingChapter.getRates() != null) {
		try {
			l_RatesOfChapter	= p_EnclosingChapter.getRates();
			l_RatesOfChapter	= sortNodes(l_RatesOfChapter);
			RateTreeNode l_FirstCodeNode = null;
			RateTreeNode l_FirstAlphaCodeNode = null;
			for (Rate l_Rate : l_RatesOfChapter){
				l_Leaf = new RateTreeNode ();
				l_Leaf.setRate(l_Rate);
				// alphabetic list
				l_alphaLeaf = new RateTreeNode ();
				l_alphaLeaf.setRate(l_Rate);
				
				if (l_FirstCodeNode == null || ! l_FirstCodeNode.getRate().getCode().equals(l_Rate.getCode())) {
					l_FirstCodeNode = l_Leaf;
					l_FirstAlphaCodeNode = l_alphaLeaf;
					l_Node.add(l_Leaf);
					m_AllRateNodeList.add(l_alphaLeaf);
				} else {
					l_FirstCodeNode.add (l_Leaf);
					l_FirstAlphaCodeNode.add (l_alphaLeaf);
				}
			}
		} catch (Exception e) {
			m_Logger.log(Level.ERROR, "Error while fetching chapter \"" + p_EnclosingChapter.getTitle() + "\"\n" + e.getMessage());
			e.printStackTrace();
		}
	}
	return (l_Node);
	}

//---------------------------------------------------------------------------
/**
 * doLayout is the main entry point for JTable resizing and row/column space
 * distribution. The ActListBox class overrides the method to impose its own
 * distribution scheme. Width of columns is expressed as percents and we want
 * the table to reflect this distribution. 
 */
//---------------------------------------------------------------------------

public void doLayout ()
	{
    TableColumn  l_Column;
	double		l_Width;
	int			l_Index;
	int			l_ColumnWidth;
	
	super.doLayout ();
	
	l_Width = (double) m_RatesTable.getWidth () / 100;
	
	for (l_Index = 0; l_Index < c_ColumnWidths.length; l_Index++)
		{
		l_Column = m_RatesTable.getColumnModel().getColumn(l_Index);
		l_ColumnWidth = (int)(l_Width * c_ColumnWidths[l_Index]);
		l_Column.setPreferredWidth(l_ColumnWidth);
		}
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

private NomenclatureInterface getNomenclatureInterface ()
	{
	if (m_NomenclatureInterface != null) return m_NomenclatureInterface;

	try {
		m_NomenclatureInterface = (NomenclatureInterface) ManagerFactory.getRemote(NomenclatureBean.class);
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}

	return m_NomenclatureInterface;
	}

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

private Collection <KeyValue> getAllKeyValueTypes ()
	{
	NomenclatureInterface	l_NomenclatureInterface;
	Collection <KeyValue>	l_KeyValues = null;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return null;
	
	try {
		l_KeyValues = l_NomenclatureInterface.getNewestKeyValues();
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	
	return 	l_KeyValues;
	}

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

private Collection <RateIndex> getRateIndexRoots ()
	{
	Collection <RateIndex> l_IndexRoots = null;
		
	NomenclatureInterface l_NomenclatureInterface;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return null;
	
	try {
		l_IndexRoots = l_NomenclatureInterface.getRateIndexRoots();
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	
	return l_IndexRoots;
	}

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

private HashMap<Integer, Vector<KeyValue>> getKeyValues()
	{
	HashMap<Integer, Vector<KeyValue>> l_KeyValuesSorted = null;
		
	NomenclatureInterface l_NomenclatureInterface;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return null;
	
	try {
		l_KeyValuesSorted = l_NomenclatureInterface.getAllKeyValuesSorted();
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	
	return l_KeyValuesSorted;
	}


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

private RateIndex fetchLazyDependencies (RateIndex p_RateIndex)
	{
	NomenclatureInterface l_NomenclatureInterface;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return null;
	
	try {
		p_RateIndex = l_NomenclatureInterface.fetchLazyDependencies(p_RateIndex);
		}
	catch (Exception p_Exception) 
		{
		List<Object[]> 	l_InvalidRates;
		int 		 	l_Option;
		StringBuffer 	l_InvalidCodes = new StringBuffer();
		Integer[] 		l_InvalidRateIds;
		
		m_Logger.warn(p_Exception.getLocalizedMessage());
		l_InvalidRates = l_NomenclatureInterface.getRatesWithoutKeyValue();
		
		for (int index = 0; index < l_InvalidRates.size(); index++)
			{
			l_InvalidCodes.append(l_InvalidRates.get(index)[1]);
			if (index + 1 < l_InvalidRates.size())
				l_InvalidCodes.append(", ");
			}
		
		l_Option = GECAMedBaseDialogImpl.showMessageDialog(MainFrame.getInstance(), 
				Translatrix.getTranslationString("RatesPanel.deleteRatesWithoutKeyValue_title"), 
				Translatrix.getTranslationString("RatesPanel.deleteRatesWithoutKeyValue") + l_InvalidCodes.toString(), 
				GECAMedBaseDialogImpl.OK_CANCEL_BUTTON_MODE, 
				GECAMedModule.getIcon(GECAMedIconNames.WARNING));
		
		if (l_Option == GECAMedBaseDialogImpl.OK_OPTION)
			{
			l_InvalidRateIds = new Integer[l_InvalidRates.size()];
			for (int index = 0; index < l_InvalidRateIds.length; index++)
				{
				l_InvalidRateIds[index] = (Integer) l_InvalidRates.get(index)[0];
				}
			l_NomenclatureInterface.removeRatesWithoutKeyValue(l_InvalidRateIds);
			}
		
		try {
			p_RateIndex = l_NomenclatureInterface.fetchLazyDependencies(p_RateIndex);
			} 
		catch (Exception e)
			{
			m_Logger.log(Level.ERROR, e.getLocalizedMessage());
			}
		}
	
	return p_RateIndex;
	}

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

private Rate saveRate (Rate p_Rate)
	{
	NomenclatureInterface l_NomenclatureInterface;
	String []			  l_Filler;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return null;
	
	try {
		p_Rate = l_NomenclatureInterface.saveRate(p_Rate);
		} 
	catch (EJBException p_Exception)
		{
		if (p_Exception.getCause() instanceof EntityExistsException)
			{
			l_Filler = new String [1];
			l_Filler [0] = p_Rate.getCode();
		
			BillingModule.notifyUser (c_CodeExistsTitle, c_CodeExistsMessage, l_Filler);
			p_Rate = null;
			}
		}
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	
	return p_Rate;
	}

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

private void deleteRate (Rate p_Rate)
	{
	NomenclatureInterface l_NomenclatureInterface;
	
	l_NomenclatureInterface = this.getNomenclatureInterface();
	if (l_NomenclatureInterface == null) return;
	
	try {
		l_NomenclatureInterface.deleteRate(p_Rate);
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.warn(p_Exception.getLocalizedMessage());
		}
	}

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

private RateDialog	getRateDialog ()
	{
	if (m_RateDialog == null) m_RateDialog = new RateDialog ();
	return m_RateDialog;
	}

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

private void updateTreeTable ()
	{
	m_Rates.setKeyValues(getKeyValues());
	m_Rates.setRoot(m_RootNode);
	m_RatesTable.setTreeTableModel(m_Rates);
	m_RatesTable.validate();
	this.doLayout();
	}

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

private RateTreeNode getSelectedNode ()
	{
	RateTreeNode		l_Node;
	TreePath			l_SelectionPath;
	int		 			l_SelectedRow;

	l_SelectedRow = m_RatesTable.getSelectedRow();
	if (l_SelectedRow < 0) return null;
		
	l_SelectionPath = m_RatesTable.getPathForRow (l_SelectedRow);
	l_Node = (RateTreeNode) l_SelectionPath.getLastPathComponent();
		
	return l_Node;
	}

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

public void editSelectedRate ()
	{
	RateDialog		l_RateDialog;
	RateTreeNode	l_Node;
	Rate			l_Rate;
	
	l_Node = this.getSelectedNode();
		
	if ((l_Node != null) && l_Node.getRate() != null)
		{
		l_Rate			= l_Node.getRate();
		l_RateDialog	= this.getRateDialog();
		l_RateDialog.setKeyValues(this.getAllKeyValueTypes());
		l_RateDialog.setRate(l_Rate);
		l_RateDialog.pack();
		
		MainFrame.showDialogCentered (l_RateDialog);	

		if (!l_RateDialog.wasCanceled())
			{
			l_Rate = l_RateDialog.getRate();
			l_Rate = this.saveRate(l_Rate);
			
			try {
				l_Node.setRate (l_Rate);
				m_Rates.nodeChanged(l_Node);
				}
			catch (Exception e)
				{
				logger.log(Level.ERROR, e.getMessage(), e);
				this.updateTreeTable();
				}
			}
		}
	}

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

public void removeSelectedRate ()
	{
	RateTreeNode		l_Node;
	Rate				l_Rate;
//	int					l_UserChoice;
//	String []			l_Filler;
	
	l_Node = this.getSelectedNode();
	
	if (l_Node == null) return;
	
	if (l_Node.isLeaf() && l_Node.getRate() != null)
		{
		l_Rate = l_Node.getRate();
//		if ((l_Rate.getKeyValue() != null) && (l_Rate.getKeyValue().getProtected()))
//			{
//			l_Filler = new String [1];
//			l_Filler [0] = l_Rate.getCode();
//			
//			l_UserChoice = BillingModule.getUserConfirmation (c_ProtectedRateTitle, c_ProtectedRateMessage, l_Filler);
//			if (l_UserChoice == JOptionPane.NO_OPTION) return;
//			}
		
		l_Rate = l_Node.getRate();
		this.deleteRate(l_Rate);
		try {
			m_Rates.removeNodeFromParent(l_Node);
			}
		catch (Exception e)
			{
			logger.log(Level.ERROR, e.getMessage(), e);
			this.updateTreeTable();
			}
		}
	}

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

public void addRate ()
	{
	RateDialog			l_RateDialog;
	RateTreeNode		l_Node;
	RateTreeNode 		l_Leaf;
	RateIndex			l_RateIndex;
	Rate				l_Rate;
	
	l_Node = this.getSelectedNode();
	
	if (l_Node == null) return;
	
	// get the selected rate
	l_Rate	= l_Node.getRate();
	
	// get Parent rate index
	while (l_Node.getRate() != null) {
		l_Node	= (RateTreeNode) l_Node.getParent();
	}
	l_RateIndex	= l_Node.getRateIndex();
	
	if (l_RateIndex == null) {
		return;
	}
	
	if (l_Rate == null) {
		l_Rate = new Rate ();
	} else {
		l_Rate = (Rate) l_Rate.clone();
		l_Rate.setId(null);
	}
	
	l_Rate.setIndexId(l_RateIndex.getId());
	l_Rate.setApplicability(new Date());
	
	l_RateDialog = this.getRateDialog();
	l_RateDialog.setKeyValues(this.getAllKeyValueTypes());
	l_RateDialog.setRate(l_Rate);
//	l_RateDialog.pack();
	MainFrame.showDialogCentered (l_RateDialog);	

	if (!l_RateDialog.wasCanceled())
		{	
		l_Rate = l_RateDialog.getRate();
		l_Rate = this.saveRate(l_Rate);
		
		if (l_Rate != null)
			{
			l_Leaf = new RateTreeNode ();
			l_Leaf.setRate(l_Rate);
			try {
				m_Rates.insertNodeInto(l_Leaf, l_Node, l_Node.getChildCount());
				}
			catch (Exception e)
				{
				logger.log(Level.ERROR, e.getMessage(), e);
				this.updateTreeTable();
				}
			}
		}
	}

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

public void relocalize()
	{
	if (m_AddRateButton != null)
		{
		m_AddRateButton.setText (Translatrix.getTranslationString(c_AddRateButton));
		m_AddRateButton.setToolTipText (Translatrix.getTranslationString(c_AddRateTip));
		}
	
	if (m_RemoveRateButton != null)
		{
		m_RemoveRateButton.setText (Translatrix.getTranslationString(c_RemoveRateButton));
		m_RemoveRateButton.setToolTipText (Translatrix.getTranslationString(c_RemoveRateTip));
		}
	
	if (m_ReloadButton != null)
	{
		m_ReloadButton.setText (Translatrix.getTranslationString("RatesPanel.reload"));
		m_ReloadButton.setToolTipText (Translatrix.getTranslationString("RatesPanel.reload"));
	}
	}

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

public void actionPerformed(ActionEvent p_ActionEvent)
	{
	if (p_ActionEvent.getSource().equals (m_AddRateButton))
		{
		this.addRate ();
		}
	else if (p_ActionEvent.getSource().equals (m_RemoveRateButton))
		{
		this.removeSelectedRate ();	
		}
	else if (p_ActionEvent.getSource().equals (m_ReloadButton))
		{
	    m_Rates       = new RateTreeTableModel(); 
	    m_Rates.setKeyValues(getKeyValues());
		m_Rates.setRoot (this.buildTree());
		m_RatesTable.setTreeTableModel(m_Rates);
		m_RatesTable.validate();
		this.doLayout();
		}
	}

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

public void valueChanged(TreeSelectionEvent p_Event)
	{
	RateTreeNode		l_Node;
	TreePath			l_SelectionPath;
	int		 			l_SelectedRow;
	
	if (!p_Event.getSource().equals(m_RatesTable))
		{
		l_SelectedRow = m_RatesTable.getSelectedRow();
		if (l_SelectedRow < 0) return;
		
		l_SelectionPath = m_RatesTable.getPathForRow (l_SelectedRow);
		l_Node = (RateTreeNode) l_SelectionPath.getLastPathComponent();
		
		if (l_Node.getRate() != null)
			{
			m_RemoveRateButton.setEnabled(true);
			}
		else m_RemoveRateButton.setEnabled(false);
		}
	}

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

public void keyValueChanged (KeyValueChangeEvent p_Event)
	{
	switch (p_Event.getType())
		{
		case KeyValueChangeEvent.c_KeyValueDeleted:
		case KeyValueChangeEvent.c_KeyValueUpdated: 
			m_Rates.setKeyValues(getKeyValues());
			this.buildTree ();
			this.updateTreeTable();
		}
	}

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


private static <T extends Object> Set<T> sortNodes (Collection<T> nodes)
{
	TreeSet<T> sortedNodes = new TreeSet<T>(RATE_TREE_COMPARATOR);
	
	sortedNodes.addAll(nodes);
	
	return sortedNodes;
}


public static String getNodeLabel (RateIndex index)
{
	String label = "";
	
	if (index.getLabel() != null)
	{
		label = index.getLabel().replaceAll("\\p{Alpha}+_", "").replaceAll("_", ".");
	}
	if (index.getTitle() != null)
	{
		label = label + " - " + index.getTitle();
	}
	
	return label;
}


public static String getNodeLabel (Rate rate)
{
	return rate.getCode();
}



public static class RateTreeComparator implements Comparator<Object>
{
	private static final Pattern DIGIT_PATTERN	= Pattern.compile("\\d+");
	
	public int compare (Object o1, Object o2)
	{
		if (o1 == null)
		{
			return o2 == null ? 0 : -1;
		}
		else if (o2 == null)
		{
			return 1;
		}
		
		int compare = inflateNumbers(o1).compareTo(inflateNumbers(o2));
		if (compare == 0 
				&& o1 instanceof Rate
				&& o2 instanceof Rate)
		{
			// turn it arround (o2.compare(o1)) to have the newer rate on top
			compare = ((Rate)o2).getApplicability().compareTo(((Rate)o1).getApplicability());
			
			if (compare == 0)
				compare = ((Rate)o2).getId().compareTo(((Rate)o1).getId());
		}
		
		return compare;
	}
	
	
	private String inflateNumbers (Object o)
	{
		String	s;
		Matcher	m;
		int		correction;
		int		groupSize;
		
		
		// Retrieve the necessary string of this object or reject it, if it's null
		if (o instanceof RateIndex)
		{
			s	= getNodeLabel((RateIndex)o);
		}
		else if (o instanceof Rate)
		{
			s	= getNodeLabel((Rate)o);
		}
		else if (o instanceof String)
		{
			s	= (String) o;
		}
		else
		{
			s	= o.toString();
		}
		
		/* To compare strings with numbers, replace all numbers 
		 * in the strings, that have less than 3 digits, numbers 
		 * with 3 digits, by adding zeros at the start
		 */
		m = DIGIT_PATTERN.matcher(s);
		correction = 0;
		while (m.find())
		{
			groupSize	= m.group().length();
			if (groupSize < 3)
			{
				s = s.substring(0, m.start() + correction)
						+ (groupSize == 1 ? "00" : "0")
						+ s.substring(m.start() + correction);
				correction += 3 -groupSize;
			}
		}
		
		return s.toUpperCase();
	}
	
	
}

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

//public static void main (String[] args)
//{
//	Set<String> set = new TreeSet<String>(new RateTreeComparator());
//	
//	set.add("0");
//	set.add("2.1");
//	set.add("1.2");
//	set.add("3");
//	set.add("10.4");
//	set.add("1.5");
//	set.add("10");
//	set.add("1.11");
//	set.add("12");
//	set.add("10.13");
//	set.add("2.14");
//	set.add("1.20");
//	set.add("21");
//	set.add("2.22");
//	set.add("23");
//	set.add("10.24");
//	set.add("99");
//	set.add("20.52");
//	set.add("C1");
//	set.add("C2");
//	set.add("C10");
//	set.add("c11");
//	set.add("c12");
//	set.add("C13");
//	set.add("c14");
//	
//	
//	for (String s : set)
//	{
//		System.out.println(s);
//	}
//}


	}
