/* CustomSortWindowCtrl.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Aug 17, 2010 6:34:44 PM , Created by Sam
}}IS_NOTE

Copyright (C) 2009 Potix Corporation. All Rights Reserved.

*/
package org.zkoss.zssex.ui.dialog;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.event.SerializableEventListener;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zss.api.Range;
import org.zkoss.zss.api.Ranges;
import org.zkoss.zss.api.AreaRef;
import org.zkoss.zss.api.model.CellData.CellType;
import org.zkoss.zss.api.model.Sheet;
import org.zkoss.zss.model.CellRegion;
import org.zkoss.zss.range.impl.DataRegionHelper;
import org.zkoss.zss.range.SRange;
import org.zkoss.zss.ui.Spreadsheet;
import org.zkoss.zssex.ui.dialog.impl.DialogCallbackEvent;
import org.zkoss.zssex.ui.dialog.impl.DialogCtrlBase;
import org.zkoss.zssex.ui.dialog.impl.RefocusDialogCtrlBase;
import org.zkoss.zul.Button;
import org.zkoss.zul.Caption;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

/**
 * @author Sam
 * @author dennis
 */
public class CustomSortCtrl extends RefocusDialogCtrlBase {

	private static final long serialVersionUID = 1L;
	
	
	private final static String URI = "~./zssex/dlg/customSort.zul";
	
	public final static String ARG_SELECTION = "selection";
	public final static String ARG_SHEET = "sheet";


	public static final String ARG_CASE_SENSITIVE = "caseSensitive";
	public static final String ARG_HAS_HEADER = "hasHeader";
	public static final String ARG_ORIENTATION = "orientation";
	public static final String ARG_RULES = "rules";
	
	
	/**
	 * Selected sort level target by user
	 */
	private ListModelList<SortRule> sortRuleModel;
	
	private List<String> availableSortIndex = new ArrayList<String>();

	@Wire
	private Caption rangeCaption; // ZSS-1261
	@Wire
	private Listbox sortRuleBox;
	@Wire
	private Checkbox caseSensitiveBox;
	@Wire
	private Checkbox hasHeaderBox;
	@Wire
	private Combobox sortOrientationBox;
	
	@Wire
	private Button addBtn;
	@Wire
	private Button delBtn;
	@Wire
	private Button upBtn;
	@Wire
	private Button downBtn;
	
	AreaRef selection;
	Sheet sheet;
	
	public static void show(EventListener<DialogCallbackEvent> callback,Sheet sheet,AreaRef selection, Spreadsheet ss) {
		Map arg = newArg(callback);
		
		arg.put(ARG_SHEET, sheet);
		arg.put(ARG_SELECTION, selection);
		arg.put(ARG_SPREADSHEET, ss);
		
		Window comp = (Window)Executions.createComponents(URI, null, arg);
		comp.doModal();
		return;
	}

	
	public void doAfterCompose(Window comp) throws Exception {
		super.doAfterCompose(comp);
		Map arg = Executions.getCurrent().getArg();
		selection = (AreaRef)arg.get(ARG_SELECTION);
		sheet = (Sheet)arg.get(ARG_SHEET);

		sortOrientationBox.setSelectedIndex(0);//column
		sortRuleModel = new ListModelList();
		sortRuleModel.add(new SortRule());
		
		
		sortRuleBox.setItemRenderer(sortRuleRenderer);
		
		refreshSortRule();

	}
	
	private void refreshSortRule(){
		updateAvailableList();
		
		sortRuleBox.setModel((ListModel)null);//clear and update
		sortRuleBox.setModel(sortRuleModel);
		
		updateButtons();
	}
	
