/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptString;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.HeaderResourcesRendererBase;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.util.SelectUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.UIPickList;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.utils.PickListUtils;

public class PickListRenderer extends HeaderResourcesRendererBase {

    private static final String HIDDEN_SUFFIX = "valueKeeper";
    private static  Log logger = LogFactory.getLog(PickListRenderer.class);  
    protected static final OrderingComponentRendererBase.ControlsHelper[] SHUTTLE_HELPERS = PickListControlsHelper.HELPERS;
    protected final static String SHOW_LABELS_ATTRIBUTE_NAME = "showButtonsLabel";
    private static final String MESSAGE_BUNDLE_NAME = PickListRenderer.class.getPackage().getName() + ".pickList";
    private boolean isSelectedList;
    private boolean isAvailableList;

    protected List<SelectItem> selectItemsForSelectedList(FacesContext facesContext, UIComponent uiComponent, List<SelectItem> selectItemList,
	    Converter converter, List<SelectItem> lookupList) {
	List<SelectItem> selectItemForSelectedValues = new ArrayList<SelectItem>();

	for (Iterator<SelectItem> i2 = lookupList.iterator(); i2.hasNext();) {
	    Object value = i2.next();
	    for (Iterator<SelectItem> i = selectItemList.iterator(); i.hasNext();) {
		SelectItem selectItem = i.next();
		String itemStrValue = PickListUtils.getConvertedStringValue(facesContext, uiComponent, converter, selectItem.getValue());
		if (value.equals(itemStrValue)) {
		    selectItemForSelectedValues.add(selectItem);
		}
	    }
	}
	return selectItemForSelectedValues;
    }

    protected List selectItemsForAvailableList(FacesContext facesContext, UIComponent uiComponent, List<SelectItem> selectItemList,
	    List<SelectItem> selectItemsForSelectedList, Converter converter) {
	Collection collection = CollectionUtils.subtract(selectItemList, selectItemsForSelectedList);
	return new ArrayList<SelectItem>(collection);
    }

    protected List getSelectItemsList(FacesContext context, UIComponent component) {
	return SelectUtils.getSelectItems(context, component);
    }

    public PickListRenderer() {
	super();
    }

    public void decode(FacesContext context, UIComponent component) {

	UIPickList picklist = (UIPickList) component;
	if (!(picklist instanceof EditableValueHolder)) {
	    throw new IllegalArgumentException("Component " + picklist.getClientId(context) + " is not an EditableValueHolder");
	}

	String hiddenClientId = picklist.getClientId(context) + HIDDEN_SUFFIX;
	Map paramValuesMap = context.getExternalContext().getRequestParameterValuesMap();

	if (picklist.isDisabled()) {
	    return;
	}

	if (paramValuesMap.containsKey(hiddenClientId)) {
	    String[] valuesInline = (String[]) paramValuesMap.get(hiddenClientId);
	    if (valuesInline[0].trim().equals("")) {
		((EditableValueHolder) picklist).setSubmittedValue(new String[] {});
	    } else {
		String[] reqValues = valuesInline[0].split(",");
		((EditableValueHolder) picklist).setSubmittedValue(reqValues);
	    }
	} else {
	    ((EditableValueHolder) picklist).setSubmittedValue(new String[] {});
	}
    }

    private static boolean isTrue(Object obj) {
	if (!(obj instanceof Boolean)) {
	    return false;
	}
	return ((Boolean) obj).booleanValue();
    }

    @Override
    public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {

	if ((component instanceof UISelectMany) && (submittedValue instanceof String[])) {
	    return SelectUtils.getConvertedUISelectManyValue(context, (UISelectMany) component, (String[]) submittedValue);
	} else if ((component instanceof UISelectOne) && (submittedValue instanceof String)) {
	    return SelectUtils.getConvertedUIInputValue(context, (UISelectOne) component, (String) submittedValue);
	} else {
	    throw new ConverterException("Unsupported component class " + component.getClass().getName());
	}

    }

