/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 Adempiere, Inc. All Rights Reserved.               *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program 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 General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 *****************************************************************************/
package org.compiere.apps;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.logging.Level;

import javax.swing.Box;
import javax.swing.JLabel;

import org.adempiere.exceptions.DBException;
import org.compiere.grid.ed.VEditor;
import org.compiere.grid.ed.VEditorFactory;
import org.compiere.model.GridField;
import org.compiere.model.GridFieldVO;
import org.compiere.model.MClient;
import org.compiere.model.MPInstancePara;
import org.compiere.process.ProcessInfo;
import org.compiere.swing.CPanel;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;

/**
 *	Process Parameter Panel, based on existing ProcessParameter dialog.
 *	- Embedded in ProcessDialog
 *	- checks, if parameters exist and inquires and saves them
 *
 * @author Low Heng Sin
 * @author Juan David Arboleda (arboleda), GlobalQSS, [ 1795398 ] Process
 *         Parameter: add display and readonly logic
 * @author Teo Sarca, www.arhipac.ro
 * 			<li>BF [ 2548216 ] Process Param Panel is not showing any parameter if error 
 * @version 	2006-12-01
 */
public class ProcessParameterPanel extends CPanel implements VetoableChangeListener, IProcessParameter {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4042260793108029845L;

		/**
		 *	Dynamic generated Parameter panel.
		 *  @param WindowNo window
		 *  @param pi process info
		 */
		public ProcessParameterPanel(int WindowNo, ProcessInfo pi)
		{
			try
			{
				jbInit();
			}
			catch(Exception ex)
			{
				log.log(Level.SEVERE, ex.getMessage());
			}
			//
			m_WindowNo = WindowNo;
			m_processInfo = pi;
			//
		}	//	ProcessParameterPanel

		private int			m_WindowNo;
		private ProcessInfo m_processInfo;
		/**	Logger			*/
		private static CLogger log = CLogger.getCLogger(ProcessParameterPanel.class);
		//
		private GridBagConstraints gbc = new GridBagConstraints();
		private Insets      nullInset       = new Insets(0,0,0,0);
		private Insets 		labelInset      = new Insets(2,12,2,0);		// 	top,left,bottom,right
		private Insets 		fieldInset      = new Insets(2,5,2,0);		// 	top,left,bottom,right
		private Insets 		fieldInsetRight = new Insets(2,5,2,12);		// 	top,left,bottom,right
		private int			m_line = 0;
		//
		private ArrayList<VEditor>	m_vEditors = new ArrayList<VEditor>();
		private ArrayList<VEditor>	m_vEditors2 = new ArrayList<VEditor>();		//	for ranges
		private ArrayList<GridField>	m_mFields = new ArrayList<GridField>();
		private ArrayList<GridField>	m_mFields2 = new ArrayList<GridField>();
		private ArrayList<JLabel> m_separators = new ArrayList<JLabel>();
		//
		private BorderLayout mainLayout = new BorderLayout();
		private CPanel centerPanel = new CPanel();
		private GridBagLayout centerLayout = new GridBagLayout();

		/**
		 *	Static Layout
		 *  @throws Exception
		 */
		void jbInit() throws Exception
		{
			this.setLayout(mainLayout);
			centerPanel.setLayout(centerLayout);
			this.add(centerPanel, BorderLayout.CENTER);
		}	//	jbInit

		/**
		 *  Dispose
		 */
		public void dispose()
		{
			m_vEditors.clear();
			m_vEditors2.clear();
			m_mFields.clear();
			m_mFields2.clear();
			m_separators.clear();
			this.removeAll();
		}   //  dispose

