/*
 * 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.richdatagrid;

import com.icesoft.faces.facelets.component.table.autosort.ISortableTableEventListener;
import com.icesoft.faces.facelets.component.table.autosort.SortableDataModel;
import com.icesoft.faces.facelets.component.table.filter.IFilteredTableEventListener;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.*;
import java.util.regex.Pattern;

public class FilterableSortableDataModel extends SortableDataModel {

    private List unfilteredList;
    private PutMonitoringStringHashMap columnFilterInput;
    private IFilteredTableEventListener filterListener;
    private static final String WILDCARD = "*";
    private static final String JAVA_WILDCARD = ".*";

    public Map getColumnFilterInput() {
        return columnFilterInput;
    }


    public FilterableSortableDataModel(Class clazz, List list, String defaultSortColumn,
                                       IFilteredTableEventListener filterListener, ISortableTableEventListener sortableListener) {
        super(list, defaultSortColumn, sortableListener);
        this.filterListener = filterListener;
        this.unfilteredList = list;
        this.columnFilterInput = new PutMonitoringStringHashMap();
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(clazz);
        for (PropertyDescriptor property : properties) {
            this.columnFilterInput.putWithoutApplyFilters(property.getName(), "");
        }

    }

    private class PutMonitoringStringHashMap implements Map, Serializable {

        private HashMap wrappedMap;

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

        public Object clone() {
            return wrappedMap.clone();
        }

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

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

        public Set entrySet() {
            return wrappedMap.entrySet();
        }

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

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

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

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

        public Set keySet() {
            return wrappedMap.keySet();
        }

        public String put(String key, String value) {
            String previousInput = (String) columnFilterInput.get(key);
            String result = (String) 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 putWithoutApplyFilters(String key, String value) {
            return (String) wrappedMap.put(key, value);
        }

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

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

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

        public Collection values() {
            return wrappedMap.values();
        }

        public PutMonitoringStringHashMap() {
            this.wrappedMap = new HashMap();
        }

        public Object put(Object key, Object value) {
            return put((String) key, (String) value);
        }

        public void putAll(Map m) {
            wrappedMap.putAll(m);
        }

    }

    private void applyFilters() {
        this.setWrappedData(new ArrayList(unfilteredList));
        for (Object o : columnFilterInput.keySet()) {
            String column = (String) o;
            String input = (String) columnFilterInput.get(column);
            if (input != null && input.length() > 0)
                applyColumnFilterToFilteredResults(column, input);
        }
        sort();
        if (filterListener != null)
            filterListener.filtersApplied(columnFilterInput);
    }


    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));
        }
        Iterator rowIter = ((List) this.getWrappedData()).iterator();
        while (rowIter.hasNext()) {
            Object row = rowIter.next();
            try {
                Object columnVal = BeanUtils.getProperty(row, column);
                if (columnVal != null) {
                    String columnString = columnVal.toString().toLowerCase();
                    if ((pattern == null && !columnString.startsWith(trimmedInput))
                            || (pattern != null && !pattern.matcher(columnString).matches())) {
                        rowIter.remove();
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public List getUnfilteredList() {
        return this.unfilteredList;
    }

}
