/******************************************************************************
 * Copyright (C) 2008 Low Heng Sin                                            *
 * 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.adempiere.webui.component;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.adempiere.webui.editor.WButtonEditor;
import org.adempiere.webui.editor.WEditor;
import org.adempiere.webui.editor.WEditorPopupMenu;
import org.adempiere.webui.editor.WebEditorFactory;
import org.adempiere.webui.event.ContextMenuListener;
import org.adempiere.webui.panel.AbstractADWindowPanel;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.util.GridTabDataBinder;
import org.adempiere.webui.window.ADWindow;
import org.compiere.model.GridField;
import org.compiere.model.GridTab;
import org.compiere.util.DisplayType;
import org.compiere.util.NamePair;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Image;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.ListitemRendererExt;
import org.zkoss.zul.Paging;
import org.zkoss.zul.RendererCtrl;

/**
 * ListItem renderer for GridTab list box.
 * @author hengsin
 *
 */
public class GridTabListItemRenderer implements ListitemRenderer, ListitemRendererExt, RendererCtrl {

	private static final int MAX_TEXT_LENGTH = 60;
	private GridTab gridTab;
	private int windowNo;
	private GridTabDataBinder dataBinder;
	private Map<GridField, WEditor> editors = new HashMap<GridField, WEditor>();
	private Paging paging;

	/**
	 * 
	 * @param gridTab
	 * @param windowNo
	 */
	public GridTabListItemRenderer(GridTab gridTab, int windowNo) {
		this.gridTab = gridTab;
		this.windowNo = windowNo;
		this.dataBinder = new GridTabDataBinder(gridTab);
	}
	
	/**
	 * @param listitem
	 * @param data
	 * @see ListitemRenderer#render(Listitem, Object)
	 */
	public void render(Listitem listitem, Object data) throws Exception {
		//don't render if not visible
		for(Component c = listitem.getParent(); c != null; c = c.getParent()) {
			if (!c.isVisible())
				return;
		}
		Object[] values = (Object[])data;
		int columnCount = gridTab.getTableModel().getColumnCount();
		GridField[] gridField = gridTab.getFields();
		for (int i = 0; i < columnCount; i++) {
			if (!gridField[i].isDisplayed()) {
				continue;
			}
			if (editors.get(gridField[i]) == null)
				editors.put(gridField[i], WebEditorFactory.getEditor(gridField[i], true));
			
			int rowIndex = listitem.getIndex();			
			if (paging != null && paging.getPageSize() > 0) {
				rowIndex = (paging.getActivePage() * paging.getPageSize()) + rowIndex;
			}
			Listcell cell = null;
			if (rowIndex == gridTab.getCurrentRow() && gridField[i].isEditable(true)) {
				cell = getEditorCell(gridField[i], values[i], i);
				cell.setParent(listitem);
			} else {
				if (gridField[i].getDisplayType() == DisplayType.YesNo) {
					cell = new Listcell("", null);
					cell.setParent(listitem);
					cell.setStyle("text-align:center");
					createReadonlyCheckbox(values[i], cell);
				} else {
					String text = getDisplayText(values[i], i);
					String display = text;
					if (text != null && text.length() > MAX_TEXT_LENGTH)
						display = text.substring(0, MAX_TEXT_LENGTH - 3) + "...";
					cell = new Listcell(display, null);
					cell.setParent(listitem);
					if (text != null && text.length() > MAX_TEXT_LENGTH)
						cell.setTooltiptext(text);
					if (DisplayType.isNumeric(gridField[i].getDisplayType())) {
						cell.setStyle("text-align:right");
					} else if (gridField[i].getDisplayType() == DisplayType.Image) {
						cell.setStyle("text-align:center");
					}
				}
			}
			CellListener listener = new CellListener((Listbox) listitem.getParent());
			cell.addEventListener(Events.ON_DOUBLE_CLICK, listener);
		}
	}

	private void createReadonlyCheckbox(Object value, Listcell cell) {
		Checkbox checkBox = new Checkbox();
		if (value != null && "true".equalsIgnoreCase(value.toString()))
			checkBox.setChecked(true);
		else
			checkBox.setChecked(false);
		checkBox.setDisabled(true);
		checkBox.setParent(cell);
	}

	private Listcell getEditorCell(GridField gridField, Object object, int i) {
		Listcell cell = new Listcell("", null);
		WEditor editor = editors.get(gridField);
		if (editor != null)  {
			if (editor instanceof WButtonEditor)
            {
				Object window = SessionManager.getAppDesktop().findWindow(windowNo);
            	if (window != null && window instanceof ADWindow)
            	{
            		AbstractADWindowPanel windowPanel = ((ADWindow)window).getADWindowPanel();
            		((WButtonEditor)editor).addActionListener(windowPanel);
            	}            		
            }
			else
			{
				editor.addValueChangeListener(dataBinder);
			}
			cell.appendChild(editor.getComponent());
			if (editor.getComponent() instanceof Checkbox || editor.getComponent() instanceof Image) {
				cell.setStyle("text-align:center");
			}
			else if (DisplayType.isNumeric(gridField.getDisplayType())) {
				cell.setStyle("text-align:right");
			}
			gridField.addPropertyChangeListener(editor);
			editor.setValue(gridField.getValue());
			WEditorPopupMenu popupMenu = editor.getPopupMenu();
            
            if (popupMenu != null)
            {
            	popupMenu.addMenuListener((ContextMenuListener)editor);
            	cell.appendChild(popupMenu);
            }
            
            //streach component to fill grid cell
            editor.fillHorizontal();
		}
		
		return cell;
	}
	
