/* CustomFiltersCtrl.java

	Purpose:
		
	Description:
		
	History:
		Fri, May 13, 2016 03:06:12 PM, Created by Henri

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

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

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.zkoss.poi.ss.usermodel.DateUtil;
import org.zkoss.poi.ss.usermodel.ZssContext;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zssex.ui.dialog.impl.DialogCallbackEvent;
import org.zkoss.zssex.ui.dialog.impl.DialogCtrlBase;
import org.zkoss.zul.Button;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Radiogroup;
import org.zkoss.zul.Window;
import org.zkoss.zss.model.SAutoFilter.FilterOp;
import org.zkoss.zss.model.SAutoFilter.NFilterColumn;
import org.zkoss.zss.model.SCell.CellType;
import org.zkoss.zss.model.SCellStyle;
import org.zkoss.zss.model.SCustomFilter;
import org.zkoss.zss.model.SAutoFilter;
import org.zkoss.zss.model.SCustomFilters;
import org.zkoss.zss.model.SDynamicFilter;
import org.zkoss.zss.model.STop10Filter;
import org.zkoss.zss.model.impl.AutoFilterImpl;
import org.zkoss.zss.model.impl.sys.FormatEngineImpl;
import org.zkoss.zss.model.impl.sys.InputResultImpl;
import org.zkoss.zss.model.sys.EngineFactory;
import org.zkoss.zss.model.sys.format.FormatContext;
import org.zkoss.zss.model.sys.format.FormatEngine;
import org.zkoss.zss.model.sys.format.FormatResult;
import org.zkoss.zss.model.sys.input.InputEngine;
import org.zkoss.zss.model.sys.input.InputParseContext;
import org.zkoss.zss.model.sys.input.InputResult;
import org.zkoss.zss.range.impl.FilterRowInfo;
import org.zkoss.zss.range.impl.LessThanOrEqual;

/**
 * 
 * @author Henri
 */
public class CustomFiltersCtrl extends DialogCtrlBase {
	private static final long serialVersionUID = 9029733549704530911L;

	private final static String URI = "~./zssex/dlg/customFilters.zul";
	
	public final static String ARG_F1_VALS = "f1vals";
	public final static String ARG_F2_VALS = "f2vals";
	
	public final static String ARG_F1_OP = "f1op";
	public final static String ARG_F2_OP = "f2op";
	public final static String ARG_F1_VAL = "f1val";
	public final static String ARG_F2_VAL = "f2val";
	public final static String ARG_ANDOR = "andor";
	public final static String ARG_ROW_TEXTS = "rowtexts";
	public final static String ARG_FILTER_TYPE = "filterType"; //ZSS-1234

	private ListModelList<FilterOp> f1opsModel;
	private ListModelList<FilterOp> f2opsModel;
	private ListModelList<FilterRowInfo> f1valsModel;
	private ListModelList<FilterRowInfo> f2valsModel;
	
	@Wire
	private Button okBtn;
	
	@Wire
	private Listbox f1operators;
	
	@Wire
	private Combobox f1values;
	
	@Wire
	private Listbox f2operators;
	
	@Wire
	private Combobox f2values;
	
	@Wire
	private Radiogroup andor;

