/*
 * ICESOFT COMMERCIAL SOURCE CODE LICENSE V 1.1
 *
 * The contents of this file are subject to the ICEsoft Commercial Source
 * Code License Agreement V1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the
 * License at
 * http://www.icesoft.com/license/commercial-source-v1.1.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.
 *
 * Copyright 2009-2015 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 */
package com.icesoft.faces.facelets.component.table.filter;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

import com.icesoft.faces.facelets.component.table.BaseTableBean;
import com.icesoft.faces.facelets.component.util.Util;

public class FilterTableBean<T> extends BaseTableBean {

	private static final long serialVersionUID = -4653475153547923149L;
	private List<T> unfilteredList;
	private Map<String, String> columnFilterInput;
	private transient Map<String, Field> columnFields;
	private String[] columns;
	private static final String WILDCARD = "*";
	private static final String JAVA_WILDCARD = ".*";
	private long refreshAllTimestamp = 0;
	
	
	private static final Logger LOG = Logger.getLogger(FilterTableBean.class.getName());

	public FilterTableBean(List<T> list, String[] columns) {

		if (columns == null || columns.length == 0)
			throw new IllegalArgumentException("Must provide column filters");

		this.unfilteredList = list;
		this.columns = columns;
		initializeDataModel();
		initializeFilterInput();
		initializeColumnsModel();
	}
	
	public long getRefreshAllTimestamp(){
		return refreshAllTimestamp;
	}
	
	private void initializeFilterInput(){
		columnFilterInput = new PutMonitoringStringHashMap();
		for (String column : columns) {
			((PutMonitoringStringHashMap) columnFilterInput).putNoFilter(
					column, "");
		}
	}
	
	private void initializeDataModel(){
		this.data = new ListDataModel<T>(new ArrayList<T>(unfilteredList));
	}

	private void initializeColumnsModel() {
		columnFields = new HashMap<String, Field>();
		if (unfilteredList != null && unfilteredList.size() > 0) {
			for (String column : columns) {
				Field field = null;
				try {
					Class c = Class.forName(unfilteredList.get(0).getClass()
							.getName());
					while (c != null) {
						try {
							field = c.getDeclaredField(column);
							break;
						} catch (NoSuchFieldException nse) {
							c = c.getSuperclass();
						}
					}
				} catch (ClassNotFoundException cnfe) {
					cnfe.printStackTrace();
				}

				if (field != null) {
					field.setAccessible(true);
					columnFields.put(column, field);
				} else {
					columnFilterInput.remove(column);
				}
			}
		}
	}

	public Map<String, String> getColumnFilterInput() {
		return columnFilterInput;
	}

	private void applyFilters() {
		if( data == null ){
			initializeColumnsModel();
		}
		if (unfilteredList != null && data != null) {
			this.data.setWrappedData(new ArrayList<T>(unfilteredList));
			for (Object o : columnFilterInput.keySet()) {
				String column = (String) o;
				String input = columnFilterInput.get(column);
				if (input != null && input.length() > 0)
					applyColumnFilterToFilteredResults(column, input);
			}
		}
	}
	
	private Map<String, Field> getColumnFields(){
		if( columnFields == null ){
			initializeColumnsModel();
		}
		return columnFields;
	}

	private void applyColumnFilterToFilteredResults(String column, String input) {
		Pattern pattern = null;
		String trimmedInput = input.toLowerCase().trim();
		if (input.indexOf("*") > -1) {
			pattern = Pattern.compile(input.replace(WILDCARD, JAVA_WILDCARD));
		}
		@SuppressWarnings("unchecked")
		Iterator<T> rowIter = ((List<T>) data.getWrappedData()).iterator();
		while (rowIter.hasNext()) {
			Object row = rowIter.next();
			try {
				Object columnVal = getColumnFields().get(column).get(row);
				if (columnVal != null) {
					String columnString = columnVal.toString().toLowerCase();
					if ((pattern == null && !columnString
							.startsWith(trimmedInput))
							|| (pattern != null && !pattern.matcher(
									columnString).matches())) {
						rowIter.remove();
					}
				}
			} catch (IllegalAccessException iae) {
				iae.printStackTrace();
			}
		}
	}

	public void clearInputField(String column) {
		columnFilterInput.put(column, null);
		applyFilters();

		UIComponent temp = Util.findComponent(FacesContext.getCurrentInstance()
				.getViewRoot(), column);
		if (temp != null && temp instanceof UIInput) {
			UIInput comp = (UIInput) temp;
			comp.setValue(null);
			comp.setSubmittedValue("");
		}
	}

	public void clearAllInputFields() {
		if( columnFilterInput != null ){
			columnFilterInput.clear();
		}
		if (columnFields != null && columnFields.size() > 0) {
			for (Object o : this.columnFields.keySet()) {
				clearInputField((String) o);
			}
		}
		refreshAllTimestamp = System.currentTimeMillis();
	}
	
	@Override
	public DataModel getData() {
    	if( data == null ){
    		initializeDataModel();
    		applyFilters();
    	}
        return data;
    }


	class PutMonitoringStringHashMap implements Map<String, String>,
			Serializable {

		private Map<String, String> wrappedMap;

		public void clear() {
			wrappedMap.clear();
		}

		@SuppressWarnings("unchecked")
		public Map<String, String> clone() {
			return (Map<String, String>) ((HashMap<String, String>) wrappedMap)
					.clone();
		}

		public boolean containsKey(Object key) {
			return wrappedMap.containsKey(key);
		}

		public boolean containsValue(Object value) {
			return wrappedMap.containsValue(value);
		}

		public Set<Map.Entry<String, String>> entrySet() {
			return wrappedMap.entrySet();
		}

		public boolean equals(Object o) {
			return wrappedMap.equals(o);
		}

		public String get(Object key) {
			return wrappedMap.get(key);
		}

		public int hashCode() {
			return wrappedMap.hashCode();
		}

		public boolean isEmpty() {
			return wrappedMap.isEmpty();
		}

		public Set<String> keySet() {
			return wrappedMap.keySet();
		}

		public String put(String key, String value) {
			String previousInput = columnFilterInput.get(key);
			String result = wrappedMap.put(key, value);
			if ((previousInput == null && columnFilterInput.get(key) != null)
					|| (previousInput != null && columnFilterInput.get(key) == null)
					|| (columnFilterInput.get(key) != null && !columnFilterInput
							.get(key).equals(previousInput)))
				applyFilters();
			return result;
		}

		public String putNoFilter(String key, String value) {
			return wrappedMap.put(key, value);
		}

		public String remove(Object key) {
			return wrappedMap.remove(key);
		}

		public int size() {
			return wrappedMap.size();
		}

		public String toString() {
			return wrappedMap.toString();
		}

		public Collection<String> values() {
			return wrappedMap.values();
		}

		public PutMonitoringStringHashMap() {
			this.wrappedMap = new HashMap<String, String>();
		}

		public void putAll(Map<? extends String, ? extends String> m) {
			wrappedMap.putAll(m);
		}
		
		
	}

}
