/******************************************************************************
 * The contents of this file are subject to the   Compiere License  Version 1.1
 * ("License"); You may not use this file except in compliance with the License
 * You may obtain a copy of the License at http://www.compiere.org/license.html
 * Software distributed under the License is distributed on an  "AS IS"  basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * The Original Code is Compiere ERP & CRM Smart Business Solution. The Initial
 * Developer of the Original Code is Jorg Janke. Portions created by Jorg Janke
 * are Copyright (C) 1999-2005 Jorg Janke.
 * All parts are Copyright (C) 1999-2005 ComPiere, Inc.  All Rights Reserved.
 * Contributor(s): ______________________________________.
 *****************************************************************************/
package com.ampiere.search;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Properties;
import java.util.logging.Level;

import org.apache.ecs.AlignType;
import org.compiere.framework.Lookup;
import org.compiere.model.MRole;
import org.compiere.util.CLogger;
import org.compiere.util.Ctx;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.Msg;
import org.compiere.util.NamePair;

import com.ampiere.dto.CodeValue;

/**
 *	Search Information and return selection - Base Class.
 *  <pre>
 *  Structure:
 *      parameterPanel  (JPanel) - for subclasses to add parameter fields
 *      scrollPame      (JScrollPane)
 *          m_table     (MiniTable)
 *      southPanel      (JPanel)
 *          confirmPanel
 *          statusPanel
 *  </pre>
 *
 *  @author 	Jorg Janke
 *  @version 	$Id: Info.java,v 1.1 2010/01/13 11:52:01 clmg Exp $
 */
public abstract class Info
{
	/**************************************************************************
	 *	Detail Constructor
	 *  @param frame parent frame
	 *  @param modal modal
	 *  @param WindowNo window no
	 *  @param tableName table name
	 *  @param keyColumn key column name
	 *  @param multiSelection muiliple selection
	 *  @param whereClause where clause
	 */
	protected Info (int windowNo, String tableName, String keyColumn,
		boolean multiSelection, String whereClause, Ctx ctx)
	{
		p_WindowNo = windowNo;
		p_tableName = tableName;
		p_keyColumn = keyColumn;
		p_multiSelection = multiSelection;
		if (whereClause == null || whereClause.indexOf('@') == -1)
			p_whereClause = whereClause;
		else
		{
			p_whereClause = Env.parseContext(ctx, p_WindowNo, whereClause, false, false);
			if (p_whereClause.length() == 0)
				log.log(Level.SEVERE, "Cannot parse context= " + whereClause);
		}

		try
		{
			jbInit();
		}
		catch(Exception ex)
		{
			log.log(Level.SEVERE, "Info", ex);
		}
	}	//	Info


	/** Master (owning) Window  */
	protected int				p_WindowNo;

	/** Table Name              */
	protected String            p_tableName;
	/** Key Column Name         */
	protected String            p_keyColumn;
	/** Enable more than one selection  */
	protected boolean			p_multiSelection;
	/** Initial WHERE Clause    */
	protected String			p_whereClause = "";

	/** Dynamic WHERE Clause    */
	protected String			p_sqlWhere = "";

	/** Model Index of Key Column   */
	protected int               m_keyColumnIndex;

	/** OK pressed                  */
	private boolean			    m_ok = false;
	/** Cancel pressed - need to differentiate between OK - Cancel - Exit	*/
	private boolean			    m_cancel = false;
	/** Result IDs              */
	private ArrayList<Integer>	m_results = new ArrayList<Integer>(3);

	/** Layout of Grid          */
	protected Info_Column[]     p_layout;
	/** Main SQL Statement      */
	private String              m_sqlMain;
	/** Count SQL Statement		*/
	private String              m_sqlCount;
	/** Order By Clause         */
	private String              m_sqlOrder;

	/** Loading success indicator       */
	protected boolean	        p_loadedOK = false;

	/**	Logger			*/
	protected CLogger log = CLogger.getCLogger(getClass());

	
	//-------------------------------------------------------------------------
	protected ArrayList<Integer> columnAttrList = new ArrayList<Integer>();
	protected String title;
	public String getTitle() { return title; }
	protected ArrayList<ComponentBean> componentList = new ArrayList<ComponentBean>();
	protected Hashtable<String, Integer> componentMapping = new Hashtable<String, Integer>();
	//-------------------------------------------------------------------------
	public ArrayList<ComponentBean> getComponentList() {
		return componentList;
	}

