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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.border.TitledBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.JTextComponent;

import lu.tudor.santec.gecamed.core.gui.GECAMedIconNames;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.utils.GECAMedGuiUtils;
import lu.tudor.santec.gecamed.core.gui.utils.LineColorListRenderer;
import lu.tudor.santec.gecamed.core.gui.widgets.LimitTextField;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.formeditor.gui.component.NumericField;
import lu.tudor.santec.gecamed.formeditor.gui.model.ComboBoxElement;
import lu.tudor.santec.gecamed.reporting.ejb.entity.beans.ParameterQuery;
import lu.tudor.santec.gecamed.reporting.ejb.entity.beans.Report;
import lu.tudor.santec.gecamed.reporting.ejb.entity.beans.ReportParameter;
import lu.tudor.santec.gecamed.reporting.ejb.session.beans.ReportManagerBean;
import lu.tudor.santec.gecamed.reporting.ejb.session.interfaces.ReportManagerInterface;
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;
import com.jgoodies.forms.layout.RowSpec;
import com.jgoodies.forms.layout.Sizes;
import com.toedter.calendar.JDateChooser;

/**
 * a Panel to fill in the Parameter values that are needed to create a Report.
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 *
 */
public class ParameterPanel extends JPanel implements ActionListener
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final long serialVersionUID = 1L;
	
	private static final int	COLUMN_REMOVE			= 2;
	private static final int	COLUMN_NAME				= 4;
	private static final int	COLUMN_OBLIGATORY		= 6;
	private static final int	COLUMN_TYPE				= 8;
	private static final int	COLUMN_DEFAULT_VALUE	= 10;
	private static final int	COLUMN_LABEL			= 12;
	private static final int	COLUMN_COMMENT			= 14;
	
	private static final String[]	TYPES		= new String[] {
			ReportParameter.TYPE_BOOLEAN, 
			ReportParameter.TYPE_INTEGER, 
 			ReportParameter.TYPE_DECIMAL, 
			ReportParameter.TYPE_STRING, 
			ReportParameter.TYPE_DATE
			};
	
	private static final RowSpec	DEFAULT_ROWSPEC	= new RowSpec(RowSpec.CENTER, Sizes.PREFERRED, RowSpec.NO_GROW);
	
	private static final RowSpec	SPACER_ROWSPEC	= new RowSpec(Sizes.pixel(5));
	
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	private static CellConstraints		CC 				= new CellConstraints();
	
	/** the logger Object for this class */
	private static Logger 				logger 			= Logger.getLogger(ParameterPanel.class.getName());
	
	private static DateFormat			dateFormatter	= new SimpleDateFormat("yyyy-MM-dd");
	
	
	private Window						owner;
	
//	private HashMap elements = new HashMap();
	
	private FormLayout					layout;
	
	private LinkedList<ParameterRow>	rows;
	
	private JButton						addButton;
	
	private ListCellRenderer			typeRenderer;
	
