/**
 *
 */

package org.richfaces.component;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.model.ExtendedDataModel;
import org.richfaces.event.extdt.ChangeColumnVisibilityEvent;
import org.richfaces.event.extdt.ColumnResizeEvent;
import org.richfaces.event.extdt.DragDropEvent;
import org.richfaces.event.extdt.ExtTableFilterEvent;
import org.richfaces.event.extdt.ExtTableSortEvent;
import org.richfaces.model.ExtendedDataTableModifiableModel;
import org.richfaces.model.ExtendedTableDataModel;
import org.richfaces.model.FilterField;
import org.richfaces.model.ModifiableModel;
import org.richfaces.model.SortField2;

/**
 * JSF component class
 * 
 */
public abstract class UIExtendedDataTable extends UIDataTable implements
		Selectable, Filterable, Sortable2 {

	/**
	 * COMPONENT_TYPE
	 */
	public static final String COMPONENT_TYPE = "org.richfaces.ExtendedDataTable";

	/**
	 * COMPONENT_FAMILY
	 */
	public static final String COMPONENT_FAMILY = "org.richfaces.ExtendedDataTable";

	protected ExtendedDataTableState state;

	public abstract Object getActiveRowKey();

	public abstract void setActiveRowKey(Object activeRowKey);

	public void broadcast(FacesEvent event) throws AbortProcessingException {
		super.broadcast(event);
		if (event instanceof AjaxEvent) {
			//TODO nick - add regions from component too
			AjaxContext.getCurrentInstance().addComponentToAjaxRender(this);
		} else if (event instanceof DragDropEvent) {
			processDradDrop((DragDropEvent) event);
		} else if (event instanceof ChangeColumnVisibilityEvent) {
			processChangeColumnVisibility((ChangeColumnVisibilityEvent) event);
		} else if (event instanceof ColumnResizeEvent) {
			processColumnResize((ColumnResizeEvent) event);
		} else if (event instanceof ExtTableSortEvent) {
			processSortingChange((ExtTableSortEvent) event);
		} else if (event instanceof ExtTableFilterEvent) {
			processFilteringChange((ExtTableFilterEvent) event);
		}

	}

	public void queueEvent(FacesEvent event) {
	    if(event.getSource() instanceof UIExtendedDataTable) {
    		if (event instanceof AjaxEvent) {
    			event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    		} else if (event instanceof DragDropEvent) {
    			new AjaxEvent(this).queue();
    			event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    		} else if (event instanceof ChangeColumnVisibilityEvent) {
    			new AjaxEvent(this).queue();
    			event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    		} else if (event instanceof ColumnResizeEvent) {
    			event.setPhaseId(PhaseId.RENDER_RESPONSE);
    		} else if (event instanceof ExtTableSortEvent) {
    			new AjaxEvent(this).queue();
    			event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    		} else if (event instanceof ExtTableFilterEvent) {
    			new AjaxEvent(this).queue();
    			event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    		}
	    }
	    super.queueEvent(event);
	}

	public Iterator<UIColumn> getSortedColumns() {
		return new SortedColumnsIterator(this);
	}

	public Iterator<UIColumn> getChildColumns() {
		return new ExtendedTableColumnsIterator(this);
	}

	public void processDradDrop(DragDropEvent event) {
		String dragValue = event.getDragValue().toString();// dnd_drag_script
		String dropValue = event.getDropValue().toString();// dnd_drop_script

		ensureTableStateInitialized();
		state.changeColumnsOrder(dragValue, dropValue, event.isDropBefore());
		state.publishChanges(getFacesContext(), this);

		getFacesContext().renderResponse();
	}

	public List<UIComponent> getSortedChildren() {
		ensureTableStateInitialized();
		return state.sortColumns(getFacesContext(), super.getChildren());
	}

	public void ensureTableStateInitialized() {
		if (state == null) {
			state = ExtendedDataTableState.getExtendedDataTableState(this);
		}
	}

	public void processChangeColumnVisibility(ChangeColumnVisibilityEvent event) {
		ensureTableStateInitialized();
		state.toggleColumnVisibility(this, event.getColumnId());
		state.publishChanges(getFacesContext(), this);

		getFacesContext().renderResponse();
	}// processChangeColumnVisibility

	public void processSortingChange(ExtTableSortEvent event) {
		resetModel();
		getFacesContext().renderResponse();
	}

	public void processFilteringChange(ExtTableFilterEvent event) {
		resetModel();
		resetGroupVisibilityState();
		getFacesContext().renderResponse();
	}

	public boolean isColumnVisible(UIComponent column) {
		ensureTableStateInitialized();
		return state.isColumnVisible(column.getId());
	}// isColumnVisible

	public void processColumnResize(ColumnResizeEvent event) {
		ensureTableStateInitialized();
		state.changeColumnSize(this, event.getColumnWidths());
		state.publishChanges(getFacesContext(), this);

		getFacesContext().renderResponse();
	}// processChangeColumnVisibility

	public String getColumnSize(UIComponent column) {
		ensureTableStateInitialized();
		return state.getColumnSize(column);
	}

	public boolean isGroupingOn() {
		ensureTableStateInitialized();
		return state.isGroupingOn();
	}

	public String getGroupingColumnId() {
		ensureTableStateInitialized();
		return state.getGroupingColumnId();
	}

	public UIColumn getGroupingColumn() {
		String groupingColumnId = getGroupingColumnId();
		if (groupingColumnId == null)
			return null;
		for (Iterator<UIColumn> columns = getChildColumns(); columns.hasNext();) {
			UIColumn child = columns.next();
			if (groupingColumnId.equalsIgnoreCase(child.getId())) {
				return child;
			}
		}
		return null;
	}// getGroupingColumn

	public void setGroupingColumn(org.richfaces.component.UIColumn column) {
		ensureTableStateInitialized();
		if (column == null) {
			state.disableGrouping();
		} else {
			state.groupBy(column.getId(), column.getSortOrder());
		}
		state.publishChanges(getFacesContext(), this);
	}

	protected void resetGroupVisibilityState() {
		ensureTableStateInitialized();
		state.resetGroupVisibilityState();
	}

	public void disableGrouping() {
		ensureTableStateInitialized();
		state.disableGrouping();
		state.publishChanges(getFacesContext(), this);
	}

	public boolean groupIsExpanded(int index) {
		ensureTableStateInitialized();
		return state.groupIsExpanded(index);
	}

	public void toggleGroup(int index) {
		ensureTableStateInitialized();
		state.toggleGroup(index);
		state.publishChanges(getFacesContext(), this);
	}

	public Collection<Object> getSortPriority() {
		Collection<Object> priority = super.getSortPriority();
		if (isGroupingOn()) {// grouping is on
			String groupColId = getGroupingColumnId();
			// try to add group column id as first

			//TODO nick - is it ok to change user's priorities collection here?
			if (priority.contains(groupColId)) {
				priority.remove(groupColId);
			}
			if (priority instanceof List) {
				((List<Object>) priority).add(0, groupColId);
			} else {
				priority.add(groupColId);
			}
		}
		return priority;
	}

	public Object saveState(FacesContext context) {
		Object values[] = new Object[2];
		values[0] = super.saveState(context);
		values[1] = state;
		return values;
	}

	public void restoreState(FacesContext context, Object state) {
		Object values[] = (Object[]) state;
		super.restoreState(context, values[0]);
		this.state = (ExtendedDataTableState) values[1];
	}

	public int getVisibleColumnsCount() {
		int count = 0;
		for (Iterator<UIColumn> iter = getChildColumns(); iter.hasNext();) {
			UIColumn column = iter.next();
			if (column.isRendered())
				count++;
		}// for
		return count;
	}// getVisibleColumnnCount

	// @Override
	@SuppressWarnings("unchecked")
	protected ExtendedDataModel createDataModel() {
		List<FilterField> filterFields = new LinkedList<FilterField>();
		Map<String, SortField2> sortFieldsMap = new LinkedHashMap<String, SortField2>();
		List<UIComponent> list = getChildren();
		for (Iterator<UIComponent> iterator = list.iterator(); iterator
				.hasNext();) {
			UIComponent component = iterator.next();
			if (component instanceof org.richfaces.component.UIColumn) {
				org.richfaces.component.UIColumn column = (org.richfaces.component.UIColumn) component;
				FilterField filterField = column.getFilterField();
				if (filterField != null) {
					filterFields.add(filterField);
				}
				SortField2 sortField = column.getSortField();
				if (sortField != null) {
					sortFieldsMap.put(component.getId(), sortField);
				}
			}

		}
		List<SortField2> sortFields = new LinkedList<SortField2>();
		Collection<Object> sortPriority = getSortPriority();
		if (sortPriority != null) {
			for (Object object : sortPriority) {
				if (object instanceof String) {
					String id = (String) object;
					SortField2 sortField = sortFieldsMap.get(id);
					if (sortField != null) {
						sortFields.add(sortField);
						sortFieldsMap.remove(id);
					}
				}
			}
		}
		sortFields.addAll(sortFieldsMap.values());
		setFilterFields(filterFields);
		setSortFields(sortFields);
		ExtendedDataModel dataModel = (ExtendedDataModel) super.getDataModel();
		if (dataModel instanceof ExtendedTableDataModel<?>) {
			ExtendedTableDataModel<?> tableDataModel = (ExtendedTableDataModel<?>) dataModel;
			return new ExtendedDataTableModifiableModel(tableDataModel,
					getVar(), getFilterFields(), getSortFields());
		} else {
			ModifiableModel modifiableModel = new ModifiableModel(dataModel,
					getVar());
			modifiableModel.modify(getFilterFields(), getSortFields());

			return modifiableModel;
		}
	}

	/**
	 * Original version of this method is defined in
	 * {@link org.ajax4jsf.component.UIDataAdaptor} and is called before
	 * RENDER_RESPONSE phase. In that version data model is reseted which causes
	 * need to sort and filter every time component is rendered.
	 */
	// @Override
	protected void resetDataModel() {
		// Do not reset only for ExtendedTableDataModel model
		if (!(getDataModel() instanceof ExtendedTableDataModel<?>)) {
			super.resetDataModel();
		}
	}

	/**
	 * Method resets data model by calling
	 * {@link org.ajax4jsf.component.UIDataAdaptor#resetDataModel()}. This
	 * method is called on sort and filter action.
	 */
	protected void resetModel() {
		super.resetDataModel();
	}

}