		/**
		 *	Read Fields to display
		 *  @return true if loaded OK
		 */
		public boolean init()
		{
			log.config("");

			//	Prepare panel
			gbc.anchor = GridBagConstraints.NORTHWEST;
			gbc.weightx = 0;
			gbc.weighty = 0;
			gbc.gridy = m_line++;
			gbc.gridx = 0;
			gbc.gridwidth = 1;
			gbc.insets = nullInset;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			centerPanel.add(Box.createVerticalStrut(10), gbc);    	//	top gap 10+2=12

			// ASP
			MClient client = MClient.get(Env.getCtx());
			String ASPFilter = "";
			if (client.isUseASP())
				ASPFilter =
					  "   AND (   p.AD_Process_Para_ID IN ( "
					// Just ASP subscribed process parameters for client "
					+ "              SELECT pp.AD_Process_Para_ID "
					+ "                FROM ASP_Process_Para pp, ASP_Process p, ASP_Level l, ASP_ClientLevel cl "
					+ "               WHERE p.ASP_Level_ID = l.ASP_Level_ID "
					+ "                 AND cl.AD_Client_ID = " + client.getAD_Client_ID()
					+ "                 AND cl.ASP_Level_ID = l.ASP_Level_ID "
					+ "                 AND pp.ASP_Process_ID = p.ASP_Process_ID "
					+ "                 AND pp.IsActive = 'Y' "
					+ "                 AND p.IsActive = 'Y' "
					+ "                 AND l.IsActive = 'Y' "
					+ "                 AND cl.IsActive = 'Y' "
					+ "                 AND pp.ASP_Status = 'S') " // Show
					+ "        OR p.AD_Process_Para_ID IN ( "
					// + show ASP exceptions for client
					+ "              SELECT AD_Process_Para_ID "
					+ "                FROM ASP_ClientException ce "
					+ "               WHERE ce.AD_Client_ID = " + client.getAD_Client_ID()
					+ "                 AND ce.IsActive = 'Y' "
					+ "                 AND ce.AD_Process_Para_ID IS NOT NULL "
					+ "                 AND ce.AD_Tab_ID IS NULL "
					+ "                 AND ce.AD_Field_ID IS NULL "
					+ "                 AND ce.ASP_Status = 'S') " // Show
					+ "       ) "
					+ "   AND p.AD_Process_Para_ID NOT IN ( "
					// minus hide ASP exceptions for client
					+ "          SELECT AD_Process_Para_ID "
					+ "            FROM ASP_ClientException ce "
					+ "           WHERE ce.AD_Client_ID = " + client.getAD_Client_ID()
					+ "             AND ce.IsActive = 'Y' "
					+ "             AND ce.AD_Process_Para_ID IS NOT NULL "
					+ "             AND ce.AD_Tab_ID IS NULL "
					+ "             AND ce.AD_Field_ID IS NULL "
					+ "             AND ce.ASP_Status = 'H')"; // Hide
			//
			String sql = null;
			if (Env.isBaseLanguage(Env.getCtx(), "AD_Process_Para"))
				sql = "SELECT p.Name, p.Description, p.Help, "
					+ "p.AD_Reference_ID, p.AD_Process_Para_ID, "
					+ "p.FieldLength, p.IsMandatory, p.IsRange, p.ColumnName, "
					+ "p.DefaultValue, p.DefaultValue2, p.VFormat, p.ValueMin, p.ValueMax, "
					+ "p.SeqNo, p.AD_Reference_Value_ID, vr.Code AS ValidationCode, p.ReadOnlyLogic, p.DisplayLogic "
					+ "FROM AD_Process_Para p"
					+ " LEFT OUTER JOIN AD_Val_Rule vr ON (p.AD_Val_Rule_ID=vr.AD_Val_Rule_ID) "
					+ "WHERE p.AD_Process_ID=?"		//	1
					+ " AND p.IsActive='Y' "
					+ ASPFilter + " ORDER BY SeqNo";
			else
				sql = "SELECT t.Name, t.Description, t.Help, "
					+ "p.AD_Reference_ID, p.AD_Process_Para_ID, "
					+ "p.FieldLength, p.IsMandatory, p.IsRange, p.ColumnName, "
					+ "p.DefaultValue, p.DefaultValue2, p.VFormat, p.ValueMin, p.ValueMax, "
					+ "p.SeqNo, p.AD_Reference_Value_ID, vr.Code AS ValidationCode, p.ReadOnlyLogic, p.DisplayLogic "
					+ "FROM AD_Process_Para p"
					+ " INNER JOIN AD_Process_Para_Trl t ON (p.AD_Process_Para_ID=t.AD_Process_Para_ID)"
					+ " LEFT OUTER JOIN AD_Val_Rule vr ON (p.AD_Val_Rule_ID=vr.AD_Val_Rule_ID) "
					+ "WHERE p.AD_Process_ID=?"		//	1
					+ " AND t.AD_Language='" + Env.getAD_Language(Env.getCtx()) + "'"
					+ " AND p.IsActive='Y' "
					+ ASPFilter + " ORDER BY SeqNo";

			//	Create Fields
			boolean hasFields = false;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			try
			{
				pstmt = DB.prepareStatement(sql, null);
				pstmt.setInt(1, m_processInfo.getAD_Process_ID());
				rs = pstmt.executeQuery();
				while (rs.next())
				{
					hasFields = true;
					createField (rs);
				}
			}
			catch(SQLException e)
			{
				throw new DBException(e, sql);
			}
			finally
			{
				DB.close(rs, pstmt);
				rs = null;
				pstmt = null;
			}

			//	both vectors the same?
			if (m_mFields.size() != m_mFields2.size()
					|| m_mFields.size() != m_vEditors.size()
					|| m_mFields2.size() != m_vEditors2.size())
				log.log(Level.SEVERE, "View & Model vector size is different");

			//	clean up
			if (hasFields)
			{
				gbc.gridy = m_line++;
				centerPanel.add(Box.createVerticalStrut(10), gbc);    	//	bottom gap
				gbc.gridx = 3;
				centerPanel.add(Box.createHorizontalStrut(12), gbc);   	//	right gap
				dynamicDisplay();
			}
			else
				dispose();
			return hasFields;
		}	//	init