	private void updateAvailableList() {
		availableSortIndex.clear();
		// ZSS-1261
		final boolean header = hasHeaderBox.isChecked();
		final int row1 = selection.getRow();
		final int row2 = selection.getLastRow();
		final int col1 = selection.getColumn();
		final int col2 = selection.getLastColumn();
		if (sortOrientationBox.getSelectedIndex()==0) {// sort from top to bottom
			//ZSS-1261
			hasHeaderBox.setDisabled(false);
			if (!header) {
				// Column X
				for (int i = col1; i <= col2; i++){
					Range r = Ranges.range(sheet,row1,i);
					availableSortIndex.add(new String("Column " + Ranges.getColumnRefString(i)));
				}
				rangeCaption.setLabel(new String("(" + Ranges.getAreaRefString(row1, col1, row2, col2) + ")"));
			} else if (row1 == row2) { // only one row with header(the row is the header)
				// (Column X)
				for (int i = col1; i <= col2; i++){
					Range r = Ranges.range(sheet,row1,i);
					availableSortIndex.add(new String("(Column " + Ranges.getColumnRefString(i) + ")"));
				}
				rangeCaption.setLabel(new String("(" + Ranges.getAreaRefString(row1, col1, row2, col2) + ")"));
			} else {
				// Header Text at header row
				for (int i = col1; i <= col2; i++){
					Range r = Ranges.range(sheet,row1,i);
					availableSortIndex.add(new String(r.getCellFormatText()));
				}
				rangeCaption.setLabel(new String("(" + Ranges.getAreaRefString(row1 + 1, col1, row2, col2) + ")"));
			}
		} else {// sort from left to right; ignore hasHeaderBox
			// Row X
			hasHeaderBox.setDisabled(true); //ZSS-1261
			for (int i = row1; i <= row2; i++){
				Range r = Ranges.range(sheet,i,col1);
				availableSortIndex.add(new String("Row " + Ranges.getRowRefString(i)));
			}
			rangeCaption.setLabel(new String("(" + Ranges.getAreaRefString(row1, col1, row2, col2) + ")"));
		}
	}
	
	@Listen("onSelect=#sortRuleBox")
	public void onSelect$sortRuleBox() {
		updateButtons();
	}
	
	private void updateButtons() {
		int idx = getSingleSelectionIndex(sortRuleModel);
		upBtn.setDisabled(idx<=0);
		downBtn.setDisabled(idx<0 || idx == ( sortRuleModel.size() - 1));
		
		addBtn.setDisabled(sortRuleModel.size()>=3);
		delBtn.setDisabled(idx<0);
	}
	
	@Listen("onClick=#addBtn")
	public void onClick$addBtn() {
		if(sortRuleModel.size()>=3){
			return;
		}
		sortRuleModel.add(new SortRule());
		updateButtons();
	}
	
	@Listen("onClick=#delBtn")
	public void onClick$delBtn () {
		int idx = getSingleSelectionIndex(sortRuleModel);
		if (idx < 0)
			return;
		sortRuleModel.remove(idx);
		updateButtons();
	}
	
	@Listen("onClick=#upBtn")
	public void onClick$upBtn () {
		int idx = getSingleSelectionIndex(sortRuleModel);
		if (idx <= 0)
			return;
		
		SortRule o1 = (SortRule)sortRuleModel.get(idx);
		SortRule o2 = (SortRule)sortRuleModel.get(idx-1);
		sortRuleModel.set(idx-1, o1);
		sortRuleModel.set(idx, o2);
		sortRuleModel.addToSelection(o1);
		
		updateButtons();
	}
	
	@Listen("onClick=#downBtn")
	public void onClick$downBtn() {
		int idx = getSingleSelectionIndex(sortRuleModel);
		if (idx<0 || idx == ( sortRuleModel.size() - 1))
			return;
		
		SortRule o1 = (SortRule)sortRuleModel.get(idx);
		SortRule o2 = (SortRule)sortRuleModel.get(idx+1);
		sortRuleModel.set(idx+1, o1);
		sortRuleModel.set(idx, o2);
		sortRuleModel.addToSelection(o1);
		updateButtons();
	}
	