	/**
	 *  Factory Constructor
	 *  @param  WindowNo	window no
	 *  @param  tableName   table name of the search
	 *  @param  keyColumn   key column of the search
	 *  @param	value		query value
	 *  @param  multiSelection  allow to select more than one row
	 *  @param  whereClause fully qualified where clause for the search
	 *  @return special or general Info Window
	 */
	public static Info create (int WindowNo, String tableName, String keyColumn, String value,
		boolean multiSelection, String whereClause, Ctx ctx)
	{
		Info info = null;

		if (tableName.equals("C_BPartner")) {
//			info = new InfoBPartner(WindowNo,  value, !Env.getContext(ctx,"IsSOTrx").equals("N"), multiSelection, whereClause, ctx);
			info = new InfoBPartner(WindowNo,  value, !ctx.getContext("IsSOTrx").equals("N"), multiSelection, whereClause,ctx);	
		} else if (tableName.equals("M_Product")) {
			info = new InfoProduct(WindowNo,  0,0, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("C_Invoice")) {
			info = new InfoInvoice(WindowNo, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("A_Asset")) {
			info = new InfoAsset(WindowNo, 0, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("C_Order")) {
			info = new InfoOrder(WindowNo, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("M_InOut")) {
			info = new InfoInOut(WindowNo, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("C_Payment")) {
			info = new InfoPayment(WindowNo, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("C_CashLine")) {
			info = new InfoCashLine(WindowNo, value, multiSelection, whereClause, ctx);
		} else if (tableName.equals("S_ResourceAssignment")) {
			info = new InfoAssignment(WindowNo, value, multiSelection, whereClause, ctx);
		} else {
			info = new InfoGeneral(WindowNo, value, tableName, keyColumn, multiSelection, whereClause, ctx);
		}
		return info;
	}   //  create

	
	/**
	 *	Static Init
	 *  @throws Exception
	 */
	protected void jbInit() throws Exception
	{
	}	//	jbInit

	/**
	 *  Loaded correctly
	 *  @return true if loaded OK
	 */
	public boolean loadedOK()
	{
		return p_loadedOK;
	}   //  loadedOK

	/**
	 *	Set Status Line
	 *  @param text text
	 *  @param error error
	 */
	public void setStatusLine (String text, boolean error)
	{
	}	//	setStatusLine

	/**
	 *	Set Status DB
	 *  @param text text
	 */
	public void setStatusDB (String text)
	{
	}	//	setStatusDB


	
	/**************************************************************************
	 *  Prepare Table, Construct SQL (m_m_sqlMain, m_sqlAdd)
	 *  and size Window
	 *  @param layout layout array
	 *  @param from from clause
	 *  @param staticWhere where clause
	 *  @param orderBy order by clause
	 */
	protected void prepareTable (Info_Column[] layout, String from, String staticWhere, String orderBy)
	{
		p_layout = layout;
		StringBuffer sql = new StringBuffer ("SELECT ");
		//  add columns & sql
		for (int i = 0; i < layout.length; i++)
		{
			if (i > 0)
				sql.append(", ");
			sql.append(layout[i].getColSQL());
			//  adding ID column
			if (layout[i].isIDcol())
				sql.append(",").append(layout[i].getIDcolSQL());

			if (m_keyColumnIndex < 0) {
				if (layout[i].getColClass() == IDColumn.class)
					m_keyColumnIndex = i;
			}
		}

		sql.append( " FROM ").append(from);
		//
		sql.append(" WHERE ").append(staticWhere);
		m_sqlMain = sql.toString();
		m_sqlCount = "SELECT COUNT(*) FROM " + from + " WHERE " + staticWhere;
		//
		m_sqlOrder = "";
		if (orderBy != null && orderBy.length() > 0)
			m_sqlOrder = " ORDER BY " + orderBy;

		if (m_keyColumnIndex == -1)
			log.log(Level.SEVERE, "No KeyColumn - " + sql);
	}   //  prepareTable

	/**
	 *	Save Selection	- Called by dispose
	 */
	protected void saveSelection ()
	{
	}	//	saveSelection

	/**
	 *  Get the key of currently selected row
	 *  @return selected key
	 */
	protected Integer getSelectedRowKey()
	{
		return null;
	}   //  getSelectedRowKey

	/**
	 *	Get selected Keys
	 *  @return selected keys (Integers)
	 */
	public Object[] getSelectedKeys()
	{
		if (!m_ok || m_results.size() == 0)
			return null;
		return m_results.toArray();
	}	//	getSelectedKeys;

	/**
	 *	Get (first) selected Key
	 *  @return selected key
	 */
	public Object getSelectedKey()
	{
		if (!m_ok || m_results.size() == 0)
			return null;
		return m_results.get(0);
	}	//	getSelectedKey

	/**
	 *	Is cancelled?
	 *	- if pressed Cancel = true
	 *	- if pressed OK or window closed = false
	 *  @return true if cancelled
	 */
	public boolean isCancelled()
	{
		return m_cancel;
	}	//	isCancelled

	/**
	 *	Get where clause for (first) selected key
	 *  @return WHERE Clause
	 */
	public String getSelectedSQL()
	{
		//	No results
		Object[] keys = getSelectedKeys();
		if (keys == null || keys.length == 0)
		{
			log.config("No Results - OK=" 
				+ m_ok + ", Cancel=" + m_cancel);
			return "";
		}
		//
		StringBuffer sb = new StringBuffer(getKeyColumn());
		if (keys.length > 1)
			sb.append(" IN (");
		else
			sb.append("=");

		//	Add elements
		for (int i = 0; i < keys.length; i++)
		{
			if (getKeyColumn().endsWith("_ID"))
				sb.append(keys[i].toString()).append(",");
			else
				sb.append("'").append(keys[i].toString()).append("',");
		}

		sb.replace(sb.length()-1, sb.length(), "");
		if (keys.length > 1)
			sb.append(")");
		return sb.toString();
	}	//	getSelectedSQL;

	/**
	 *	Dispose (not OK)
	 */
	public void dispose()
	{
		dispose(false);
	}	//	dispose

	/**
	 *	Dispose and save Selection
	 *	@param ok OK pressed
	 */
	public void dispose(boolean ok)
	{
		log.config("OK=" + ok);
		m_ok = ok;
	}	//	dispose

	/**
	 *  Get Table name Synonym
	 *  @return table name
	 */
	String getTableName()
	{
		return p_tableName;
	}   //  getTableName

	/**
	 *  Get Key Column Name
	 *  @return column name
	 */
	String getKeyColumn()
	{
		return p_keyColumn;
	}   //  getKeyColumn

	
	/**************************************************************************
	 *  Get dynamic WHERE part of SQL
	 *	To be overwritten by concrete classes
	 *  @return WHERE clause
	 */
	abstract String getSQLWhere();
	
	/**
	 *  Set Parameters for Query
	 *	To be overwritten by concrete classes
	 *  @param pstmt statement
	 *  @param forCount for counting records
	 *  @throws SQLException
	 */
	abstract void setParameters (PreparedStatement pstmt, boolean forCount) 
		throws SQLException;

	/**
	 *  Reset Parameters
	 *	To be overwritten by concrete classes
	 */
	void doReset()					{}
	/**
	 *  Has Reset (false)
	 *	To be overwritten by concrete classes
	 *  @return true if it has reset (default false)
	 */
	boolean hasReset()				{return false;}
	/**
	 *  History dialog
	 *	To be overwritten by concrete classes
	 */
	void showHistory()					{}
	/**
	 *  Has History (false)
	 *	To be overwritten by concrete classes
	 *  @return true if it has history (default false)
	 */
	boolean hasHistory()				{return false;}
	/**
	 *  Customize dialog
	 *	To be overwritten by concrete classes
	 */
	void customize()					{}
	/**
	 *  Has Customize (false)
	 *	To be overwritten by concrete classes
	 *  @return true if it has customize (default false)
	 */
	boolean hasCustomize()				{return false;}
	/**
	 *  Zoom action
	 *	To be overwritten by concrete classes
	 */
	void zoom()							{}
	/**
	 *  Has Zoom (false)
	 *	To be overwritten by concrete classes
	 *  @return true if it has zoom (default false)
	 */
	boolean hasZoom()					{return false;}
	/**
	 *  Save Selection Details
	 *	To be overwritten by concrete classes
	 */
	void saveSelectionDetail()          {}
	
	/**
	 * 	Test Row Count
	 *	@return row count
	 */
	public int testCount(Ctx ctx)
	{
		long start = System.currentTimeMillis();
		String dynWhere = getSQLWhere();
		StringBuffer sql = new StringBuffer (m_sqlCount);
		if (dynWhere.length() > 0)
			sql.append(dynWhere);   //  includes first AND
		String countSql = Msg.parseTranslation(ctx, sql.toString());	//	Variables
		countSql = MRole.getDefault().addAccessSQL(countSql, getTableName(), MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO);

		log.finer(countSql);
		int no = -1;
		try
		{
			PreparedStatement pstmt = DB.prepareStatement(countSql, null);
			setParameters (pstmt, true);
			ResultSet rs = pstmt.executeQuery();
			if (rs.next()) {
				no = rs.getInt(1);
			}
			rs.close();
			pstmt.close();
		}
		catch (Exception e)
		{
			log.log(Level.SEVERE, countSql, e);
			no = -2;
		}
		log.fine("#" + no + " - " + (System.currentTimeMillis()-start) + "ms");
		return no;
	}	//	testCount

	/**************************************************************************
	 *  Execute Query
	 */
	public ArrayList executeQuery(Ctx ctx, int maxRecNo, Lookup lookup)
	{
		long start = System.currentTimeMillis();

		//
		String dynWhere = getSQLWhere();
		StringBuffer sql = new StringBuffer (m_sqlMain);
		if (dynWhere.length() > 0) {
			sql.append(dynWhere);   //  includes first AND
		}
		sql.append(m_sqlOrder);
		String dataSql = Msg.parseTranslation(ctx, sql.toString());	//	Variables
		dataSql = MRole.getDefault().addAccessSQL(dataSql, getTableName(), MRole.SQL_FULLYQUALIFIED, MRole.SQL_RO);
		log.finer(dataSql);

		try
		{
			PreparedStatement pstmt = DB.prepareStatement(dataSql, null);
			setParameters (pstmt, false);	//	no count
			log.fine("Start query - " + (System.currentTimeMillis()-start) + "ms");
			ResultSet rs = pstmt.executeQuery();
			log.fine("End query - " + (System.currentTimeMillis()-start) + "ms");

			ArrayList<ColBean> resultList = new ArrayList<ColBean>();
			int count = 0;
			while (rs.next())
			{
				if (++count > maxRecNo) {
					break;
				}

				ColBean colBean = new ColBean();
				int colOffset = 1;  //  columns start with 1
				int beanOffset = 0;
				for (int col = 0; col < p_layout.length; col++)
				{
					Object data = null;
					Class c = p_layout[col].getColClass();
					int colIndex = col + colOffset;
					String style = "";
					if (c == IDColumn.class) {
						data = new Integer(rs.getInt(colIndex));
					} else if (c == Boolean.class) {
						data = rs.getString(colIndex);
						if (data == null || "".equals(data)) {
							data = "N";
						}
						style += "text-align: " + AlignType.CENTER + ";";
					} else if (c == Timestamp.class) {
						data = rs.getTimestamp(colIndex);
						style += "white-space: nowrap;";
					} else if (c == BigDecimal.class) {
						data = rs.getBigDecimal(colIndex);
						style += "text-align: " + AlignType.RIGHT + ";";
					} else if (c == Double.class) {
						data = new Double(rs.getDouble(colIndex));
						style += "text-align: " + AlignType.RIGHT + ";";
					} else if (c == Integer.class) {
						data = new Integer(rs.getInt(colIndex));
						style += "text-align: " + AlignType.RIGHT + ";";
					} else if (c == KeyNamePair.class) {
						data = rs.getString(colIndex);
						colOffset++;
					} else {
						data = rs.getString(colIndex);
					}

					colBean.setColData(beanOffset, data);
					colBean.setColTitle(beanOffset, p_layout[col].getColHeader());
					colBean.setStyle(beanOffset, style);
					if (col == m_keyColumnIndex) {
						colBean.setColId(data.toString());
						String colName = data.toString();
						if (lookup != null) {
							NamePair pair = lookup.getDirect(data, false, true);
							if (pair != null) {
								colName = pair.getName();
							}
						}
						colBean.setColName(colName);
					}
					beanOffset++;
				}

				resultList.add(colBean);
			}

			rs.close();
			pstmt.close();
			return resultList;
		}
		catch (SQLException e)
		{
			log.log(Level.SEVERE, dataSql, e);
		}

		return null;
	}   //  executeQuery

	/**
	 * 	Create dynamic Query WHERE
	 */
	public void createWhereClause(String where, SimpleDateFormat dateFormat)
	{
		p_sqlWhere = "";
		if (where == null || where.length() == 0) {
			return;
		}

		String p[] = where.split(";");
		if (p == null || p.length == 0) {
			return;
		}

		Hashtable<Integer, String> valueMapping = new Hashtable<Integer, String>();
		Hashtable<Integer, String> valueMapping2 = new Hashtable<Integer, String>();
		for (int i = 0; i < p.length; i++) {
			String pp[] = p[i].split(",");
			if (pp == null || pp.length != 2) {
				continue;
			}

			try {
				if (pp[1] != null && pp[1].split(":") != null) {
					String ppp[] = pp[1].split(":");
					valueMapping.put(new Integer(Integer.parseInt(pp[0])), ppp[0]);
				} else {
					valueMapping.put(new Integer(Integer.parseInt(pp[0])), pp[1]);
				}
				valueMapping2.put(new Integer(Integer.parseInt(pp[0])), pp[1]);
			} catch (Exception e) {
				log.log(Level.SEVERE, "InfoPAttribute : getWhereClause", e);
			}
		}

		for (int i = 0; i < componentList.size(); i++) {
			ComponentBean bean = (ComponentBean)componentList.get(i);
			String s = getValue(valueMapping2, i);
			switch (bean.getType()) {
			case ComponentBean.TYPE_TEXT:
			case ComponentBean.TYPE_NUMBER:
			case ComponentBean.TYPE_DATE:
				bean.setContent(s);
				break;

			case ComponentBean.TYPE_CHECKBOX:
				if (s != null) {
					if ("Y".equalsIgnoreCase(s)) {
						bean.setContent("Y");
					} else {
						bean.setContent("N");
					}
				}
				break;

			case ComponentBean.TYPE_SELECT:
				bean.setLabel(s);	// Label used for selected value
				break;

			case ComponentBean.TYPE_LOOKUP:
				if (s != null && s.length() > 0) {
					bean.setContent(s);
				} else {
					bean.setContent("");
				}
				break;
			}
		}

		p_sqlWhere = createQuery(valueMapping, dateFormat);
		log.config(p_sqlWhere);
	}	//	getQuery

	/**
	 * 	Create Query
	 *  <code>
	 * 	Available synonyms:
	 *		M_Product p
	 *		M_ProductPrice pr
	 *		M_AttributeSet pa
	 *	</code>
	 *	@return query
	 */
	protected String createQuery(Hashtable valueMapping, SimpleDateFormat dateFormat) {
		return "";
	}

	/**
	 * Get search value
	 * @param valueMapping
	 * @param idx
	 * @return
	 */
	protected String getValue(Hashtable valueMapping, int idx) {
		String s = (String)valueMapping.get(idx);
		if (s != null && s.length() > 0) {
			return s;
		}
		return null;
	}

	/**
	 * Get component search attribute value
	 * @param valueMapping
	 * @param key
	 * @return
	 */
	protected String getComponetValue(Hashtable valueMapping, String key) {
		Integer idx = (Integer)componentMapping.get(key);
		if (idx != null && valueMapping.get(idx) != null) {
			return getValue(valueMapping, idx.intValue());
		}
		return null;
	}

	/**
	 * 
	 * @return
	 */
	public ArrayList getColumnAttrList() {
		return columnAttrList;
	}

	/**
	 *	Get table direct select items
	 *	@param lookup
	 *	@return array
	 */
	protected ArrayList<CodeValue> getTableDirectList(Lookup lookup)
	{
		ArrayList<CodeValue> list = new ArrayList<CodeValue>();
		list.add(new CodeValue("", ""));

		if (lookup != null) {
			ArrayList dataList = lookup.getData(false, false, false, false);
			if (dataList != null) {
				for (int i = 0; i < dataList.size(); i++) {
					NamePair pair = (NamePair)dataList.get(i);
					if (pair != null) {
						list.add(new CodeValue(pair.getID(), pair.getName()));
					}
				}
			}
		}

		return list;
	}	//	getAttributeList
}	//	Info
