/*
 * Copyright 2004-2014 ICEsoft Technologies Canada Corp.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS
 * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package org.icefaces.ace.component.list;

import org.icefaces.ace.json.JSONException;
import org.icefaces.ace.renderkit.CoreRenderer;
import org.icefaces.ace.util.HTML;
import org.icefaces.ace.util.JSONBuilder;
import org.icefaces.render.MandatoryResourceComponent;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.model.SelectItem;
import java.io.IOException;
import java.util.*;

import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import org.icefaces.ace.component.datetimeentry.DateTimeEntry;
import org.icefaces.ace.component.datetimeentry.DateTimeEntryRenderer;
import org.icefaces.ace.component.datetimeentry.DateTimeEntryUtils;
import org.icefaces.util.EnvUtils;
import javax.faces.application.Resource;

@MandatoryResourceComponent(tagName="list", value="org.icefaces.ace.component.list.ACEList")
public class ListRenderer extends CoreRenderer {
    public static final String containerStyleClass = "if-list ui-widget ui-widget-content ui-corner-all";
    public static final String controlsContainerStyleClass = "if-list-ctrls";
    public static final String pointerStyleClass = "if-pntr";
    public static final String bodyStyleClass = "if-list-body";
    public static final String miniClass = "if-mini";
    public static final String itemStyleClass = "if-list-item ui-state-default";
    public static final String selectedItemStyleClass = "ui-state-active";
    public static final String disabledItemStyleClass = "disabled";
    public static final String controlsItemStyleClass = "if-list-ctrl";
    public static final String controlsItemSpacerClass = "if-list-ctrl-spcr";
    public static final String headerStyleClass = "if-list-head ui-widget-header";
    public static final String footerStyleClass = "if-list-foot ui-widget-content";
    public static final String placeholderStyleClass = "if-list-plhld if-list-item ui-state-default";

    @Override
    public void decode(FacesContext context, UIComponent component) {
        ACEList list = (ACEList)component;
        String id = list.getClientId(context);
        Map<String, String> params = context.getExternalContext().getRequestParameterMap();
        String filtering = id + "_filtering";
        String sorting = id + "_sorting";

		if (params.get(filtering) != null) {
			list.savedFilterState = new FilterState(context, list);
			list.applyFilters();
		} else if (params.get(sorting) != null) {
			list.savedSortState = SortState.getSortStateFromRequest(context, list);
			list.applySorting();
		} else {
			String select = id + "_selections";
			String deselect = id + "_deselections";
			String reordering = id + "_reorderings";
			String immigration = id + "_immigration";
			String emigration = id + "_emigration";

			String selectInput = params.get(select);
			String deselectInput = params.get(deselect);
			String reorderingInput = params.get(reordering);
			String immigrationInput = params.get(immigration);
			String emigrationInput = params.get(emigration);

			ListDecoder decoder = new ListDecoder(list);

			try {
				decoder.processSelections(selectInput)
						.processDeselections(deselectInput)
						 // If source, find outgoing objects and pass them
						 // to their destination list
						 .attachEmigrants(context, emigrationInput)
						  .processReorderings(reorderingInput)
						   /// If destination, fetch incoming objects if not
						   /// already passed by decoding source list
						   .fetchImmigrants(context, immigrationInput)
							//// If destination, insert immigrant objects from
							//// records attached to List
							.insertImmigrants()
							 ///// If source, check destination list for attached
							 ///// immigrants (if we did not earlier put them there)
							 ///// and remove them from our attached list
							 .removeEmigrants(context, emigrationInput);
			} catch (JSONException e) {
				throw new FacesException(e);
			}
		}

        decodeBehaviors(context, component);
    }

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        ACEList list = (ACEList) component;
        String clientId = component.getClientId(context);
        String styleClass = list.getStyleClass();
        String style = list.getStyle();
        Boolean mini = list.isCompact();

		// Make sure filters and sorting are applied in the current data model at this stage of the lifecycle
		// when using Mojarra (in some scenarios, filters and sorting aren't being applied at this point).
		if (!EnvUtils.isMyFaces()) {
				if (list.savedFilterState != null) {
					list.savedFilterState.apply(list);
					list.setFilteredData(list.processFilters(context));
				}
		}
		if (list.savedSortState != null) {
			list.setFilteredData(list.processFilters(context));
			list.processSorting();
		}
        list.getDataModel(); // DataModel init determines if some features are available

        styleClass = styleClass == null ? containerStyleClass : styleClass + " " + containerStyleClass;
        if (mini) styleClass += " " + miniClass;

        writer.startElement(HTML.DIV_ELEM, component);
        writer.writeAttribute(HTML.ID_ATTR, clientId, "clientId");
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "styleClass");
        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "style");

        if (list.getFacet("header") != null)
            encodeHeader(context, writer, list);

		if (list.getValueExpression("sortBy") != null) {
			writer.startElement(HTML.DIV_ELEM, null);
			writer.writeAttribute(HTML.CLASS_ATTR, "if-list-sort", null);
			encodeSortControl(writer, context, list);
			writer.endElement(HTML.DIV_ELEM);
		}

		if (list.getValueExpression("filterBy") != null) {
			writer.startElement(HTML.DIV_ELEM, null);
			writer.writeAttribute(HTML.CLASS_ATTR, "if-list-filter", null);
			writer.startElement(HTML.DIV_ELEM, null);
			encodeFilter(context, list);
			writer.endElement(HTML.DIV_ELEM);
			writer.endElement(HTML.DIV_ELEM);
		}

        if (list.isControlsEnabled())
            encodeControls(context, writer, list);
    }

    private void encodeControls(FacesContext context, ResponseWriter writer, ACEList component) throws IOException {
        String styleClass = component.getControlsContainerClass();
        styleClass = styleClass == null ? controlsContainerStyleClass : styleClass + " " + controlsContainerStyleClass;

        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "controlsContainerClass");

        for (String buttonCode : component.getControlsFormat().split(" "))
            encodeControl(context, writer, component, buttonCode);

        writer.endElement(HTML.DIV_ELEM);
    }

    private void encodeControl(FacesContext context, ResponseWriter writer, ACEList component, String buttonCode) throws IOException {
        String itemStyleClass = "if-list-ctrl-" + buttonCode + " " + component.getControlsItemClass();
        String iconStyleClass;
        String property;
        UIComponent facet;

        itemStyleClass = itemStyleClass != null
                ? itemStyleClass + " " + controlsItemStyleClass
                : controlsItemStyleClass;

        if (buttonCode.equals("top")) {
            iconStyleClass = component.getTopButtonClass();
            property = "topButtonClass";
            facet = component.getFacet("topButton");
        } else if (buttonCode.equals("up")) {
            iconStyleClass = component.getUpButtonClass();
            property = "upButtonClass";
            facet = component.getFacet("upButton");
        } else if (buttonCode.equals("dwn")) {
            iconStyleClass = component.getDownButtonClass();
            property = "downButtonClass";
            facet = component.getFacet("downButton");
        } else if (buttonCode.equals("btm")) {
            iconStyleClass = component.getBottomButtonClass();
            property = "bottomButtonClass";
            facet = component.getFacet("bottomButton");
        } else return;

        if (facet != null) {
            facet.encodeAll(context);
        } else {
            writer.startElement(HTML.SPAN_ELEM, null);
            writer.writeAttribute(HTML.CLASS_ATTR, controlsItemSpacerClass, null);

            writer.startElement(HTML.SPAN_ELEM, null);
            writer.writeAttribute(HTML.CLASS_ATTR, itemStyleClass, "controlsItemClass");
            writer.startElement(HTML.SPAN_ELEM, null);
            writer.writeAttribute(HTML.CLASS_ATTR, iconStyleClass, property);
            writer.endElement(HTML.SPAN_ELEM);
            writer.endElement(HTML.SPAN_ELEM);

            writer.endElement(HTML.SPAN_ELEM);
        }
    }

    private void encodeHeader(FacesContext context, ResponseWriter writer, ACEList component) throws IOException {
        String styleClass = component.getHeaderClass();
        styleClass = styleClass == null ? headerStyleClass : styleClass + " " + headerStyleClass;
        String style = component.getHeaderStyle();

        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "headerClass");
        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "headerStyle");

        UIComponent facet = component.getFacet("header");
        if (facet != null)
            facet.encodeAll(context);

        writer.endElement(HTML.DIV_ELEM);
    }

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        ACEList list = (ACEList)component;

        //reset data model
        list.setDataModel(null);
        list.getDataModel();

        String dropGroup = list.getDropGroup();
        String style = list.getBodyStyle();
        String bodyHeight = list.getHeight();
        String styleClass = list.getBodyClass();

        styleClass = styleClass == null
                ? bodyStyleClass
                : styleClass + " " + bodyStyleClass;

        if (dropGroup != null) styleClass += " dg-" + dropGroup;

        writer.startElement(HTML.UL_ELEM, null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "bodyClass");

        if (bodyHeight != null) {
            bodyHeight = " height:"+bodyHeight+";";
            style = style == null ? bodyHeight : style + "; " + bodyHeight;
        }

        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "bodyStyle");

        encodeChildren(context, writer, list);

        writer.endElement(HTML.UL_ELEM);
    }

    private void encodeChildren(FacesContext context, ResponseWriter writer, ACEList list) throws IOException {
        final Collection<Object> selections = list.isSelectItemModel() ? (Collection)list.getValue() : list.getSelections();
        String style = list.getItemStyle();
        String styleClass = list.getItemClass();
        String selectionMode = list.getSelectionMode();
        boolean pointerTable = ("single".equals(selectionMode) || "multiple".equals(selectionMode)) || list.isDragging();

        styleClass = styleClass == null ? itemStyleClass : styleClass + " " + itemStyleClass;

        list.setRowIndex(0);

        boolean selectItems = false;
        if (list.isRowAvailable())
            selectItems = list.getRowData() instanceof SelectItem;

        while (list.isRowAvailable()) {
            String itemStyleClass = new String(styleClass);
            SelectItem item = selectItems ? (SelectItem)list.getRowData() : null;
            Object val = selectItems && list.isSelectItemModel() ? item.getValue() : list.getRowData();

            boolean selected = selections == null ? false : selections.contains(val);
            boolean disabled = selectItems ? item.isDisabled() : false;

            if (selected) itemStyleClass = itemStyleClass + " " + selectedItemStyleClass;
            if (disabled) itemStyleClass = itemStyleClass + " " + disabledItemStyleClass;
            else if (pointerTable) itemStyleClass = itemStyleClass + " " + pointerStyleClass;

            if (selectItems)
                encodeStringChild(context, writer, list, item, itemStyleClass, style);
            else
                encodeCompositeChild(context, writer, list, itemStyleClass, style);

            list.setRowIndex(list.getRowIndex()+1);
        }

        list.setRowIndex(-1);
    }

    private void encodeStringChild(FacesContext context, ResponseWriter writer, ACEList list, SelectItem item, String styleClass, String style) throws IOException {
        writer.startElement(HTML.LI_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, list.getContainerClientId(context), null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "itemClass");
        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "itemStyle");

        writer.write(item.getLabel() == null ? "null" : item.getLabel());

        writer.endElement(HTML.LI_ELEM);
    }

    private void encodeCompositeChild(FacesContext context, ResponseWriter writer, ACEList list, String styleClass, String style) throws IOException {
        writer.startElement(HTML.LI_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, list.getContainerClientId(context), null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "itemClass");
        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "itemStyle");

        // List has implicit UIColumn child to wrap composite children
        for (UIComponent component : list.getChildren()) {
            if (!(component instanceof UISelectItem ||
                    component instanceof UISelectItems))
                component.encodeAll(context);
        }
        writer.endElement(HTML.LI_ELEM);
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        ACEList list = (ACEList) component;

        if (component.getFacet("footer") != null)
            encodeFooter(context, writer, list);

        encodeHiddenFields(context, writer, list);
        encodeScript(context, writer, list);

        writer.endElement(HTML.DIV_ELEM);
    }

    private void encodeHiddenFields(FacesContext context, ResponseWriter writer, ACEList list) throws IOException {
        // Write fields intended for delayed communications of changes to list
        String id = list.getClientId(context);

        writer.startElement(HTML.INPUT_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, id + "_selections", null);
        writer.writeAttribute(HTML.NAME_ATTR, id + "_selections", null);
        writer.writeAttribute(HTML.TYPE_ATTR, "hidden", null);
        writer.writeAttribute(HTML.AUTOCOMPLETE_ATTR, "off", null);
        writer.endElement(HTML.INPUT_ELEM);

        writer.startElement(HTML.INPUT_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, id + "_deselections", null);
        writer.writeAttribute(HTML.NAME_ATTR, id + "_deselections", null);
        writer.writeAttribute(HTML.TYPE_ATTR, "hidden", null);
        writer.writeAttribute(HTML.AUTOCOMPLETE_ATTR, "off", null);
        writer.endElement(HTML.INPUT_ELEM);

        writer.startElement(HTML.INPUT_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, id + "_reorderings", null);
        writer.writeAttribute(HTML.NAME_ATTR, id + "_reorderings", null);
        writer.writeAttribute(HTML.TYPE_ATTR, "hidden", null);
        writer.writeAttribute(HTML.AUTOCOMPLETE_ATTR, "off", null);
        writer.endElement(HTML.INPUT_ELEM);
    }

    private void encodeScript(FacesContext context, ResponseWriter writer, ACEList component) throws IOException {
        String widgetVar = resolveWidgetVar(component);
        String clientId = component.getClientId(context);

        writer.startElement(HTML.SPAN_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, clientId+"_script", null);
        writer.startElement(HTML.SCRIPT_ELEM, null);
        writer.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);

        String styleClass = component.getPlaceholderClass();
        styleClass = styleClass == null ? placeholderStyleClass : styleClass + " " + placeholderStyleClass;
        String selectionMode = component.getSelectionMode();
        String dropGroup = component.getDropGroup();
        boolean selectItemModel = component.isSelectItemModel();

        JSONBuilder cfgBuilder = JSONBuilder.create().initialiseVar(widgetVar)
                .beginFunction("ice.ace.create").item("List").beginArray()
                .item(clientId);

        cfgBuilder.beginMap();
        cfgBuilder.entry("separator", UINamingContainer.getSeparatorChar(context));

        if (component.isPlaceholder())
            cfgBuilder.entry("placeholder", styleClass);

        if ("single".equals(selectionMode) || "multiple".equals(selectionMode))
            cfgBuilder.entry("selection", selectionMode);

        // Select item model doesn't allow reordering or migration
        if (!selectItemModel) {
            if (dropGroup != null)
                cfgBuilder.entry("connectWith", ".dg-"+dropGroup);

            if (component.isDragging())
                cfgBuilder.entry("dragging", true);

            if (component.isControlsEnabled())
                cfgBuilder.entry("controls", true);

            if (component.isDoubleClickMigration())
                cfgBuilder.entry("dblclk_migrate", true);
        }

        if (component.getValueExpression("filterBy") != null) {
			cfgBuilder.entry("filterEvent", component.getFilterEvent());
		}

		if (component.getValueExpression("sortBy") != null) {
			cfgBuilder.entry("sorting", true);
		}

        encodeClientBehaviors(context, component, cfgBuilder);

        cfgBuilder.endMap().endArray().endFunction();

        writer.write(cfgBuilder.toString());
        writer.endElement(HTML.SCRIPT_ELEM);
        writer.endElement(HTML.SPAN_ELEM);
    }

    private void encodeFooter(FacesContext context, ResponseWriter writer, ACEList component) throws IOException {
        String styleClass = component.getFooterClass();
        styleClass = styleClass == null ? footerStyleClass : styleClass + " " + footerStyleClass;
        String style = component.getFooterStyle();

        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, "footerClass");
        if (style != null)
            writer.writeAttribute(HTML.STYLE_ATTR, style, "footerStyle");

        UIComponent facet = component.getFacet("footer");
        if (facet != null)
            facet.encodeAll(context);

        writer.endElement(HTML.DIV_ELEM);
    }

	// ---------------------
	// ----- FILTERING -----
	// ---------------------

    private static void encodeFilter(FacesContext context, ACEList list) throws IOException {
        Map<String,String> params = context.getExternalContext().getRequestParameterMap();
        ResponseWriter writer = context.getResponseWriter();

		if (list.getFilterFacet() != null) {
			encodeFilterFacet(context, list);
			return;
		}

		String clientId = list.getClientId(context);
        String filterId = clientId + "_filter";
        String filterFunction = "ice.ace.instance('"+clientId+"').filter(event)";

        String filterStyleClass = list.getFilterStyleClass();
        String filterEvent = list.getFilterEvent();
        filterStyleClass = filterStyleClass == null ? 
				"if-list-filter"
                : "if-list-filter" + " " + filterStyleClass;


        if (list.getValueExpression("filterOptions") == null) {
			encodeFilterField(context, list, filterId, filterFunction, 
				filterStyleClass, filterEvent, "");
        }
        else {
            writer.startElement("select", null);
            writer.writeAttribute(HTML.ID_ATTR, filterId, null);
            writer.writeAttribute(HTML.NAME_ATTR, filterId, null);
            writer.writeAttribute(HTML.TABINDEX_ATTR, list.getTabIndex(), null);
            //writer.writeAttribute(HTML.CLASS_ATTR, filterStyleClass, null);
            writer.writeAttribute("onchange", filterFunction, null);
			String accesskey = list.getAccesskey();
			if (accesskey != null) {
				writer.writeAttribute("accesskey", accesskey, null);				
			}

            SelectItem[] itemsArray = (SelectItem[]) getFilterOptions(list);
            Object filterVal = list.getFilterValue();

            for (SelectItem item : itemsArray) {
                writer.startElement("option", null);
                writer.writeAttribute("value", item.getValue(), null);

                Object itemVal = item.getValue();

                if ((filterVal == null && itemVal == null)
                        || itemVal.toString().equals(filterVal)) {
                    writer.writeAttribute("selected", "selected", null);
                }

                writer.write(item.getLabel());
                writer.endElement("option");
            }

            writer.endElement("select");
        }
    }

	private static void encodeFilterField(FacesContext context, ACEList list,
			String filterId, String filterFunction, String filterStyleClass, String filterEvent, String suffix) throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		Object filterValue = list.getFilterValue() != null ? list.getFilterValue() : "";

		writer.startElement(HTML.INPUT_ELEM, null);
		writer.writeAttribute(HTML.ID_ATTR, filterId, null);
		writer.writeAttribute(HTML.NAME_ATTR, filterId, null);
		writer.writeAttribute(HTML.TABINDEX_ATTR, list.getTabIndex(), null);
		writer.writeAttribute(HTML.CLASS_ATTR, filterStyleClass, null);
		writer.writeAttribute("size", "1", null); // Webkit requires none zero/null size value to use CSS width correctly.
		writer.writeAttribute("value", filterValue , null);

		if (filterEvent.equals("keyup") || filterEvent.equals("blur"))
			writer.writeAttribute("on"+filterEvent, "ice.setFocus('"+filterId+"');"+filterFunction , null);

		if (list.getFilterStyle() != null)
			writer.writeAttribute(HTML.STYLE_ELEM, list.getFilterStyle(), null);

		String accesskey = list.getAccesskey();
		if (accesskey != null) writer.writeAttribute("accesskey", accesskey, null);

		writer.endElement(HTML.INPUT_ELEM);

		writer.startElement(HTML.SPAN_ELEM, null);
		writer.writeAttribute(HTML.ID_ATTR, filterId + "_script", null);
		writer.startElement(HTML.SCRIPT_ELEM, null);
		writer.writeAttribute("type", "text/javascript", null);
		writer.write("document.getElementById('"+filterId+"').submitOnEnter = 'disabled'; // "+filterValue);

		writer.endElement(HTML.SCRIPT_ELEM);
		writer.endElement(HTML.SPAN_ELEM);
	}

	private static void encodeFilterFacet(FacesContext context, ACEList list) throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		String clientId = list.getClientId(context);

        writer.startElement("span", null);
        writer.writeAttribute("id", clientId + "_filter", null);
        writer.writeAttribute("class", "if-list-filter if-list-filter-button fa fa-chevron-down", null);
		writer.endElement("span");

        writer.startElement("span", null);
        writer.writeAttribute("id", clientId + "_filterFacet", null);
        writer.writeAttribute("class", "ui-widget-content if-list-filter-facet", null);
		writer.writeAttribute("style", "display: none;", null);

		list.getFilterFacet().encodeAll(context);

		writer.endElement("span");
	}

    private static SelectItem[] getFilterOptions(ACEList list) {
        Object options = list.getFilterOptions();
        if (options instanceof SelectItem[]) return (SelectItem[]) options;
        else if (options instanceof Collection<?>) return ((Collection<SelectItem>) list.getFilterOptions()).toArray(new SelectItem[] {});
        else throw new FacesException("Filter options for list " + list.getClientId() + " should be a SelectItem array or collection");
    }

	// -------------------
	// ----- SORTING -----
	// -------------------

    private static void encodeSortControl(ResponseWriter writer, FacesContext context, ACEList list) throws IOException {
        writer.startElement(HTML.SPAN_ELEM, null);
		String clientId = list.getClientId();
        writer.writeAttribute(HTML.ID_ATTR, clientId + "_sortControl", null);
        writer.writeAttribute(HTML.CLASS_ATTR, "ui-sortable-control", null);

        // Write carats
		writer.startElement(HTML.SPAN_ELEM, null);
		writer.writeAttribute(HTML.CLASS_ATTR, "ui-sortable-list-icon", null);

		final String iconUpID = clientId + "_sortControl_up";
		final String iconDownID = clientId + "_sortControl_down";

		writer.startElement(HTML.ANCHOR_ELEM, null);
		writer.writeAttribute(HTML.ID_ATTR, iconUpID, null);
		writer.writeAttribute(HTML.TABINDEX_ATTR, list.getTabIndex(), null);
		writer.writeAttribute(HTML.ONCLICK_ATTR, "ice.setFocus('" + iconUpID + "');", null);
		if (list.isSortAscending())
			writer.writeAttribute(HTML.CLASS_ATTR, "ui-icon ui-icon-triangle-1-n" + " ui-toggled", null);
		else writer.writeAttribute(HTML.CLASS_ATTR, "ui-icon ui-icon-triangle-1-n", null);
		if (list.getValueExpression("filterBy") == null) {
			String accesskey = list.getAccesskey();
			if (accesskey != null) writer.writeAttribute("accesskey", accesskey, null);
		}
		writer.endElement(HTML.ANCHOR_ELEM);

		writer.startElement(HTML.ANCHOR_ELEM, null);
		writer.writeAttribute(HTML.ID_ATTR, iconDownID, null);
		writer.writeAttribute(HTML.TABINDEX_ATTR, list.getTabIndex(), null);
		writer.writeAttribute(HTML.ONCLICK_ATTR, "ice.setFocus('" + iconDownID + "');", null);
		if (!list.isSortAscending())
			writer.writeAttribute(HTML.CLASS_ATTR, "ui-icon ui-icon-triangle-1-s" + " ui-toggled", null);
		else writer.writeAttribute(HTML.CLASS_ATTR, "ui-icon ui-icon-triangle-1-s", null);
		writer.endElement(HTML.ANCHOR_ELEM);

		writer.startElement(HTML.SCRIPT_ELEM, null);
		writer.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
		String widgetVar = resolveWidgetVar(list);
		writer.writeText("(function() {if (window." + widgetVar + ") " + widgetVar + ".setupSortEvents();})();", null);
		writer.endElement(HTML.SCRIPT_ELEM);

        writer.endElement(HTML.SPAN_ELEM);

        writer.endElement(HTML.SPAN_ELEM);
    }
}