	@Listen("onOK=#window;onClick=#okBtn")
	public void onClick$okBtn() {
		if (checkEmptySortRule(sortRuleModel.getInnerList())
				|| checkDuplicateSortIndex(sortRuleModel.getInnerList())) {
			return;
		}
		
		postCallback(ON_OK, newMap(
				newEntry(ARG_CASE_SENSITIVE, caseSensitiveBox.isChecked()),
				//ZSS-1261: header flag ignored if sort from left to right
				newEntry(ARG_HAS_HEADER, sortOrientationBox.getSelectedIndex()==1? false:hasHeaderBox.isChecked()),  
				newEntry(ARG_ORIENTATION, sortOrientationBox.getSelectedIndex()==1?"row":"column"),
				newEntry(ARG_RULES, sortRuleModel.getInnerList())));
		
		detach();
	}
		
	private boolean checkEmptySortRule(List<SortRule> list) {
		boolean r = false;
		boolean column = sortOrientationBox.getSelectedIndex()==0;
		int w = selection.getLastColumn()-selection.getColumn();
		int h = selection.getLastRow()-selection.getRow();
		for (SortRule s : list) {
			if (s.sortIndex == -1){
				r = true;
				break;
			}
			if(column && s.sortIndex>w){
				r = true;
				break;
			}else if(!column && s.sortIndex>h){
				r = true;
				break;
			}
		}
		if(list.size()==0 || r){
			Messagebox.show(Labels.getLabel("zssex.dlg.sort.err.hasEmptySortRule"));
			r = true;
		}
		return r;
	}
	
	private boolean checkDuplicateSortIndex(List<SortRule> list) {
		HashMap<Integer, Boolean> map = new HashMap<Integer, Boolean>();
		for (SortRule s : list) {
			if ( map.containsKey( Integer.valueOf(s.sortIndex) ) ) {
				String title = null;
				if (sortOrientationBox.getSelectedIndex()==0){//column 
					title = Labels.getLabel("zssex.dlg.column")+" " + Ranges.getColumnRefString(selection.getColumn()+s.sortIndex);
				}else{
					title = Labels.getLabel("zssex.dlg.row")+" " + Ranges.getRowRefString(selection.getRow()+s.sortIndex);
				}
				Messagebox.show(Labels.getLabel("zssex.dlg.sort.err.duplicateSortRule",new Object[]{title}));
				return true;
			} else
				map.put(Integer.valueOf(s.sortIndex), Boolean.valueOf(true));
		}
		return false;
	}

	@Listen("onCheck=#hasHeaderBox")
	public void onCheck$hasHeaderBox() {
		refreshSortRule();
	}
	@Listen("onSelect=#sortOrientationBox")
	public void onSelect$sortOrientationBox() {
		refreshSortRule();
	}
	
	private ListitemRenderer sortRuleRenderer = new ListitemRenderer() {

		@Override
		public void render(Listitem item, Object data, int index)
				throws Exception {
			Listcell cell = new Listcell();
			SortRule sort = (SortRule)data;
			cell.setLabel(index==0?Labels.getLabel("zssex.dlg.sort.sortBy"):Labels.getLabel("zssex.dlg.sort.thenBy"));
			item.appendChild(cell);
			
			cell = new Listcell();
			item.appendChild(cell);
			SortIndexCombobox indexComp = new SortIndexCombobox(item,sort);
			cell.appendChild(indexComp);
			
			cell = new Listcell();
			item.appendChild(cell);
			SortAlgorithmCombobox algorComp = new SortAlgorithmCombobox(item,sort);
			cell.appendChild(algorComp);
		}
	};
	
	
	
	
	public class SortIndexCombobox extends Combobox {
		SortRule sort;
		Listitem listitem;
		
		public SortIndexCombobox (Listitem listitem,SortRule sort) {
			setWidth("100%");
			setReadonly(true);
			setMold("rounded");
			this.sort = sort;
			ListModelList model = new ListModelList(availableSortIndex);
			
			if(sort.sortIndex>=0 && sort.sortIndex<model.size()){
				model.addToSelection(model.get(sort.sortIndex));
			}else if(model.size()==0){
				sort.sortIndex=-1;
			}
			
			setModel(model);
			this.listitem = listitem;
		} 

