/*
 * 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.component.dragdrop.DndEvent;
import com.icesoft.faces.component.dragdrop.DropEvent;
import com.icesoft.faces.component.ext.HtmlDataTable;
import com.icesoft.faces.component.ext.RowSelectorEvent;
import com.icesoft.faces.context.effects.JavascriptContext;
import com.icesoft.faces.facelets.component.table.IEditableTable;
import com.icesoft.faces.facelets.component.table.ISortableTable;
import com.icesoft.faces.facelets.component.table.editable.AbstractEditableTableBean;
import com.icesoft.faces.facelets.component.table.editable.IEditableTableEventListener;
import com.icesoft.faces.facelets.component.table.filter.IFilteredTableEventListener;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import java.beans.PropertyDescriptor;
import java.util.*;
import java.util.logging.Logger;

public class RichDataGridBean extends AbstractEditableTableBean
        implements ISortableTable, IEditableTable {

    private IRichDataColumn[] columns;
    private boolean showHiddenColumnsMenu;
    private long random;
    private boolean filtering;
    private String columnWidths;
    // Editable property in Objects bound to the dataTable. Determines whether rowSelection works on a specific row.
    private RichDataBooleanRowProperty editableRowProperty;
    
    private Logger LOG = Logger.getLogger(RichDataGridBean.class.getName());

    public RichDataBooleanRowProperty getEditableRowProperty() {
        return editableRowProperty;
    }

    //*********** CTORS ****************************
    public RichDataGridBean(Class c, IRichDataColumn[] columns, RichDataBooleanRowProperty editableRowProperty, IEditableTableEventListener eventListener) {
        initializeRichDataGrid(c, columns, editableRowProperty, null, eventListener);
    }

    public RichDataGridBean(List list, IRichDataColumn[] columns, RichDataBooleanRowProperty editableRowProperty, IEditableTableEventListener eventListener) {
        initializeRichDataGrid(null, columns, editableRowProperty, list, eventListener);
    }

    public RichDataGridBean(List list) {
        initializeRichDataGrid(null, null, null, list, null);
    }

    public RichDataGridBean(Class c, List list, IRichDataColumn[] columns, RichDataBooleanRowProperty editableRowProperty, IEditableTableEventListener eventListener) {
        initializeRichDataGrid(c, columns, editableRowProperty, list, eventListener);
    }


    private void initializeRichDataGrid(Class c, IRichDataColumn[] columns, RichDataBooleanRowProperty editableRowProperty, List list, IEditableTableEventListener listener) {
        if (c != null) {
            this.clazz = c;
        } else {
            if (list.size() > 0) {
                this.clazz = list.get(0).getClass();
            } else {
                throw new IllegalArgumentException(this.getClass().getSimpleName()
                        + " must be initialized with a row type class or a non-empty list");
            }
        }
        this.eventListener = listener;
        if (editableRowProperty != null) {
            this.editableRowProperty = editableRowProperty;
        }
        if (columns == null) {
            this.columns = createAutomaticColumnStructure();
        } else {
            this.columns = columns;
        }
        this.properties = new HashSet();
        for (IRichDataColumn column : this.columns) {
            this.properties.add(column.getProperty());
        }
        this.data = new FilterableSortableDataModel(this.clazz, list, this.columns[0].getProperty(),
                new FilteredTableEventListener(), null);
        updateColumnVisibleOrdering();
        updateRandom();
    }

    public void setEditableRowProperty(RichDataBooleanRowProperty editableRowProperty) {
        this.editableRowProperty = editableRowProperty;
    }

    private class FilteredTableEventListener implements IFilteredTableEventListener {

        public void filtersApplied(Map filterInputMap) {
            updateRandom();
        }
    }

    protected void createDefaultDataModel() {
        data = new FilterableSortableDataModel(clazz, new ArrayList(), columns[0].getProperty(),
                new FilteredTableEventListener(), null);
    }

    private void updateColumnVisibleOrdering() {

        List list = new ArrayList();
        //reset all columns
        for (IRichDataColumn column : columns) {
            ((BaseRichDataColumn) column).firstInTable = false;
            ((BaseRichDataColumn) column).lastInTable = false;
            list.add((BaseRichDataColumn) column);
        }

        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            if (((IRichDataColumn) iter.next()).isHidden())
                iter.remove();
        }
        if (list.size() > 0) {
            ((BaseRichDataColumn) list.get(0)).firstInTable = true;
            ((BaseRichDataColumn) list.get(list.size() - 1)).lastInTable = true;
        }
        columnWidths = null;

    }

    /**
     * A copy of the column structure.
     * 
     * @return
     */
    public IRichDataColumn[] getColumns() {
        IRichDataColumn[] copy = new IRichDataColumn[columns.length];
        for(int i = 0 ; i < columns.length ; i++ ){
           copy[i] = columns[i];
        }
        return copy;
    }

    public void rowSelectionListener(RowSelectorEvent evt) {
    	
    	data.setRowIndex(evt.getRow());
        Object row = data.getRowData();
        if (editableRowProperty != null) {
            Boolean result = editableRowProperty.callBooleanRowPropertyGetter(row);
            if (!result) {
                selectedRows.remove(row);
                return;
            }
        }
        if (evt.isSelected()) {
        	if (eventListener != null) {
                eventListener.rowSelected(row);
            }
            if (selectedRowEdits.get(row) == null) {
                try {
                    selectedRowEdits.put(row, BeanUtils.cloneBean(row));
                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
            selectedRows.put(row, Boolean.TRUE);

        } else {
            selectedRows.remove(row);
        }
    }

    public String getSortColumn() {
        return ((FilterableSortableDataModel) getData()).getSort();
    }

    public boolean isSortAscending() {
        return ((FilterableSortableDataModel) getData()).isAscending();
    }

    public void setSortAscending(boolean sortAscending) {
        ((FilterableSortableDataModel) getData()).setAscending(sortAscending);
    }

    public void setSortColumn(String sortColumn) {
        ((FilterableSortableDataModel) getData()).setSort(sortColumn);
    }

    public void toggleHiddenColumnsMenu(ActionEvent evt) {
        this.showHiddenColumnsMenu = !this.showHiddenColumnsMenu;
    }

    public List getHiddenColumns() {
        List list = new ArrayList();
        for (IRichDataColumn column : columns) {
            if (column.isHidden()) {
                list.add(column);
            }
        }
        return list;
    }

    public void showHiddenColumn(ActionEvent evt) {
        String property = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap().get("columnProperty");
        for (IRichDataColumn column : columns) {
            if (column.getProperty().equals(property)) {
                column.setHidden(false);
                columnWidths = null;
                updateRandom();
                updateColumnVisibleOrdering();
                break;
            }
        }
    }

    public void moveColumn(DropEvent event) {
        if (event.getEventType() == DndEvent.DROPPED) {
            IRichDataColumn draggedCol = (IRichDataColumn) event.getTargetDragValue();
            IRichDataColumn droppedCol = (IRichDataColumn) event.getTargetDropValue();
            System.out.println("dragged: " + draggedCol);
            System.out.println("dropped: " + droppedCol);
            int col1Index = -1, col2Index = -1;
            for (int i = 0; i < columns.length; i++) {
                if (columns[i] == draggedCol)
                    col1Index = i;
                if (columns[i] == droppedCol)
                    col2Index = i;
            }
            columns[col1Index] = droppedCol;
            columns[col2Index] = draggedCol;
            updateRandom();
        }

    }

    private void fireColumnEffect(UIComponent comp) {
        if (comp != null) {
            FacesContext fc = FacesContext.getCurrentInstance();
            String clientId = comp.getClientId(fc);
            JavascriptContext.addJavascriptCall(fc, "$('" + clientId + "').highlight({startcolor:'#fda505'});");
        }
    }

    public void moveColumnLeft(ActionEvent evt) {
        String property = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap().get("col");
        IRichDataColumn colA = getColumnByPropertyName(property);
        for (int i = 0; i < columns.length; i++) {
            if (columns[i] == colA) {
                if (i == 0)
                    break;
                int colBIndex = i - 1;
                while (true) {
                    if (!columns[colBIndex].isHidden()) {
                        IRichDataColumn colB = columns[colBIndex];
                        columns[colBIndex] = colA;
                        columns[i] = colB;
                        fireColumnEffect(findNextContainerSiblingOfColumn(evt, colBIndex));
                        i = columns.length;
                        break;
                    }
                    colBIndex--;
                    if (colBIndex < 0) {
                        i = columns.length;
                        break;
                    }
                }
            }
        }
        updateColumnVisibleOrdering();
    }

    private UIComponent findNextContainerSiblingOfColumn(ActionEvent evt, int columnIndexToFire) {
    	UIComponent dataTable = evt.getComponent();
    	UIComponent target = null;
    	while( !(dataTable instanceof HtmlDataTable) && dataTable != null ){
    		dataTable = dataTable.getParent();
    	}
    	if( dataTable != null && dataTable instanceof UINamingContainer ){
    		target = dataTable.getChildren().get(columnIndexToFire).getFacet("header");
    	}
        return target;
    }

    public void moveColumnRight(ActionEvent evt) {
        String property = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap().get("col");
        IRichDataColumn colA = getColumnByPropertyName(property);
        for (int i = 0; i < columns.length; i++) {
            if (columns[i] == colA) {
                if (i == columns.length - 1) {
                    break;
                }
                int colBIndex = i + 1;
                while (true) {
                    if (!columns[colBIndex].isHidden()) {
                        IRichDataColumn colB = columns[colBIndex];
                        columns[colBIndex] = colA;
                        columns[i] = colB;
                        fireColumnEffect(findNextContainerSiblingOfColumn(evt, colBIndex));
                        i = columns.length;
                        break;
                    }
                    colBIndex++;
                    if (colBIndex == columns.length) {
                        i = columns.length;
                        break;
                    }
                }
            }
        }
        updateColumnVisibleOrdering();
    }

    private void updateRandom() {
        random = new Random().nextLong();
    }

    public long getRandom() {
        return random;
    }

    public void createNewRow(ActionEvent evt) {
        super.createNewRow(evt);
        this.updateRandom();
    }

    /**
     * Create a new row for the table above or below an existing row.
     * The new row object will be created by reflection.
     * 
     * @param existingRow An existing row
     * @param location 'above' or 'below'
     */
    public void createNewRow(Object existingRow, String location) {
        super.createNewRow(existingRow, location, null);
        this.updateRandom();
    }
    
    /**
     * Crate a new row the table at a specific index. The new
     * row object will be created by reflection.
     * 
     * @param index The index to insert the row.
     */
    public void createNewRowAtIndex(int index){
        super.createNewRowAtIndex(index);
        this.updateRandom();
    }
    
    public void hideColumn(ActionEvent evt) {
        String property = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap().get("col");
        IRichDataColumn col = getColumnByPropertyName(property);
        col.setHidden(true);
        updateColumnVisibleOrdering();
        columnWidths = null;
        updateRandom();
    }

    private IRichDataColumn getColumnByPropertyName(String propertyName) {
        for (IRichDataColumn column : columns) {
            if (column.getProperty().equals(propertyName))
                return column;
        }
        return null;
    }

    public void deleteRow(ActionEvent evt) {
        super.deleteRow(evt);
        updateRandom();
    }

    public void toggleColumnFiltering(ActionEvent evt) {
        filtering = !filtering;
        updateRandom();
    }

    public boolean isFiltering() {
        return filtering;
    }

    public boolean isShowHiddenColumnsMenu() {
        return showHiddenColumnsMenu;
    }

    private IRichDataColumn[] createAutomaticColumnStructure() {

        List list = new ArrayList();
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(clazz);
        if (properties != null) {
            for (PropertyDescriptor property : properties) {
            	if( property.getWriteMethod() != null){
            		IRichDataColumn col = BaseRichDataColumn.asIRichDataColumn(property);
                    if (col != null)
                        list.add(col);
            	}
                
            }
        }
        return (IRichDataColumn[]) list.toArray(new IRichDataColumn[0]);
    }

    public List getListData() {
        return ((FilterableSortableDataModel) data).getUnfilteredList();
    }

    public void addRow(int index, Object row) {
        ((List) getData().getWrappedData()).add(index, row);
    }

    public void removeRow(Object row) {
        getListData().remove(row);
        ((List) getData().getWrappedData()).remove(row);
    }

    public String getColumnWidths() {
        if (columnWidths == null) {
            StringBuffer strBuf = new StringBuffer("1px,");//dummy first val for unrendered row selection column
            for (int i = 0 ; i < columns.length ; i++ ) {
            	IRichDataColumn column = columns[i];
                strBuf.append(column.getWidth());
                strBuf.append(",");
            }
            strBuf.append("60px");//operator col
            columnWidths = strBuf.toString();
        }
        return columnWidths;

    }
    
    /**
     * Returns a copy of the RichDataGridBean with a new column structure.
     * 
     * @param columns The new column structure
     * @return a new copy of the bean with the new column structure
     */
    public RichDataGridBean cloneWithNewColumnStructure(IRichDataColumn[] columns){
       return new RichDataGridBean(this.clazz, (List)this.data.getWrappedData(), columns, this.editableRowProperty, this.eventListener);       
    }


}
