/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.renderkit;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.richfaces.component.Column;
import org.richfaces.component.Row;
import org.richfaces.component.UIDataTable;
import org.richfaces.model.Ordering;

/**
 * @author shura
 * 
 */
public abstract class AbstractTableRenderer extends AbstractRowsRenderer {

	private static final String SORT_FILTER_PARAMETER = "fsp";
	
	private static final String FILTER_INPUT_FACET_NAME = "filterValueInput";
	
	private static final String SPACER_PATH = "/org/richfaces/renderkit/html/images/spacer.gif";
	
	/**
	 * Encode all table structure - colgroups definitions, caption, header,
	 * footer
	 * 
	 * @param context
	 * @param table
	 * @throws IOException
	 */
	
	public void encodeTableStructure(FacesContext context, UIDataTable table)
			throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		int columns = getColumnsCount(table);
		// Encode colgroup definition.
		writer.startElement("colgroup", table);
		writer.writeAttribute("span", String.valueOf(columns), null);
		String columnsWidth = (String) table.getAttributes().get("columnsWidth");
		
		
		if (null != columnsWidth) {
			
			String[] widths = columnsWidth.split(",");
			for (int i = 0; i < widths.length; i++) {
				writer.startElement("col", table);
				writer.writeAttribute("width", widths[i], null);
				writer.endElement("col");
			}
		}
		writer.endElement("colgroup");
		encodeCaption(context, table);
		encodeHeader(context, table, columns);
		encodeFooter(context, table, columns);
	}

	public void encodeHeader(FacesContext context, UIDataTable table,
			int columns) throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		UIComponent header = table.getHeader();
		Iterator<UIComponent> headers = columnFacets(table,"header");
		Iterator<UIComponent> colums = table.columns();
		int colCount = calculateRowColumns(table.columns());
		
		
		if (header != null ||headers.hasNext()) {
			writer.startElement("thead", table);
			writer.writeAttribute(HTML.class_ATTRIBUTE, "dr-table-thead", null);
			String headerClass = (String) table.getAttributes().get(
					"headerClass");
			if (header != null) {
				encodeTableHeaderFacet(context, columns, writer, header,
						"dr-table-header rich-table-header",
						"dr-table-header-continue rich-table-header-continue",
						"dr-table-headercell rich-table-headercell",
						headerClass, "th");
			}

			if (headers.hasNext()) {
				writer.startElement("tr", table);
				encodeStyleClass(writer, null,
						"dr-table-subheader rich-table-subheader", null,
						headerClass);
					encodeHeaderFacets(context, writer, colums,
						"dr-table-subheadercell rich-table-subheadercell",
						headerClass, "header", "th", colCount);
				
				writer.endElement("tr");
			}
			writer.endElement("thead");
		}
	}
	
	public boolean findFacet(UIDataTable table, String facetName) {
		return columnFacets(table,facetName).hasNext();
	}
	
	protected void encodeHeaderFacets(FacesContext context,
			ResponseWriter writer, Iterator<UIComponent> headers, String skinCellClass,
			String headerClass, String facetName, String element, int colCount)
			throws IOException {
		int t_colCount = 0;
		String spacerUrl = getResource(SPACER_PATH).getUri(context, null);
		
		HeaderEncodeStrategy richEncodeStrategy = new RichHeaderEncodeStrategy();
		HeaderEncodeStrategy simpleEncodeStrategy = new SimpleHeaderEncodeStrategy();
		
		while (headers.hasNext()) {
			UIComponent column = (UIComponent) headers.next();
			if((Integer)column.getAttributes().get("colspan")!=null){
				t_colCount = t_colCount + ((Integer)column.getAttributes().get("colspan")).intValue();
			}else{
				t_colCount++;
			}			
			if(t_colCount>colCount){
				break;
			}
			
			String classAttribute = facetName + "Class";
			String columnHeaderClass = (String) column.getAttributes().get(
					classAttribute);
			writer.startElement(element, column);
			encodeStyleClass(writer, null, skinCellClass, headerClass,
					columnHeaderClass);
			writer.writeAttribute("scope", "col", null);			
			getUtils().encodeAttribute(context, column, "colspan");
			
			boolean sortableColumn = column.getValueExpression("comparator") != null 
				|| column.getValueExpression("sortBy") != null;
			
			HeaderEncodeStrategy strategy = (column instanceof org.richfaces.component.UIColumn
					&& "header".equals(facetName)) ? richEncodeStrategy : simpleEncodeStrategy;
			
			strategy.encodeBegin(context, writer, column, facetName, sortableColumn);
			
			UIComponent facet = column.getFacet(facetName);
			if (facet != null) {
				renderChild(context, facet);
			}
			
			strategy.encodeEnd(context, writer, column, facetName, sortableColumn, spacerUrl);
			
			writer.endElement(element);
			

		}
	}

	public void encodeFooter(FacesContext context, UIDataTable table,
			int columns) throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		UIComponent footer = table.getFooter();
		Iterator<UIComponent> footers = columnFacets(table,"footer");
		Iterator<UIComponent> tableColumns = table.columns();//columnFacets(table,"footer");
		int colCount = calculateRowColumns(table.columns());
		if (footer != null || footers.hasNext()) {
			writer.startElement("tfoot", table);
			String footerClass = (String) table.getAttributes().get(
					"footerClass");

			if (footers.hasNext()&&findFacet(table,"footer")) {
				writer.startElement("tr", table);
				encodeStyleClass(writer, null,
						"dr-table-subfooter rich-table-subfooter", null,
						footerClass);
		
				encodeHeaderFacets(context, writer, tableColumns,
						"dr-table-subfootercell rich-table-subfootercell",
						footerClass, "footer", "td",colCount);
				
				writer.endElement("tr");
			}
			if (footer != null) {
				encodeTableHeaderFacet(context, columns, writer, footer,
						"dr-table-footer rich-table-footer",
						"dr-table-footer-continue rich-table-footer-continue",
						"dr-table-footercell rich-table-footercell",
						footerClass, "td");
			}
			writer.endElement("tfoot");
		}

	}

	public void encodeOneRow(FacesContext context, TableHolder holder)
			throws IOException {
		UIDataTable table = (UIDataTable) holder.getTable();
		ResponseWriter writer = context.getResponseWriter();
		Iterator<UIComponent> iter = table.columns();
		boolean first = true;
		int currentColumn = 0;
		UIComponent column = null;
		while (iter.hasNext()) {
			column = (UIComponent) iter.next();
			// Start new row for first column - expect a case of the detail
			// table, wich will be insert own row.
			if (first && !(column instanceof Row)) {
				encodeRowStart(context, getFirstRowSkinClass(), holder
						.getRowClass(), table, writer);
			}
			if (column instanceof Column) {
				boolean breakBefore = ((Column) column).isBreakBefore()
						|| column instanceof Row;
				if (breakBefore && !first) {
					// close current row
					writer.endElement(HTML.TR_ELEMENT);
					// reset columns counter.
					currentColumn = 0;
					// Start new row, expect a case of the detail table, wich
					// will be insert own row.
					if (!(column instanceof Row)) {
						holder.nextRow();
						encodeRowStart(context, holder.getRowClass(), table,
								writer);
					}
				}
				encodeCellChildren(context, column,
						first ? getFirstRowSkinClass() : null,
						getRowSkinClass(), holder.getRowClass(),
						getCellSkinClass(), holder
								.getColumnClass(currentColumn));
				// renderChild(context, column);
				if ((column instanceof Row) && iter.hasNext()) {
					// Start new row for remained columns.
					holder.nextRow();
					encodeRowStart(context, holder.getRowClass(), table, writer);
					// reset columns counter.
					currentColumn = -1;
				}
			} else if (column.isRendered()) {
				// UIColumn don't have own renderer
				writer.startElement(HTML.td_ELEM, table);
				getUtils().encodeId(context, column);
				String columnClass = holder.getColumnClass(currentColumn);
				encodeStyleClass(writer, null, getCellSkinClass(), null,
						columnClass);
				// TODO - encode column attributes.
				renderChildren(context, column);
				writer.endElement(HTML.td_ELEM);
			}
			currentColumn++;
			first = false;
		}
		// Close row if then is open.
		if (!first && !(column instanceof Row)) {
			writer.endElement(HTML.TR_ELEMENT);
		}
	}

	protected void encodeRowStart(FacesContext context, String rowClass,
			UIDataTable table, ResponseWriter writer) throws IOException {
		encodeRowStart(context, getRowSkinClass(), rowClass, table, writer);
	}

	/**
	 * @return
	 */
	protected String getRowSkinClass() {
		return "dr-table-row rich-table-row";
	}

	/**
	 * @return
	 */
	protected String getFirstRowSkinClass() {
		return "dr-table-firstrow rich-table-firstrow";
	}

	/**
	 * @return
	 */
	protected String getCellSkinClass() {
		return "dr-table-cell rich-table-cell";
	}

	protected void encodeRowStart(FacesContext context, String skinClass,
			String rowClass, UIDataTable table, ResponseWriter writer)
			throws IOException {
		writer.startElement(HTML.TR_ELEMENT, table);
		encodeStyleClass(writer, null, skinClass, null, rowClass);
		encodeRowEvents(context, table);
	}

	/*
	 * protected Iterator columnFacets(UIDataTable table) { return
	 * table.columns();
	 * 
	 * Changed by Alexej Kushunin
	 */
	@SuppressWarnings("unchecked")
	protected Iterator<UIComponent> columnFacets(UIDataTable table,final String name){	
			return new FilterIterator(table.columns(), new Predicate() {
	
			public boolean evaluate(Object input) {
				UIComponent component = (UIComponent) input;
				// accept only columns with corresponding facets.
				 if (component instanceof Column || component instanceof UIColumn) {
					return component.isRendered()&&(component.getFacet(name) != null);			
				}
				 else{
					 return false;		
				 }
			}});
	}

	/**
	 * Calculate total number of columns in table.
	 * 
	 * @param context
	 * @param table
	 * @return
	 */
	protected int getColumnsCount(UIDataTable table) {
		int count = 0;
		// check for exact value in component
		Integer span = (Integer) table.getAttributes().get("columns");
		if (null != span && span.intValue() != Integer.MIN_VALUE) {
			count = span.intValue();
		} else {
			// calculate max html columns count for all columns/rows children.
			Iterator<UIComponent> col = table.columns();
			count = calculateRowColumns(col);
		}
		return count;
	}

	/**
	 * Calculate max number of columns per row. For rows, recursive calculate
	 * max length.
	 * 
	 * @param col -
	 *            Iterator other all columns in table.
	 * @return
	 */
	protected int calculateRowColumns(Iterator<UIComponent> col) {
		int count = 0;
		int currentLength = 0;
		while (col.hasNext()) {
			UIComponent column = (UIComponent) col.next();
			if (column.isRendered()) {
				if (column instanceof Row) {
					// Store max calculated value of previsous rows.
					if (currentLength > count) {
						count = currentLength;
					}
					// Calculate number of columns in row.
					currentLength = calculateRowColumns(((Row) column)
							.columns());
					// Store max calculated value
					if (currentLength > count) {
						count = currentLength;
					}
					currentLength = 0;
				} else if (column instanceof Column) {
					Column tableColumn = (Column) column;
					// For new row, save length of previsous.
					if (tableColumn.isBreakBefore()) {
						if (currentLength > count) {
							count = currentLength;
						}
						currentLength = 0;
					}
					Integer colspan = (Integer) column.getAttributes().get(
							"colspan");
					// Append colspan of this column
					if (null != colspan
							&& colspan.intValue() != Integer.MIN_VALUE) {
						currentLength += colspan.intValue();
					} else {
						currentLength++;
					}
				} else if (column instanceof UIColumn) {
					// UIColumn always have colspan == 1.
					currentLength++;
				}

			}
		}
		if (currentLength > count) {
			count = currentLength;
		}
		return count;
	}
	
	public void encodeScriptIfNecessary(FacesContext context, UIDataTable component) throws IOException {
		boolean shouldRender = false;
		Iterator<UIComponent> columns = component.columns();
		while(columns.hasNext() && !shouldRender) {
			UIComponent next = columns.next();
			shouldRender = (next instanceof Column) && ((Column)next).isSortable();
		}
		if (shouldRender) {
			JSFunction function = new JSFunction("new RichFaces.DataTable");
			function.addParameter(component.getBaseClientId(context));
			ScriptOptions scriptOptions = new ScriptOptions(component);
			scriptOptions.addOption("sortMode", new JSFunction("new RichFaces.SortMode.Single"));
			function.addParameter(scriptOptions);
			getUtils().writeScript(context, component, function.toScript());
		}
	}
	
	@Override
	protected void doDecode(FacesContext context, UIComponent component) {
		Map<String, String> map = context.getExternalContext().getRequestParameterMap();
		String clientId = component.getClientId(context);
		if (SORT_FILTER_PARAMETER.equals(map.get(clientId))) {
			String sortColumnId = map.get(SORT_FILTER_PARAMETER);
			List<UIComponent> list = component.getChildren();
			UIDataTable table = (UIDataTable) component;
			boolean isSingleSortMode = !"multi".equals(table.getSortMode());
			for (Iterator<UIComponent> iterator = list.iterator(); iterator
					.hasNext();) {
				UIComponent child = iterator.next();
				if (child instanceof org.richfaces.component.UIColumn) {
					org.richfaces.component.UIColumn column = (org.richfaces.component.UIColumn) child;
					child.setId(child.getId());
					if (sortColumnId != null) {
						if (sortColumnId.equals(child.getClientId(context))) {
							String id = child.getId();
							Collection<Object> sortPriority = table.getSortPriority();
							if (isSingleSortMode) {
								sortPriority.clear();
							}
							if(!sortPriority.contains(id)) {
								sortPriority.add(id);
							}
							column.toggleSortOrder();
						} else if(isSingleSortMode){
							column.setSortOrder(Ordering.UNSORTED);
						}
					}
					UIInput filterValueInput = (UIInput)child.getFacet(FILTER_INPUT_FACET_NAME);
					if (null != filterValueInput) {
						filterValueInput.decode(context);
						Object submittedValue = filterValueInput.getSubmittedValue();
						if (null != submittedValue) {
							column.setFilterValue(filterValueInput.getSubmittedValue().toString());
						}
					}
				}

			}
			AjaxContext.getCurrentInstance()
					.addComponentToAjaxRender(component);
			AjaxContext.getCurrentInstance().addRenderedArea(clientId+ ":tb");
		}
	}
	
	@Override
	public void encodeEnd(FacesContext context, UIComponent component)
			throws IOException {
		super.encodeEnd(context, component);
		String clientId = component.getClientId(context);
		Set ajaxRenderedAreas = AjaxContext.getCurrentInstance().getAjaxRenderedAreas();
		if(ajaxRenderedAreas.contains(clientId+ ":tb")) {
			ajaxRenderedAreas.remove(clientId);
		}
	}
	protected void addInplaceInput(FacesContext context, UIComponent column,
			String buffer) throws IOException {
		UIInput filterValueInput = (UIInput) column
				.getFacet(FILTER_INPUT_FACET_NAME);
		boolean inplaceInput = true;
		if (null == filterValueInput) {
			try {
				filterValueInput = (UIInput) context.getApplication().createComponent("org.richfaces.InplaceInput");
			} catch (FacesException e) {
				filterValueInput = (UIInput) context.getApplication().createComponent(UIInput.COMPONENT_TYPE);
				inplaceInput = false;
			}
			filterValueInput.setId(column.getId() + SORT_FILTER_PARAMETER);
			filterValueInput.setImmediate(true);
			column.getFacets().put(FILTER_INPUT_FACET_NAME, filterValueInput);
			filterValueInput.getAttributes().put(HTML.onclick_ATTRIBUTE, "Event.stop(event);");
		}
		String onkeydownEvent = HTML.onkeydown_ATTRIBUTE;

		String filterEvent = (String) column.getAttributes().get("filterEvent");
		if (inplaceInput) {
			onkeydownEvent = "oninputkeydown";
			filterValueInput.getAttributes().put("defaultLabel", column.getAttributes().get("filterDefaultLabel"));
		}
		if (null == filterEvent || "".equals(filterEvent)) {
			filterEvent = "onchange";
		}
		
		filterValueInput.getAttributes().put(filterEvent, buffer);
		if (!onkeydownEvent.equals(filterEvent)) {
			filterValueInput.getAttributes().put(onkeydownEvent, "RichFaces.blurFilterInput(event);");
		}
		
		filterValueInput.setValue(column.getAttributes().get("filterValue"));

		getUtils().encodeBeginFormIfNessesary(context, column);
		renderChild(context, filterValueInput);
		getUtils().encodeEndFormIfNessesary(context, column);
	}
	
	protected String buildAjaxFunction(FacesContext context, UIComponent column, boolean sortable) {
		UIComponent table = column.getParent();
		String id = table.getClientId(context);
		JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(table, context);
		Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(context, table);
		
		
		@SuppressWarnings("unchecked")
		Map<String, Object> parameters = 
			(Map<String, Object>) eventOptions.get("parameters");
		
		
		parameters.put(id, SORT_FILTER_PARAMETER);
		if (sortable) {
			parameters.put(SORT_FILTER_PARAMETER, column.getClientId(context));
		}
		ajaxFunction.addParameter(eventOptions);
		StringBuffer buffer = new StringBuffer();
		ajaxFunction.appendScript(buffer);
		
		return buffer.toString();
	}
	
	protected class SimpleHeaderEncodeStrategy implements HeaderEncodeStrategy {

		public void encodeBegin(FacesContext context, ResponseWriter writer,
				UIComponent column, String facetName, boolean sortableColumn)
				throws IOException {
			
		}

		public void encodeEnd(FacesContext context, ResponseWriter writer,
				UIComponent column, String facetName, boolean sortableColumn, String spacelUrl)
				throws IOException {
			
		}


	}
	
	protected class RichHeaderEncodeStrategy implements HeaderEncodeStrategy {

		public void encodeBegin(FacesContext context, ResponseWriter writer,
				UIComponent column, String facetName, boolean sortableColumn)
					throws IOException {
				org.richfaces.component.UIColumn col = 
					(org.richfaces.component.UIColumn) column;
				String clientId = col.getClientId(context) + facetName;
				writer.writeAttribute("id", clientId, null);
				
				if (sortableColumn && col.isSelfSorted()) {
					writer.writeAttribute(HTML.onclick_ATTRIBUTE, buildAjaxFunction(context, column, true)
							.toString(), null);
					writer.writeAttribute(HTML.style_ATTRIBUTE, "cursor: pointer;", null);
				}
				
				writer.startElement(HTML.DIV_ELEM, column);
				writer.writeAttribute(HTML.id_ATTRIBUTE, clientId + ":sortDiv", null);
				AjaxContext.getCurrentInstance().addRenderedArea(clientId + ":sortDiv");
				
				if (sortableColumn) {
					writer.startElement(HTML.SPAN_ELEM, column);
					
					String spanClass = "";
					if (Ordering.ASCENDING.equals(col.getSortOrder())) {
						spanClass = "dr-table-header-sort-up dr-table-sortable-header";
					} else if (Ordering.DESCENDING.equals(col.getSortOrder())) {
						spanClass = "dr-table-header-sort-down dr-table-sortable-header";
					}
					writer.writeAttribute(HTML.class_ATTRIBUTE, spanClass, null);
				}
		}

		public void encodeEnd(FacesContext context, ResponseWriter writer,
				UIComponent column, String facetName, boolean sortableColumn, String spacerUrl) throws IOException {
				org.richfaces.component.UIColumn col = 
					(org.richfaces.component.UIColumn) column;
				if (sortableColumn) {
					writer.startElement(HTML.IMG_ELEMENT, column);
					writer.writeAttribute(HTML.src_ATTRIBUTE, spacerUrl, null);
					writer.writeAttribute(HTML.width_ATTRIBUTE, "16", null);
					writer.endElement(HTML.IMG_ELEMENT);
					
					writer.endElement(HTML.SPAN_ELEM);
				}
				
				writer.endElement(HTML.DIV_ELEM);
				
				if (col.getFilterMethod() == null
						&& col.getValueExpression("filterExpression") == null
						&& col.getValueExpression("filterBy") != null) {
					
					writer.startElement(HTML.DIV_ELEM, column);
					addInplaceInput(context, column, buildAjaxFunction(context, column, false));
					writer.endElement(HTML.DIV_ELEM);
				}
		}
	}
}