		public void onSelect() {
			sort.sortIndex = getSingleSelectionIndex(getModel());
			//fire linking change
			Events.postEvent(new Event("onSortRuleChanged",listitem,sort));
		}
	}
	
	public class SortAlgorithmCombobox extends Combobox {
		private SortRule sort;
		Listitem listitem;
		
		/**
		 * Default display the String 
		 */
		private final static String STR_ASCENDING_KEY = "zssex.dlg.sort.str.ascending";
		private final static String STR_DESCENDING_KEY = "zssex.dlg.sort.str.descending";
		private final static String NUM_ASCENDING_KEY = "zssex.dlg.sort.num.ascending";
		private final static String NUM_DESCENDING_KEY = "zssex.dlg.sort.num.descending";
		
		public SortAlgorithmCombobox(Listitem listitem,SortRule sort) {
			this.sort = sort;
			setWidth("100%");
			setReadonly(true);
			setMold("rounded");
			
			refreshModel();
			
			//linking from SortIndexCombobox
			listitem.addEventListener("onSortRuleChanged", new SerializableEventListener<Event>() {
				private static final long serialVersionUID = 3238074278451651852L;

				@Override
				public void onEvent(Event event) throws Exception {
					if(event.getData()==SortAlgorithmCombobox.this.sort){
						//reset model to force rerender
						refreshModel();
					}
				}
			});
			this.listitem = listitem;
		}
		
		void refreshModel(){
			ListModelList model = new ListModelList(createLabelList());
			model.addToSelection(model.get(SortAlgorithmCombobox.this.sort.ascending?0:1));
			setModel(model);
		}
		
		private List<String> createLabelList(){
			List<String> list = new ArrayList(2);
			if (isAllCellNumberType(sort.sortIndex)) {
				list.add(Labels.getLabel(NUM_ASCENDING_KEY));
				list.add(Labels.getLabel(NUM_DESCENDING_KEY));
			} else {
				list.add(Labels.getLabel(STR_ASCENDING_KEY));
				list.add(Labels.getLabel(STR_DESCENDING_KEY));
			}
			return list;
		}
		
		private boolean isAllCellNumberType(int idx) {
			boolean column = sortOrientationBox.getSelectedIndex()==0; 
			boolean header = hasHeaderBox.isChecked();//head, should skip first row or column
			if(column){
				int col = selection.getColumn()+idx;
				//seek row
				int row = selection.getRow()+(header?1:0);
				int lrow = selection.getLastRow();
				for(;row<=lrow;row++){
					Range r = Ranges.range(sheet, row, col);
					if(r.getCellData().getResultType()!=CellType.NUMERIC){
						return false;
					}
				}
			}else{
				int row = selection.getRow()+idx;
				//seek row
				int col = selection.getColumn()+(header?1:0);
				int lcol = selection.getLastColumn();
				for(;col<=lcol;col++){
					Range r = Ranges.range(sheet, row, col);
					if(r.getCellData().getResultType()!=CellType.NUMERIC){
						return false;
					}
				}
			}
			return true;
		}

		public void onSelect (SelectEvent evt) {
			sort.ascending = getSingleSelectionIndex(getModel())!=1;//1 is descending
			//no linking
		}
	}
	
	public static class SortRule {

		/**
		 * Indicate sort target, row or column index number
		 */
		public int sortIndex = 0;
		
		/**
		 * Indicate sort order, either descending or ascending
		 * <p> Default false: ascending
		 */
		public boolean ascending = true;
		
		public String toString(){
			return "{index:"+sortIndex+",ascending:"+ascending+"}";
		}
	}
	
	@Listen("onCancel=window; onClick=#cancelBtn")
	public void onCancel(){
		detach();
		postCallback(ON_CANCEL, null);
	}

}