    private void encodeRows(FacesContext context, UIComponent component, boolean source) throws IOException {
	List selectItemsList = SelectUtils.getSelectItems(context, component);

	Converter converter = PickListUtils.findUISelectManyConverterFailsafe(context, component);
	List lookupList = PickListUtils.getValuesList(component, context, converter);
	List selectItemsForSelectedValues = selectItemsForSelectedList(context, component, selectItemsList, converter, lookupList);
	List selectItemsForAvailableList = selectItemsForAvailableList(context, component, selectItemsList, selectItemsForSelectedValues, converter);
	isSelectedList = !selectItemsForSelectedValues.isEmpty();
	isAvailableList = !selectItemsForAvailableList.isEmpty();

	List selectItemList = null;
	if (source) {
	    selectItemList = selectItemsForAvailableList;
	} else {
	    selectItemList = selectItemsForSelectedValues;
	}

	int i = 0;
	for (Iterator it = selectItemList.iterator(); it.hasNext();) {
	    i++;
	    SelectItem selectItem = (SelectItem) it.next();
	    if (selectItem instanceof SelectItemGroup) {
		SelectItem[] items = ((SelectItemGroup) selectItem).getSelectItems();
		for (int j = 0; j < items.length; j++) {
		    encodeItem(context, component, converter, items[i], source, "group:" + Integer.toString(j));
		}
	    } else {
		encodeItem(context, component, converter, selectItem, source, Integer.toString(i));
	    }
	}
    }

    public void encodeItem(FacesContext context, UIComponent component, Converter converter, SelectItem selectItem, boolean source, String suff)
	    throws IOException {

	ResponseWriter writer = context.getResponseWriter();
	writer.startElement(HTML.TR_ELEMENT, component);
	String clientId = component.getClientId(context);
	if (source) {
	    clientId += ":source:";
	}
	String id = clientId + ":" + suff;
	writer.writeAttribute("id", id, null);

	StringBuffer rowClassName = new StringBuffer();
	StringBuffer cellClassName = new StringBuffer();
	if (source) {
	    rowClassName.append("rich-picklist-source-row");
	    cellClassName.append("rich-picklist-source-cell");
	} else {
	    rowClassName.append("rich-picklist-target-row");
	    cellClassName.append("rich-picklist-target-cell");
	}

	writer.writeAttribute("class", rowClassName.toString(), null);
	writer.startElement(HTML.td_ELEM, component);
	Object width = component.getAttributes().get("width");
	writer.writeAttribute(HTML.class_ATTRIBUTE, cellClassName, null);

	if (width != null) {
	    writer.writeAttribute("style", "width: " + HtmlUtil.qualifySize(width.toString()), null);
	}

	encodeSpacer(context, component, writer);

	boolean escape = isTrue(component.getAttributes().get("escape"));
	if (escape) {
	    writer.writeText(selectItem.getLabel(), null);
	} else {
	    writer.write(selectItem.getLabel());
	}

	String itemValue = PickListUtils.getConvertedStringValue(context, component, converter, selectItem.getValue());
	encodeItemValue(context, component, writer, id, itemValue);
	writer.endElement(HTML.td_ELEM);
	writer.endElement(HTML.TR_ELEMENT);

    }

    public void encodeTargetRows(FacesContext context, UIComponent component) throws IOException {
	encodeRows(context, component, false);
    }

    public void encodeSourceRows(FacesContext context, UIComponent component) throws IOException {
	encodeRows(context, component, true);
    }

    private void encodeItemValue(FacesContext context, UIComponent component, ResponseWriter writer, String id, String itemValue) throws IOException { //rowKey = i

	writer.startElement(HTML.INPUT_ELEM, component);
	writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
	writer.writeAttribute(HTML.NAME_ATTRIBUTE, id, null);

	StringBuffer value = new StringBuffer();

	value.append(itemValue);
	writer.writeAttribute(HTML.value_ATTRIBUTE, value.toString(), null);
	writer.writeAttribute(HTML.id_ATTRIBUTE, id + "StateInput", null);
	writer.endElement(HTML.INPUT_ELEM);
    }

