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

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;

import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

import org.apache.commons.beanutils.BeanToPropertyValueTransformer;

/**
 * Helper class for sortable datatables, will autosort fields when used as a
 * list in conjunction with the <ice:dataTable> and <ice:commandSortHeader>
 * components. Sortable columns have to be specified with a sort column name
 * corresponding to field on the object type of the SortableList.java class.
 * This class uses the Commons BeanUtils api to find the field to sort, and access the
 * values. This requires that the row-level object type conform to the JavaBeans api.
 */
public class SortableDataModel extends DataModel implements Serializable {

    private String sort;
    private boolean ascending;
    private boolean needsSort = false;
    private ISortableTableEventListener listener;
    private transient ListDataModel<Serializable> wrappedDataModel;
    private List<Serializable> data;
    
    private static final Logger LOG = Logger.getLogger(SortableDataModel.class.getName());

    public SortableDataModel(List<Serializable> list, String defaultSortColumn, ISortableTableEventListener listener) {
        this.data = list;
        sort = defaultSortColumn;
        this.listener = listener;
        initializeWrappedModel();
    }
    
    public SortableDataModel(List list, String defaultSortColumn){
    	this(list,defaultSortColumn,null);
    }
    
    private void initializeWrappedModel(){
    	this.wrappedDataModel = new ListDataModel<Serializable>(data);
    }
    
    private ListDataModel<Serializable> getWrappedModel(){
    	if( this.wrappedDataModel == null ){
    		initializeWrappedModel();
    	}
    	return this.wrappedDataModel;
    }

    public void sort() {
    	final BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer(sort);

        Comparator<Serializable> comparator = new Comparator<Serializable>() {

            public int compare(Serializable o1, Serializable o2) {
                if (sort == null) {
                    return 0;
                }
                int result = 0;

                try {
                    Object prop1 = transformer.transform(o1);
                    Object prop2 = transformer.transform(o2);
                    if (prop1 != null && prop2 != null) {
                        if (Comparable.class.isInstance(prop1)) {
                            result = ascending ? ((Comparable<Serializable>) prop1)
                                    .compareTo((Serializable)prop2) : ((Comparable<Serializable>) prop2)
                                    .compareTo((Serializable)prop1);
                        }
                    } else if (prop1 == null && prop2 != null){
                        result = ascending ? -1 : 1;
                    }
                    else if (prop1 != null && prop2 == null){
                        result = ascending ? 1 : -1;
                    }
                    else if( prop1 == null && prop2 == null ){
                    	result = 0;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                
                return result;
            }
        };
        Collections.sort((List<Serializable>) this.getWrappedData(), comparator);
        if (listener != null)
            listener.columnSorted(sort);

    }

    /**
     * Get the current sort column.
     *
     * @return the column name String
     */
    public String getSort() {
        return sort;
    }

    /**
     * Will set the current column name to sort by, but expects the
     * setAscending() method to actually trigger to the sort.
     *
     * @param sort the column name string to sort on
     */
    public void setSort(String sort) {
        if (sort != null && !sort.equals(this.sort)) {
            needsSort = true;
            this.ascending = true;
        }
        this.sort = sort;
    }

    /**
     * Get the current ascending state.
     *
     * @return whether to sort ascending or descending
     */
    public boolean isAscending() {
        return ascending;
    }

    /**
     * Set the ascending flag and sort the list if it needs sorting.
     *
     * @param ascending whether to sort ascending or descending
     */
    public void setAscending(boolean ascending) {
        if (ascending != this.ascending || needsSort) {
            this.ascending = ascending;
            sort();
        }
    }

    public void resetList(List<Serializable> list) {
        ((List<Serializable>) this.getWrappedData()).clear();
        ((List<Serializable>) this.getWrappedData()).addAll(list);
        sort();
    }

	@Override
	public int getRowCount() {
		return getWrappedModel().getRowCount();
	}

	@Override
	public Object getRowData() {
		return getWrappedModel().getRowData();
	}

	@Override
	public int getRowIndex() {
		return getWrappedModel().getRowIndex();
	}

	@Override
	public Object getWrappedData() {
		return getWrappedModel().getWrappedData();
	}

	@Override
	public boolean isRowAvailable() {
		return getWrappedModel().isRowAvailable();
	}

	@Override
	public void setRowIndex(int rowIndex) {
		getWrappedModel().setRowIndex(rowIndex);
	}

	@Override
	public void setWrappedData(Object wrappedData) {
		getWrappedModel().setWrappedData(wrappedData);
	}
}