//	private ListCellRenderer			parameterOptionsRenderer;
	
	
	
	/* ======================================== */
	// 		CONSTRUCTORS
	/* ======================================== */
	
	public ParameterPanel()
	{
		this(null);
	}
	
	
	public ParameterPanel(Collection<ReportParameter> parameters) 
	{
		JLabel	label;
		
		
		if (parameters == null)
			parameters	= new LinkedList<ReportParameter>();
		
		this.layout	= new FormLayout(
				"5px,f:32px,"	+ // remove button
				"5px,f:100px,"	+ // name
				"5px,c:100px,"	+ // obligatory
				"5px,f:p,"		+ // type
				"5px,f:100px,"	+ // optional
				"5px,f:100px,"	+ // default value
				"5px,f:250px,"	+ // comment
				"5px");  
		
		this.rows	= new LinkedList<ParameterRow>();
		
		this.setLayout(this.layout);
		this.setOpaque(false);
		this.layout.appendRow(SPACER_ROWSPEC);
		this.setBorder(new TitledBorder(Translatrix.getTranslationString("ReportModule.ParameterPanel.parameters") + ":"));
		
		// define the headlines
		this.layout.appendRow(DEFAULT_ROWSPEC);
		this.layout.appendRow(SPACER_ROWSPEC);
		
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.name"));
		this.add(label, CC.xy(COLUMN_NAME, 2));
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.obligatory"));
		this.add(label,	CC.xy(COLUMN_OBLIGATORY, 2));
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.type"));
		this.add(label, CC.xy(COLUMN_TYPE, 2));
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.defaultValue"));
		this.add(label, CC.xy(COLUMN_DEFAULT_VALUE, 2));
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.label"));
		this.add(label, CC.xy(COLUMN_LABEL, 2));
		label	= new JLabel(Translatrix.getTranslationString("ReportModule.ParameterPanel.comment"));
		this.add(label, CC.xy(COLUMN_COMMENT, 2));
		
		// add the add button
		this.addButton	= new JButton(GECAMedModule.getMediumIcon(GECAMedIconNames.ADD_LINE));
		this.addButton.addActionListener(this);
		this.layout.appendRow(new RowSpec(RowSpec.FILL, Sizes.pixel(32), RowSpec.NO_GROW));
		this.add(this.addButton, CC.xy(2, this.layout.getRowCount()));
		
		setParameter(parameters);
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	public void setParameter (Collection<ReportParameter> parameters)
	{
		ParameterRow	row;
		
		clearPanel();
		
		for (ReportParameter parameter : parameters) 
		{
			row	= new ParameterRow(parameter);
			addLine(row);
		}
	}
	
	
	public LinkedList<ReportParameter> getParameter (Report report)
	{
		LinkedList<ReportParameter> parameters	= new LinkedList<ReportParameter>();
		ReportParameter parameter;
		
		
		for (ParameterRow row : this.rows)
		{
			parameter	= row.getParameter();
			parameter.setReport(report);
			parameters.add(parameter);
		}
		
		return parameters;
	}
	
	
	public void addLine (ParameterRow parameter)
	{
		addLine(parameter, this.rows.size());
	}
	
	
	public void addLine (ParameterRow row, int index)
	{
		row.addMe(index);
	}
	
	
	public void removeLine (int index)
	{
		this.rows.get(index).removeMe();
	}
	
	
	public boolean allParameterFilledOut ()
	{
		String	name;
		
		
		for (ParameterRow row : this.rows)
		{
			name	= row.getParameter().getName();
			if (name == null || name.length() <= 0)
				return false;
		}
		
		return true;
	}
	
	
	public void clearPanel ()
	{
		while (!this.rows.isEmpty())
			this.rows.getLast().removeMe();
	}
	
	
	public void actionPerformed(ActionEvent e)
	{
		// add button clicked
		ReportParameter	parameter	= new ReportParameter();
		addLine(new ParameterRow(parameter));
	}
	
	
	public static String queryParameterStringValue (Object value)
	{
		if (value instanceof Date)
			return dateFormatter.format(value);
		else
			return String.valueOf(value);
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	private Window getOwner ()
	{
		Component parent;
		
		if (this.owner == null)
		{
			parent	= this.getParent();
			while (parent != null && !(parent instanceof Window))
				parent	= parent.getParent();
			
			this.owner	= (Window) parent;
		}
		
		return this.owner;
	}
	
	
	private int getRowOfIndex (int index)
	{
		return (index + 1) * 2 + 2;
	}
	
	
	private Object[] getTypes ()
	{
		ReportManagerInterface	manager		= (ReportManagerInterface) ManagerFactory.getRemote(ReportManagerBean.class);
		List<ParameterQuery>	parameters	= manager.getAllParameterQueries();
		Object[] 				types		= new Object[TYPES.length + parameters.size()];
		
		
		for (int i = 0; i < types.length; i++)
		{
			if (i < TYPES.length)
				types[i] = TYPES[i];
			else
				types[i] = parameters.get(i-TYPES.length);
		}
		
		return types;
	}
	
	
	private ListCellRenderer getTypeRenderer ()
	{
		if (typeRenderer == null)
		{
			typeRenderer	= new LineColorListRenderer()
			{
				private static final long	serialVersionUID	= 1L;

				public Component getListCellRendererComponent(JList list, Object value, 
						int index, boolean isSelected, boolean cellHasFocus)
				{
					JLabel c	= (JLabel) super.getListCellRendererComponent(
							list, value, index, isSelected, cellHasFocus);
					
					if (value instanceof ParameterQuery)
					{
						c.setToolTipText(((ParameterQuery) value).getComment());
					}
					
					return c;
				}
			};
		}
		
		return typeRenderer;
	}
	
	
	
	/* ======================================== */
	// 		CLASS: ParameterRow
	/* ======================================== */
	
	private class ParameterRow implements ActionListener, ItemListener, 
			ChangeListener, CaretListener, PropertyChangeListener
	{
		/* ======================================== */
		// 		MEMBERS
		/* ======================================== */
		
		private ReportParameter	parameter;
		
		private JButton			removeButton;
		
		private JTextField		nameField;
		
		private JComboBox		typeBox;
		
		private JCheckBox		obligatoryBox;
		
		private JTextField		labelField;
		
		private JPanel			defaultValuePanel;
		
		private JComponent		defaultValueComponent;
		
		private JTextField		commentField;
		
		
		
		/* ======================================== */
		// 		CONSTRUCTORS
		/* ======================================== */
		
		public ParameterRow(ReportParameter parameter)
		{
			if (parameter == null)
				 this.parameter	= new ReportParameter();
			else this.parameter	= parameter;
			
			this.nameField			= new LimitTextField(100);
			this.nameField.setDocument(new VariableNameDocument());
			this.nameField.setText(this.parameter.getName());
			this.typeBox			= new JComboBox(getTypes());
			selectType(parameter.getType());
			this.typeBox.addItemListener(this);
			this.typeBox.setRenderer(getTypeRenderer());
			this.obligatoryBox		= new JCheckBox();
			this.obligatoryBox.setSelected(this.parameter.getMendatory());
			this.obligatoryBox.setOpaque(false);
			this.defaultValueComponent	= new JTextField(this.parameter.getDefaultValue());
			this.defaultValuePanel	= new JPanel(new BorderLayout());
			this.defaultValuePanel.setOpaque(false);
			this.labelField			= new LimitTextField(100);
			this.labelField.setText(this.parameter.getLabel());
			this.commentField		= new JTextField(this.parameter.getComment());
			this.removeButton		= new JButton(GECAMedModule.getMediumIcon(GECAMedIconNames.REMOVE_LINE));
			this.removeButton.setToolTipText(Translatrix.getTranslationString("ReportModule.ParameterPanel.removeParameter"));
			this.removeButton.addActionListener(this);
			
			setDefaultValueComponent(typeBox.getSelectedItem());
		}
		
		
		
		private void selectType (String type)
		{
			Integer			id;
			ParameterQuery	query;
			
			
			try
			{
				id	= Integer.parseInt(type);
				// this is the ID of a QueryParameter
				
				for (int i = TYPES.length; i < typeBox.getItemCount(); i++)
				{
					if (typeBox.getItemAt(i) instanceof ParameterQuery)
					{
						query	= (ParameterQuery) typeBox.getItemAt(i);
						if (query.getId().equals(id))
							typeBox.setSelectedIndex(i);
					}
				}
			}
			catch (NumberFormatException e)
			{
				// this is not the ID of a query parameter, so you can simply set it
				this.typeBox.setSelectedItem(type);
			}
		}



		/* ======================================== */
		// 		CLASS BODY
		/* ======================================== */
		
		public void addMe (int index)
		{
			int	rowIndex	= getRowOfIndex(index);
			
			rows.add(index, this);
			
			layout.insertRow(rowIndex,		DEFAULT_ROWSPEC);
			layout.insertRow(rowIndex + 1,	SPACER_ROWSPEC);

			add(this.removeButton,		CC.xy(COLUMN_REMOVE,		rowIndex));
			add(this.nameField,			CC.xy(COLUMN_NAME,			rowIndex));
			add(this.obligatoryBox,		CC.xy(COLUMN_OBLIGATORY,	rowIndex));
			add(this.typeBox,			CC.xy(COLUMN_TYPE,			rowIndex));
			add(this.labelField,		CC.xy(COLUMN_LABEL,			rowIndex));
			add(this.defaultValuePanel,	CC.xy(COLUMN_DEFAULT_VALUE,	rowIndex));
			add(this.commentField,		CC.xy(COLUMN_COMMENT,		rowIndex));
			
			getOwner().validate();
		}
		
		
		public void removeMe ()
		{
			int index		= rows.indexOf(this);
			int rowIndex	= getRowOfIndex(index);
			
			rows.remove(index);

			remove(removeButton);
			remove(nameField);
			remove(obligatoryBox);
			remove(typeBox);
			remove(labelField);
			remove(defaultValuePanel);
			remove(commentField);
			
			// remove the parameter row
			layout.removeRow(rowIndex);
			// remove the spacer
			layout.removeRow(rowIndex);

			getOwner().validate();
			getOwner().repaint();
		}
		
		
		public ReportParameter getParameter ()
		{
			Object	type	= typeBox.getSelectedItem();
			
			
			if (type instanceof ParameterQuery)
				type	= ((ParameterQuery) type).getId();
			
			parameter.setName(nameField.getText());
			parameter.setMendatory(obligatoryBox.isSelected());
			parameter.setType(String.valueOf(type));
			parameter.setLabel(labelField.getText());
			parameter.setComment(commentField.getText());
			
			return parameter;
		}
		
		
		public void setDefaultValueComponent (Object type)
		{
			Vector<ComboBoxElement<String>>	options;
			ReportManagerInterface			manager;
			ParameterQuery	parameterQuery;
			String			defaultValue	= parameter.getDefaultValue();
			
			
			this.defaultValuePanel.removeAll();
			
			if (type instanceof ParameterQuery)
			{
				List<?>		results;
				int			index;
				int			selectedIndex	= -1;
				JComboBox	box;
				Object[]	option;
				String		label;
				String		value;
				
				
				parameterQuery	= (ParameterQuery) type;
				manager			= (ReportManagerInterface) ManagerFactory.getRemote(ReportManagerBean.class);
				results			= manager.getParameterOptions(parameterQuery.getQuery());
				if (results == null)
				{
					options	= new Vector<ComboBoxElement<String>>();
					index	= -1;
				}
				else
				{
					options			= new Vector<ComboBoxElement<String>>(results.size());
					index			= 0;
					
					for (Object result : results)
					{
						if (result.getClass().isArray())
						{
							option	= (Object[]) result;
							if (((Object[]) result).length > 1)
								label	= String.valueOf(option[1]);
							else
								label	= String.valueOf(option[0]);
							
							value	= queryParameterStringValue(option[0]);
						}
						else
						{
							value	= queryParameterStringValue(result);
							label	= value;
						}
						options.add(new ComboBoxElement<String>(label, value));
						if (value.equals(defaultValue))
							selectedIndex	= index;
						index++;
					}
				}
				
				box	= new JComboBox(options);
				box.addItemListener(this);
				box.setRenderer(new LineColorListRenderer());
				if (selectedIndex < box.getItemCount())
					box.setSelectedIndex(selectedIndex);
				else
					box.setSelectedItem(null);
				
				defaultValueComponent	= box;
			}
			else if (type instanceof String)
			{
				if (ReportParameter.TYPE_BOOLEAN.equals(type))
				{
					defaultValueComponent	= new JCheckBox("");
					defaultValueComponent.setOpaque(false);
					
					// set the value
					defaultValue		= defaultValue.trim().toLowerCase();
					((JCheckBox)defaultValueComponent).setSelected(defaultValue == null
							? false : defaultValue.equals("true"));
					((JCheckBox)defaultValueComponent).addChangeListener(this);
				}
				else if (ReportParameter.TYPE_DATE.equals(type))
				{
//					defaultValueComponent	= new DynamicDateChooser(defaultValue == null
//							? "TODAY" : defaultValue);
					try
					{
						defaultValueComponent	= GECAMedGuiUtils.getDateChooser(false);
						((JDateChooser)defaultValueComponent).setDate(defaultValue != null && defaultValue.length() > 0 ? 
								dateFormatter.parse(defaultValue) : new Date());
					}
					catch (ParseException e)
					{
						logger.log(Level.ERROR, e.getMessage(), e);
						
						defaultValueComponent	= GECAMedGuiUtils.getDateChooser(false);;
					}
					defaultValueComponent.addPropertyChangeListener(this);
				}
				else if (ReportParameter.TYPE_DECIMAL.equals(type))
				{
					defaultValueComponent	= new NumericField(defaultValue, true);
					((JTextComponent)defaultValueComponent).addCaretListener(this);
	//				((JTextComponent)defaultValueComponent).setText(defaultValue == null ?
	//						"0.0" : defaultValue);
				}
				else if (ReportParameter.TYPE_INTEGER.equals(type))
				{
					defaultValueComponent	= new NumericField(defaultValue, false);
					((JTextComponent)defaultValueComponent).addCaretListener(this);
	//				((JTextComponent)defaultValueComponent).setText(defaultValue == null ?
	//						"0" : defaultValue);
				}
				else if (ReportParameter.TYPE_STRING.equals(type))
				{
					defaultValueComponent	= new JTextField(defaultValue == null 
							? "" : parameter.getDefaultValue());
					((JTextField)defaultValueComponent).addCaretListener(this);
					((JTextComponent)defaultValueComponent).setText(defaultValue == null ?
							"" : defaultValue);
				}
				else
				{
					logger.warn("Type "+type+" not supported!");
				}
			}
			else
			{
				logger.warn("Type "+type+" not supported!");
			}
			
			this.defaultValuePanel.add(this.defaultValueComponent, BorderLayout.CENTER);
			ParameterPanel.this.validate();
			ParameterPanel.this.repaint();
		}
		
		
		
		/* ======================================== */
		// 		EVENT METHODS
		/* ======================================== */
		
		public void actionPerformed(ActionEvent e)
		{
			// remove button clicked
			removeMe();
		}
		
		
		public void itemStateChanged(ItemEvent e)
		{
			JComboBox			box;
			ComboBoxElement<?>	element;
			
			
			// item of JComboBox selected
			if (e.getStateChange() == ItemEvent.DESELECTED)
				return;
			
			if (e.getSource() == typeBox)
			{
				setDefaultValueComponent((Object)e.getItem());
			}
			else if (e.getSource() == defaultValueComponent)
			{
				box		= (JComboBox) defaultValueComponent;
				element	= (ComboBoxElement<?>) box.getSelectedItem();
				this.parameter.setDefaultValue(String.valueOf(element.getValue()));
			}
		}
		
		
		public void stateChanged(ChangeEvent e)
		{
			// JCheckbox clicked
			this.parameter.setDefaultValue(
					((JCheckBox)defaultValueComponent).isSelected() 
					? "TRUE" : "FALSE");
		}
		
		
		public void propertyChange(PropertyChangeEvent e)
		{
			Date	defaultDate;
			
			// TODO: DynamicDateChooser changed
			if ("date".equals(e.getPropertyName().toLowerCase()))
			{
				defaultDate	= ((JDateChooser)defaultValueComponent).getDate();
				parameter.setDefaultValue(dateFormatter.format(defaultDate));
			}
		}
		
		
		public void caretUpdate(CaretEvent e)
		{
			this.parameter.setDefaultValue(((JTextComponent)defaultValueComponent).getText());
		}
	}
}