	public static void show(EventListener<DialogCallbackEvent> callback, SAutoFilter filter, int index, FilterOp op) { //ZSS-1233
		final NFilterColumn fc = filter.getFilterColumn(index, false);  //ZSS-1233
		final SCustomFilters cusf = fc == null ? null : fc.getCustomFilters();
		Boolean isAnd = cusf == null ? Boolean.TRUE : cusf.isAnd();
		SCustomFilter f1 = cusf == null ? null : cusf.getCustomFilter1();
		SCustomFilter f2 = cusf == null ? null : cusf.getCustomFilter2();
		final int filterType = (Integer)
				((AutoFilterImpl)filter).getFilterType(index); //ZSS-1234
		
		
		Object val = null; //ZSS-1193, ZSS-1234
		Object val2 = null; //ZSS-1234
		FilterOp f1op = null;
		FilterOp f2op = null;
		if (op == FilterOp.between || op == FilterOp.betweenDates) {
			f1op = FilterOp.greaterThanOrEqual;
			f2op = FilterOp.lessThanOrEqual;
		} else {
			f1op = op;
		}
		if (f1op != FilterOp.custom) {
			if (f1op != (f1 != null ? f1.getOperator() : null)
			|| f2op != (f2 != null ? f2.getOperator() : null)){
				f1 = null;
				f2 = null;
				isAnd = true;
			}
		} else { //ZSS-1193
			final SDynamicFilter df = fc == null ? null : fc.getDynamicFilter();
			if (df != null) {
				//ZSS-1234
				final FilterOp opx = FilterOp.valueOf(df.getType());
				switch(opx) {
				case aboveAverage:
					op = FilterOp.greaterThan;
					val = df.getValue();
					break;
					
				case belowAverage:
					op = FilterOp.lessThan;
					val = df.getValue();
					break;
					
				case before:
				case after:
				case betweenDates:
				case tomorrow:
				case today:
				case yesterday:
				case nextWeek:
				case thisWeek:
				case lastWeek:
				case nextMonth:
				case thisMonth:
				case lastMonth:
				case nextQuarter:
				case thisQuarter:
				case lastQuarter:
				case nextYear:
				case thisYear:
				case lastYear:
				case yearToDate:
					op = FilterOp.within;
					val = formatDateText(df.getValue());
					val2 = formatDateText(df.getMaxValue());
					break;
					
				default:
					op = FilterOp.equal;
					break;
				}
			} else {
				final STop10Filter tf = fc == null ? null : fc.getTop10Filter();
				if (tf != null) {
					op = tf.isTop() ? FilterOp.greaterThanOrEqual : FilterOp.lessThanOrEqual;
					val = tf.getFilterValue();
				}
			}
		}
		
		f1op = f1 == null ? 
				op == null ? FilterOp.equal : 
					op == FilterOp.within ? FilterOp.afterEq : op : 
						f1.getOperator();
		f2op = f2 == null ? 
					op == FilterOp.within ? FilterOp.before : FilterOp.none : 
						f2.getOperator();

		//ZSS-1234
		if (filterType == 1) {
			f1op = convertToDateOp(f1op);
			f2op = convertToDateOp(f2op);
		}
		if (f1 != null) {
			if (filterType == 1) {
				val = parseInput(f1.getValue());
				val = formatDateText(val);
			} else {
				val = f1.getValue();
			}
			if (f2 != null) {
				if (filterType == 1) {
					val2 = parseInput(f2.getValue());
					val2 = formatDateText(val2);
				} else {
					val2 = f2.getValue();
				}
			}
		}

		final String f1val = val == null ? "" : val.toString();
		final String f2val = val2 == null ? "" : val2.toString();
		
		final List<FilterRowInfo> rowInfos = (List<FilterRowInfo>)
				((AutoFilterImpl)filter).getCachedSet(index); //ZSS-1233
		
		Map arg = newArg(callback);
		arg.put(ARG_F1_OP, f1op);
		arg.put(ARG_F2_OP, f2op);
		arg.put(ARG_ANDOR, isAnd);
		arg.put(ARG_F1_VAL, f1val);
		arg.put(ARG_F2_VAL, f2val);
		arg.put(ARG_ROW_TEXTS, rowInfos);
		arg.put(ARG_FILTER_TYPE, Integer.valueOf(filterType)); //ZSS-1234
		
		Window comp = (Window) Executions.createComponents(URI, null, arg);
		comp.doModal();
		return;
	}

	//ZSS-1234
	private static FilterOp convertToDateOp(FilterOp op) {
		if (op == null) {
			return op;
		}
		switch(op) {
		case greaterThan:
			return FilterOp.after;
		case greaterThanOrEqual:
			return FilterOp.afterEq;
		case lessThan:
			return FilterOp.before;
		case lessThanOrEqual:
			return FilterOp.beforeEq;
		case between:
			return FilterOp.betweenDates;
		default:
			return op;
		}
	}
	