		/**
		 *	Create Field.
		 *	- creates Fields and adds it to m_mFields list
		 *  - creates Editor and adds it to m_vEditors list
		 *  Handles Ranges by adding additional mField/vEditor.
		 *  <p>
		 *  mFields are used for default value and mandatory checking;
		 *  vEditors are used to retrieve the value (no data binding)
		 *
		 * @param rs result set
		 */
		private void createField (ResultSet rs)
		{
			//  Create Field
			GridFieldVO voF = GridFieldVO.createParameter(Env.getCtx(), m_WindowNo, rs);
			GridField mField = new GridField (voF);
			m_mFields.add(mField);                      //  add to Fields

			//	Label Preparation
			gbc.gridy = m_line++;
			gbc.gridwidth = 1;
			gbc.fill = GridBagConstraints.HORIZONTAL;	//	required for right justified
			gbc.gridx = 0;
			gbc.weightx = 0;
			JLabel label = VEditorFactory.getLabel(mField);
			if (label == null)
			{
				gbc.insets = nullInset;
				centerPanel.add(Box.createHorizontalStrut(12), gbc);   	//	left gap
			}
			else
			{
				gbc.insets = labelInset;
				centerPanel.add(label, gbc);
			}

			//	Field Preparation
			gbc.insets = fieldInset;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.gridwidth = 1;
			gbc.gridx = 1;
			gbc.weightx = 1;

			//	The Editor
			VEditor vEditor = VEditorFactory.getEditor(mField, false);
			vEditor.addVetoableChangeListener(this);
			//  MField => VEditor - New Field value to be updated to editor
			mField.addPropertyChangeListener(vEditor);
			//
			centerPanel.add ((Component)vEditor, gbc);
			m_vEditors.add (vEditor);                   //  add to Editors
			//  Set Default
			Object defaultObject = mField.getDefault();
			mField.setValue (defaultObject, true);
			//
			if (voF.isRange)
			{
				//	To Label
				gbc.gridx = 2;
				gbc.weightx = 0;
				gbc.fill = GridBagConstraints.NONE;
				JLabel dash = new JLabel(" - ");
				centerPanel.add (dash, gbc);
				m_separators.add(dash);
				//  To Field
				gbc.gridx = 3;
				gbc.insets = fieldInsetRight;
				gbc.weightx = 1;
				gbc.fill = GridBagConstraints.HORIZONTAL;
				gbc.anchor = GridBagConstraints.WEST;
				//
				GridFieldVO voF2 = GridFieldVO.createParameter(voF);
				GridField mField2 = new GridField (voF2);
				m_mFields2.add (mField2);
				//	The Editor
				VEditor vEditor2 = VEditorFactory.getEditor(mField2, false);
				//  New Field value to be updated to editor
				mField2.addPropertyChangeListener(vEditor2);
				//
				centerPanel.add ((Component)vEditor2, gbc);
				m_vEditors2.add (vEditor2);
				//  Set Default
				Object defaultObject2 = mField2.getDefault();
				mField2.setValue (defaultObject2, true);
			}
			else
			{
				m_separators.add(null);
				m_mFields2.add (null);
				m_vEditors2.add (null);
			}
		}	//	createField