    protected void encodeSpacer(FacesContext context, UIComponent component, ResponseWriter writer) throws IOException {
	writer.startElement(HTML.IMG_ELEMENT, component);
	writer.writeAttribute(HTML.src_ATTRIBUTE, getResource("/org/richfaces/renderkit/html/images/spacer.gif").getUri(context, null), null);
	writer.writeAttribute(HTML.alt_ATTRIBUTE, " ", null);
	writer.writeAttribute(HTML.style_ATTRIBUTE, "width:1px;height:1px;", null);
	writer.endElement(HTML.IMG_ELEMENT);
    }

    public void encodeHiddenField(FacesContext context, UIComponent component) throws IOException {
	ResponseWriter writer = context.getResponseWriter();
	Converter converter = PickListUtils.findUISelectManyConverterFailsafe(context, component);
	List lookupList = PickListUtils.getValuesList(component, context, converter);
	encodeHiddenField(context, component, lookupList, writer);
    }

    private void encodeHiddenField(FacesContext context, UIComponent component, List lookupList, ResponseWriter writer) throws IOException {
	String hiddenFieldCliendId = component.getClientId(context) + HIDDEN_SUFFIX;
	writer.startElement(HTML.INPUT_ELEM, component);
	writer.writeAttribute(HTML.TYPE_ATTR, "hidden", "type");
	writer.writeAttribute(HTML.id_ATTRIBUTE, hiddenFieldCliendId, "id");
	writer.writeAttribute("name", hiddenFieldCliendId, null);
	StringBuffer sb = new StringBuffer();
	int n = 0;

	for (Iterator i = lookupList.iterator(); i.hasNext();) {
	    if (n > 0) {
		sb.append(",");
	    }
	    String value = (String) i.next();
	    sb.append(value);
	    n++;
	}

	writer.writeAttribute(HTML.value_ATTRIBUTE, sb.toString(), null);
	writer.endElement(HTML.INPUT_ELEM);
    }

    protected Class<? extends UIComponent> getComponentClass() {
	return UIPickList.class;
    }

    public String getAsEventHandler(FacesContext context, UIComponent component, String attributeName) {
	String event = (String) component.getAttributes().get(attributeName);
	ScriptString result = JSReference.NULL;

	if (event != null) {
	    event = event.trim();
	    if (event.length() != 0) {
		JSFunctionDefinition function = new JSFunctionDefinition();
		function.addParameter("event");
		function.addToBody(event);
		result = function;
	    }
	}

	return ScriptUtils.toScript(result);
    }

    public String getColumnClassesAsJSArray(FacesContext context, UIComponent component) {
	return ScriptUtils.toScript(getClassesAsList(context, component, "columnClasses"));
    }

    public String getRowClassesAsJSArray(FacesContext context, UIComponent component) {
	return ScriptUtils.toScript(getClassesAsList(context, component, "rowClasses"));
    }

    protected List getClassesAsList(FacesContext context, UIComponent component, String attr) {
	String value = (String) ((UIComponent) component).getAttributes().get(attr);
	if (value != null && (value.length() != 0)) {
	    return Arrays.asList(value.split(","));
	}
	return null;
    }

    public void encodePickListControlsFacets(FacesContext context, UIComponent component) throws IOException {
	String clientId = component.getClientId(context);
	ResponseWriter writer = context.getResponseWriter();
	boolean enable = false;
	
	for (int i = 0; i < SHUTTLE_HELPERS.length; i++) {
	    OrderingComponentRendererBase.ControlsHelper helper = SHUTTLE_HELPERS[i];
	    boolean isDisabled = helper.getButtonStyleClass().equals(PickListControlsHelper.DISABLED_STYLE_PREF);
	    if (helper.getBundlePropertyName().equals(PickListControlsHelper.BUNDLE_REMOVE_ALL_LABEL)) {
		enable = isSelectedList;
		if(enable != isDisabled) {
		    enable = true;
		} else {
		    enable = false;
		}
	    } else if (helper.getBundlePropertyName().equals(PickListControlsHelper.BUNDLE_COPY_ALL_LABEL)) {
		enable = isAvailableList;
		if(enable != isDisabled) {
		    enable = true;
		} else {
		    enable = false;
		}
	    } else {
		if (helper.getButtonStyleClass().equals(PickListControlsHelper.DISABLED_STYLE_PREF)) {
		    enable = true;
		} else {
		    enable = false;
		}
	    }
	    encodeControlFacet(context, component, SHUTTLE_HELPERS[i], clientId, writer, enable, "rich-list-picklist-button", " rich-picklist-control");
	}
    }