	//ZSS-1234
	private static boolean isDateOp(FilterOp op) {
		return op == FilterOp.before 
				|| op == FilterOp.beforeEq 
				|| op == FilterOp.after 
				|| op == FilterOp.afterEq;
	}
	
	// ZSS-1234
	private static Object parseInput(String editText) {
		if (editText == null) {
			editText = "";
		}
		if (editText.startsWith("=")) { // skip formula parsing
			return editText;
		}
		final InputEngine ie = EngineFactory.getInstance().createInputEngine();
		InputResult irx = ie.parseInput(editText,
				SCellStyle.FORMAT_GENERAL, new InputParseContext(ZssContext
						.getCurrent().getLocale()));
		if (irx.getType() !=  CellType.NUMBER 
				&& irx.getType() != CellType.STRING) { // only Number and String
			return editText;
		}
		return irx.getValue();
	}

	// ZSS-1234
	private static String formatDateText(Object val) {
		final FormatContext formatContext = new FormatContext(ZssContext.getCurrent().getLocale());
		final FormatEngine formatEngine = new FormatEngineImpl();
		final String format = "m/d/yyyy"; // must be this default format
		
		if (!(val instanceof Number) && !(val instanceof Date)) {
			return val.toString();
		}
		
		if (val instanceof Number) {
			val = DateUtil.getJavaDate(((Number)val).doubleValue(), TimeZone.getTimeZone("UTC"));
		}
		final FormatResult result = formatEngine.format(format, val, formatContext, 8);
		return result.getText();
	}
	

	private void init(FilterOp f1op, String f1val, boolean isAnd, 
			FilterOp f2op, String f2val, List<FilterRowInfo> rowInfos) {
		f1opsModel = newOps(f1op);
		Set<FilterOp> f1ops = new HashSet<FilterOp>();
		f1ops.add(f1op);
		f1opsModel.setSelection(f1ops);		
		f1operators.setModel(f1opsModel);
		
		andor.setSelectedIndex(isAnd ? 0 : 1);
		
		f2opsModel = newOps(f1op);
		Set<FilterOp> f2ops = new HashSet<FilterOp>();
		f2ops.add(f2op);
		f2opsModel.setSelection(f2ops);
		f2operators.setModel(f2opsModel);
		
		f1valsModel = rowInfos == null ? 
			new ListModelList<FilterRowInfo>() : new ListModelList<FilterRowInfo>(rowInfos);
		f1values.setModel(f1valsModel);
		f1values.setValue(f1val);
				
		f2valsModel = rowInfos == null ? 
			new ListModelList<FilterRowInfo>() : new ListModelList<FilterRowInfo>(rowInfos);
		f2values.setModel(f2valsModel);
		f2values.setValue(f2val);
	}
	
	private ListModelList newOps(FilterOp op) {
		ListModelList<FilterOp> opmodel = new ListModelList<FilterOp>();
        opmodel.add(FilterOp.none);
        opmodel.add(FilterOp.equal);
        opmodel.add(FilterOp.notEqual);
        //ZSS-1234
        switch(op) {
        case after:
        case afterEq:
        case before:
        case beforeEq:
        case betweenDates:
        case tomorrow:
        case today:
        case yesterday:
        case nextWeek:
        case thisWeek:
        case lastWeek:
        case nextMonth:
        case thisMonth:
        case lastMonth:
        case nextQuarter:
        case thisQuarter:
        case lastQuarter:
        case nextYear:
        case thisYear:
        case lastYear:
        case yearToDate:
        case Q1:
        case Q2:
        case Q3:
        case Q4:
        case M1:
        case M2:
        case M3:
        case M4:
        case M5:
        case M6:
        case M7:
        case M8:
        case M9:
        case M10:
        case M11:
        case M12:
            opmodel.add(FilterOp.after);
            opmodel.add(FilterOp.afterEq);
            opmodel.add(FilterOp.before);
            opmodel.add(FilterOp.beforeEq);
            break;
    	default:
            opmodel.add(FilterOp.greaterThan);
            opmodel.add(FilterOp.greaterThanOrEqual);
            opmodel.add(FilterOp.lessThan);
            opmodel.add(FilterOp.lessThanOrEqual);
        }
        opmodel.add(FilterOp.beginWith);
        opmodel.add(FilterOp.notBeginWith);
        opmodel.add(FilterOp.endWith);
        opmodel.add(FilterOp.notEndWith);
        opmodel.add(FilterOp.contains);
        opmodel.add(FilterOp.notContains);
        
        return opmodel;
	}
	