		/**
		 *	Editor Listener
		 *	@param evt Event
		 * 	@exception PropertyVetoException if the recipient wishes to roll back.
		 */
		public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException
		{
		//	log.fine( "ProcessParameterPanel.vetoableChange");
			processNewValue(evt.getNewValue(), evt.getPropertyName());
		}	//	vetoableChange

		private void processNewValue(Object value, String name) {
			if (value == null)
				value = new String("");

			if (value instanceof String)
				Env.setContext(Env.getCtx(), m_WindowNo, name, (String) value);
			else if (value instanceof Integer)
				Env.setContext(Env.getCtx(), m_WindowNo, name, ((Integer) value)
						.intValue());
			else if (value instanceof Boolean)
				Env.setContext(Env.getCtx(), m_WindowNo, name, ((Boolean) value)
						.booleanValue());
			else if (value instanceof Timestamp)
				Env.setContext(Env.getCtx(), m_WindowNo, name, (Timestamp) value);
			else
				Env.setContext(Env.getCtx(), m_WindowNo, name, value.toString());

			dynamicDisplay();
		}

		/**
		 * Dynamic Display.
		 * 
		 **/
		public void dynamicDisplay() {
			Component[] comps = centerPanel.getComponents();
			for (int i = 0; i < comps.length; i++) {
				Component comp = comps[i];
				String columnName = comp.getName();

				if (columnName != null && columnName.length() > 0) {
					int index = getIndex(columnName);
					if (m_mFields.get(index) != null) {
						if (m_mFields.get(index).isDisplayed(true)) { // check
							// context
							if (!comp.isVisible()) {
								comp.setVisible(true); // visibility
								if (m_mFields.get(index).getVO().isRange)
									m_separators.get(index).setText(" - ");
							}
							boolean rw = m_mFields.get(index).isEditablePara(true); // r/w - check if field is Editable
							m_vEditors.get(index).setReadWrite(rw);
							if (m_mFields.get(index).getVO().isRange)
								m_vEditors2.get(index).setReadWrite(rw);
						} else {
							if (comp.isVisible()) {
								comp.setVisible(false);
								if (m_mFields.get(index).getVO().isRange)
									m_separators.get(index).setText("");
							}
						}
					}
				}
			}
		} // Dynamic Display.

		/**
		 * getIndex. Get m_mFields index from columnName
		 * 
		 * @param columnName
		 * @return int
		 **/
		private int getIndex(String columnName) {

			for (int i = 0; i < m_mFields.size(); i++) {
				if (m_mFields.get(i).getColumnName().equals(columnName)) {
					return i;
				}
			}
			return 0;
		} // getIndex