	/**
	 * Detach all editor and optionally set the current value of the editor as cell label.
	 * @param updateCellLabel
	 */
	public void stopEditing(boolean updateCellLabel) {
		for (Entry<GridField, WEditor> entry : editors.entrySet()) {
			if (entry.getValue().getComponent().getParent() != null) {
				if (updateCellLabel) {
					Listcell cell = (Listcell) entry.getValue().getComponent().getParent();
					if (entry.getKey().getDisplayType() == DisplayType.YesNo) {
						cell.setLabel("");
						createReadonlyCheckbox(entry.getValue().getValue(), cell);
					} else {				
						cell.setLabel(getDisplayText(entry.getValue().getValue(), getColumnIndex(entry.getKey())));
					}
				}
				entry.getValue().getComponent().detach();
				entry.getKey().removePropertyChangeListener(entry.getValue());
				entry.getValue().removeValuechangeListener(dataBinder);
			}
		}
	}

	private int getColumnIndex(GridField field) {
		GridField[] fields = gridTab.getFields();
		for(int i = 0; i < fields.length; i++) {
			if (fields[i] == field)
				return i;
		}
		return 0;
	}

	/**
	 * @see ListitemRendererExt#getControls()
	 */
	public int getControls() {
		return DETACH_ON_RENDER;
	}

	/**
	 * @param item
	 * @see ListitemRendererExt#newListcell(Listitem)
	 */
	public Listcell newListcell(Listitem item) {
		ListCell listCell = new ListCell();
		listCell.applyProperties();
		listCell.setParent(item);
		return listCell;
	}

	/**
	 * @param listbox
	 * @see ListitemRendererExt#newListitem(Listbox)
	 */
	public Listitem newListitem(Listbox listbox) {
		ListItem item = new ListItem();
		item.applyProperties();
		return item;
	}

	private Map<Integer, Map<Object, String>> lookupCache = null;
	
	private String getDisplayText(Object value, int columnIndex)
	{
		if (value == null)
			return "";
		
		GridField[] gridField = gridTab.getFields();
		if (gridField[columnIndex].isEncryptedField())
		{
			return "********";
		}
		else if (gridField[columnIndex].isLookup())
    	{
			if (value == null) return "";
			
			if (lookupCache != null)
			{
				Map<Object, String> cache = lookupCache.get(columnIndex);
				if (cache != null && cache.size() >0) 
				{
					String text = cache.get(value);
					if (text != null) 
					{
						return text;
					}				
				}
			}
			NamePair namepair = gridField[columnIndex].getLookup().get(value);
			if (namepair != null)
			{
				String text = namepair.getName();
				if (lookupCache != null)
				{
					Map<Object, String> cache = lookupCache.get(columnIndex);
					if (cache == null) 
					{
						cache = new HashMap<Object, String>();
						lookupCache.put(columnIndex, cache);
					}
					cache.put(value, text);
				}
				return text;
			}
			else
				return "";
    	}
    	else if (gridTab.getTableModel().getColumnClass(columnIndex).equals(Timestamp.class))
    	{
    		SimpleDateFormat dateFormat = DisplayType.getDateFormat(DisplayType.Date);
    		return dateFormat.format((Timestamp)value);
    	}
    	else if (DisplayType.isNumeric(gridField[columnIndex].getDisplayType()))
    	{
    		return DisplayType.getNumberFormat(gridField[columnIndex].getDisplayType()).format(value);
    	}
    	else if (DisplayType.Button == gridField[columnIndex].getDisplayType())
    	{
    		return "";
    	}
    	else if (DisplayType.Image == gridField[columnIndex].getDisplayType())
    	{
    		if (value == null || (Integer)value <= 0)
    			return "";
    		else
    			return "...";
    	}
    	else
    		return value.toString();
	}
	
	class CellListener implements EventListener {

		private Listbox _listbox;
		
		public CellListener(Listbox listbox) {
			_listbox = listbox;
		}
		
		public void onEvent(Event event) throws Exception {
			if (Events.ON_DOUBLE_CLICK.equals(event.getName())) {
				Event evt = new Event(Events.ON_DOUBLE_CLICK, _listbox);
				Events.sendEvent(_listbox, evt);
			}
		}
		
	}

	/**
	 * Is renderer initialize
	 * @return boolean
	 */
	public boolean isInitialize() {
		return !editors.isEmpty();
	}

	/**
	 * 
	 * @return active editor list
	 */
	public List<WEditor> getEditors() {
		List<WEditor> editorList = new ArrayList<WEditor>();
		if (!editors.isEmpty())
			editorList.addAll(editors.values());
		
		return editorList;
	}
	
	/**
	 * @param paging
	 */
	public void setPaging(Paging paging) {
		this.paging = paging;
	}

	/**
	 * @see RendererCtrl#doCatch(Throwable)
	 */
	public void doCatch(Throwable ex) throws Throwable {
		lookupCache = null;
	}

	/**
	 * @see RendererCtrl#doFinally()
	 */
	public void doFinally() {
		lookupCache = null;
	}

	/**
	 * @see RendererCtrl#doTry()
	 */
	public void doTry() {
		lookupCache = new HashMap<Integer, Map<Object,String>>();
	}
}