	public void doAfterCompose(Window comp) throws Exception {
		super.doAfterCompose(comp);
		
		Map<?, ?> arg = Executions.getCurrent().getArg();
		final FilterOp f1op = (FilterOp)arg.get(ARG_F1_OP);
		final FilterOp f2op = (FilterOp)arg.get(ARG_F2_OP);
		final String f1val = (String)arg.get(ARG_F1_VAL);
		final String f2val = (String)arg.get(ARG_F2_VAL);
		final Boolean isAnd = (Boolean)arg.get(ARG_ANDOR);
		final List<FilterRowInfo> rowInfos = (List<FilterRowInfo>) arg.get(ARG_ROW_TEXTS);
		final int type = (Integer) arg.get(ARG_FILTER_TYPE); //ZSS-1234

		init(f1op, f1val, isAnd.booleanValue(), f2op, f2val, rowInfos);
	}

	@Listen("onOK=window; onClick=#okBtn;")
	public void onOK() {
		FilterOp f1op = f1opsModel.getSelection().iterator().next();
		FilterOp f2op = f2opsModel.getSelection().iterator().next();
		
		//ZSS-1193
		Set<FilterRowInfo> f1valSelSet = f1valsModel.getSelection();
		Set<FilterRowInfo> f2valSelSet = f2valsModel.getSelection();
		FilterRowInfo f1valInfo = f1valSelSet == null || f1valSelSet.isEmpty() ?
				null : f1valSelSet.iterator().next();
		FilterRowInfo f2valInfo = f2valSelSet == null || f2valSelSet.isEmpty() ?
				null : f2valSelSet.iterator().next();
		
		Object f1val = null;
		Object f2val = null;
		// If operation is for String type; always check with String
		switch(f1op) {
		case equal:
		case notEqual:
		case beginWith:
		case notBeginWith:
		case endWith:
		case notEndWith:
		case contains:
		case notContains:
			f1val = f1valInfo != null ? f1valInfo.getDisplay() : f1values.getValue();
			break;
			
		default:
			f1val = f1valInfo != null ? f1valInfo.getValue() : parseInput(f1values.getValue());
			//ZSS-1234
			if (f1val instanceof Date) {
				f1val = DateUtil.getExcelDate((Date) f1val);
			}
			break;
		}
		
		switch(f2op) {
		case equal:
		case notEqual:
		case beginWith:
		case notBeginWith:
		case endWith:
		case notEndWith:
		case contains:
		case notContains:
			f2val = f2valInfo != null ? f2valInfo.getDisplay() : f2values.getValue();
			break;
			
		default:
			f2val = f2valInfo != null ? f2valInfo.getValue() : parseInput(f2values.getValue());
			//ZSS-1234
			if (f2val instanceof Date) {
				f2val = DateUtil.getExcelDate((Date) f2val);
			}
			break;
		}
		Boolean isAnd = andor.getSelectedIndex() == 0;
		
		postCallback(ON_OK, newMap(
				newEntry(ARG_F1_OP, f1op), 
				newEntry(ARG_F1_VAL, f1val),
				newEntry(ARG_ANDOR, isAnd),
				newEntry(ARG_F2_OP, f2op), 
				newEntry(ARG_F2_VAL, f2val)));

		detach();
	}
	
	@Listen("onCancel = window; onClick = #cancelBtn")
	public void onCancel() {
		detach();
		postCallback(ON_CANCEL, null);
	}
}