		/* (non-Javadoc)
		 * @see org.compiere.apps.ProcessParameters#saveParameters()
		 */
		public boolean saveParameters()
		{
			log.config("");

			/**
			 *	Mandatory fields
			 *  see - MTable.getMandatory
			 */
			StringBuffer sb = new StringBuffer();
			int size = m_mFields.size();
			for (int i = 0; i < size; i++)
			{
				GridField field = (GridField)m_mFields.get(i);
				if (field.isMandatory(true))        //  check context
				{
					VEditor vEditor = (VEditor)m_vEditors.get(i);
					Object data = vEditor.getValue();
					if (data == null || data.toString().length() == 0)
					{
						field.setInserting (true);  //  set editable (i.e. updateable) otherwise deadlock
						field.setError(true);
						if (sb.length() > 0)
							sb.append(", ");
						sb.append(field.getHeader());
					}
					else
						field.setError(false);
					//  Check for Range
					VEditor vEditor2 = (VEditor)m_vEditors2.get(i);
					if (vEditor2 != null)
					{
						Object data2 = vEditor.getValue();
						GridField field2 = (GridField)m_mFields2.get(i);
						if (data2 == null || data2.toString().length() == 0)
						{
							field.setInserting (true);  //  set editable (i.e. updateable) otherwise deadlock
							field2.setError(true);
							if (sb.length() > 0)
								sb.append(", ");
							sb.append(field.getHeader());
						}
						else
							field2.setError(false);
					}   //  range field
				}   //  mandatory
			}   //  field loop


			if (sb.length() != 0)
			{
				ADialog.error(m_WindowNo, this, "FillMandatory", sb.toString());
				return false;
			}

			/**********************************************************************
			 *	Save Now
			 */
			for (int i = 0; i < m_mFields.size(); i++)
			{
				//	Get Values
				VEditor editor = (VEditor)m_vEditors.get(i);
				VEditor editor2 = (VEditor)m_vEditors2.get(i);
				Object result = editor.getValue();
				Object result2 = null;
				if (editor2 != null)
					result2 = editor2.getValue();
				
				//	Create Parameter
				MPInstancePara para = new MPInstancePara (Env.getCtx(), m_processInfo.getAD_PInstance_ID(), i);
				GridField mField = (GridField)m_mFields.get(i);
				para.setParameterName(mField.getColumnName());
				
				//	Date
				if (result instanceof Timestamp || result2 instanceof Timestamp)
				{
					para.setP_Date((Timestamp)result);
					if (editor2 != null && result2 != null)
						para.setP_Date_To((Timestamp)result2);
				}
				//	Integer
				else if (result instanceof Integer || result2 instanceof Integer)
				{
					if (result != null)
					{
						Integer ii = (Integer)result;
						para.setP_Number(ii.intValue());
					}
					if (editor2 != null && result2 != null)
					{
						Integer ii = (Integer)result2;
						para.setP_Number_To(ii.intValue());
					}
				}
				//	BigDecimal
				else if (result instanceof BigDecimal || result2 instanceof BigDecimal)
				{
					para.setP_Number ((BigDecimal)result);
					if (editor2 != null && result2 != null)
						para.setP_Number_To ((BigDecimal)result2);
				}
				//	Boolean
				else if (result instanceof Boolean)
				{
					Boolean bb = (Boolean)result;
					String value = bb.booleanValue() ? "Y" : "N";
					para.setP_String (value);
					//	to does not make sense
				}
				//	String
				else
				{
					if (result != null)
						para.setP_String (result.toString());
					if (editor2 != null && result2 != null)
						para.setP_String_To (result2.toString());
				}

				//  Info
				para.setInfo (editor.getDisplay());
				if (editor2 != null)
					para.setInfo_To (editor2.getDisplay());
				//
				para.save();
				log.fine(para.toString());
			}	//	for every parameter

			return true;
		}	//	saveParameters
		
		/**
		 * Restore window context.
		 * @author teo_sarca [ 1699826 ]
		 * @see org.compiere.model.GridField#restoreValue()
		 */
		protected void restoreContext() {
			for (GridField f : m_mFields) {
				if (f != null)
					f.restoreValue();
			}
			for (GridField f : m_mFields2) {
				if (f != null)
					f.restoreValue();
			}
		}
	}	//	ProcessParameterPanel