    protected void encodeControlFacet(FacesContext context, UIComponent component, OrderingComponentRendererBase.ControlsHelper helper, String clientId,
	ResponseWriter writer, boolean enabled, String baseStyle, String baseControlStyle) throws IOException {
	renderDefaultControl(context, component, writer, helper, clientId, enabled, baseStyle, baseControlStyle);
    }

    protected ClassLoader getCurrentLoader(Object fallbackClass) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = fallbackClass.getClass().getClassLoader();
        }
        return loader;
    }
    
    protected String findLocalisedLabel(FacesContext context, String propertyId, String bundleName) {
	String label = null;
	Locale locale = null;
	String userBundleName = null;
	ResourceBundle bundle = null;

	UIViewRoot viewRoot = context.getViewRoot();
	if ( viewRoot != null) {
	    locale = viewRoot.getLocale(); 
	} else {
	    locale = Locale.getDefault();
	}
	
	if(locale != null) {
		try {
		    if( null != (userBundleName = context.getApplication().getMessageBundle())) {
			bundle = ResourceBundle.getBundle(userBundleName,locale, getCurrentLoader(userBundleName));
			if (bundle != null) {
			    label = bundle.getString(propertyId);
			}    
		    } 
		} catch (MissingResourceException e) {
			if (logger.isDebugEnabled()) {
				logger.debug("Can't find bundle properties file " + userBundleName + " " + locale.getLanguage() + " " + locale.getCountry()) ;
			}
		} 
		if(label == null && bundleName != null) {
		try {
			bundle = ResourceBundle.getBundle(bundleName ,locale, getCurrentLoader(bundleName));
			if (bundle != null) { 
			    label = bundle.getString(propertyId);
			}    
		    } catch (MissingResourceException e) {
				if (logger.isDebugEnabled()) {
					logger.debug("Can't find bundle properties file " + bundleName + " " + locale.getLanguage() + " " + locale.getCountry()) ;
				}

		    }
		}
	}	
	    	
	return label; 
    }
    
    protected void renderDefaultControl(FacesContext context, UIComponent component, ResponseWriter writer, 
	OrderingComponentRendererBase.ControlsHelper helper, String clientId, boolean enabled, String baseStyle,
	String baseControlStyle) throws IOException {
	UIComponent facet = component.getFacet(getAltAttribbute(helper));
	boolean useFacet = (facet != null && facet.isRendered());

	String customEvent = null;
	Map attributes = component.getAttributes();

	if (helper.customEvent != null) {
	    customEvent = (String) attributes.get(helper.customEvent);
	}

	String styleFromAttribute = (String) attributes.get(helper.styleFromAttribute);
	String baseStyleLight = baseStyle.concat("-light");
	String baseStylePress = baseStyle.concat("-press");
	String currentStyle = baseControlStyle + helper.getStyleClassName();

	if (styleFromAttribute != null) {
	    currentStyle = styleFromAttribute.concat(currentStyle);
	}

	writer.startElement(HTML.DIV_ELEM, component);
	String controlId = clientId + helper.getIdSuffix();
	writer.writeAttribute(HTML.id_ATTRIBUTE, controlId, null); // FIXME:
	writer.writeAttribute(HTML.class_ATTRIBUTE, currentStyle, null);
	String style = null;

	if (enabled) {
	    style = "display:block;";
	} else {
	    style = "display:none;";
	}

	writer.writeAttribute(HTML.style_ATTRIBUTE, style, null);

	if (!useFacet) {
	    writer.startElement(HTML.DIV_ELEM, component);
	    writer.writeAttribute(HTML.class_ATTRIBUTE, baseStyle + helper.getButtonStyleClass(), null);

	    if (helper.enable) {
		writer.writeAttribute(HTML.onmouseover_ATTRIBUTE, "this.className='" + baseStyleLight + "'", null);
		writer.writeAttribute(HTML.onmousedown_ATTRIBUTE, "this.className='" + baseStylePress + "'", null);
		writer.writeAttribute(HTML.onmouseup_ATTRIBUTE, "this.className='" + baseStyle + "'", null);
		writer.writeAttribute(HTML.onmouseout_ATTRIBUTE, "this.className='" + baseStyle + "'", null);
	    }

	    if (!helper.enable) {
		//writer.writeAttribute(HTML.DISABLED_ATTR, "disabled", null);
		//writer.writeAttribute(HTML.class_ATTRIBUTE, baseStyle + "-a-disabled", null);
		//writer.startElement(HTML.a_ELEMENT, component);
	    } else {
		writer.startElement(HTML.a_ELEMENT, component);
		writer.writeAttribute(HTML.id_ATTRIBUTE, controlId + "link", null); // FIXME:
		writer.writeAttribute(HTML.onclick_ATTRIBUTE, "return false;", null);
		    
		writer.writeAttribute(HTML.class_ATTRIBUTE, baseStyle + "-selection", null);
		writer.writeAttribute(HTML.onblur_ATTRIBUTE, "Richfaces.Control.onblur(this);", null);
		writer.writeAttribute(HTML.onfocus_ATTRIBUTE, "Richfaces.Control.onfocus(this);", null);
	    }

	    writer.startElement(HTML.DIV_ELEM, component);
	    writer.writeAttribute(HTML.class_ATTRIBUTE, baseStyle + "-content", null);
	}

	if (customEvent != null) {
	    writer.writeAttribute(HTML.onclick_ATTRIBUTE, customEvent, null);
	}

	if (useFacet) {
	    renderChild(context, facet);
	} else {
	    writer.startElement(HTML.IMG_ELEMENT, component);
	    writer.writeAttribute(HTML.class_ATTRIBUTE, "rich-picklist-control-img", null);
	    writer.writeAttribute(HTML.alt_ATTRIBUTE, getAltAttribbute(helper), null);
	    writer.writeAttribute(HTML.src_ATTRIBUTE, getResource(helper.getImageURI()).getUri(context, null), null);
	    writer.endElement(HTML.IMG_ELEMENT);

	    if (getUtils().isBooleanAttribute(component, SHOW_LABELS_ATTRIBUTE_NAME)) {
		String label = (String) attributes.get(helper.getLabelAttributeName());
		if (label == null || label.equals("")) {
		    label = findLocalisedLabel(context, helper.getBundlePropertyName(), MESSAGE_BUNDLE_NAME);
		}

		if (label == null || label.equals("")) {
		    label = helper.getDefaultText();
		}
		writer.write(label);
	    }
	}

	if (!useFacet) {
	    writer.endElement(HTML.DIV_ELEM);
	    if (helper.enable) {
		writer.endElement(HTML.a_ELEMENT);
	    }
	    writer.endElement(HTML.DIV_ELEM);
	}
	writer.endElement(HTML.DIV_ELEM);
    }

	private String getAltAttribbute(
			OrderingComponentRendererBase.ControlsHelper helper) {
		return helper.getFacetName()!=null?helper.getFacetName():" ";
	}
    
    public void reRenderScript(FacesContext context, UIComponent component) throws IOException {
	AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
	Set <String> areas = ajaxContext.getAjaxRenderedAreas();
	String clientId = component.getClientId(context);
	if (ajaxContext.isAjaxRequest() && areas.contains(clientId)){
	    areas.add(clientId + "script");
    	}
    }
}
