/**
 * 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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
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.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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.UIPickList;
import org.richfaces.component.util.HtmlUtil;

public class PickListRenderer extends HeaderResourcesRendererBase {

	private static  Log logger = LogFactory.getLog(PickListRenderer.class);
	
	private static final String HIDDEN_SUFFIX = "valueKeeper";
   
    private static final String MESSAGE_BUNDLE_NAME = PickListRenderer.class.getPackage().getName() + ".pickList";
    
    private static final String SHOW_LABELS_ATTRIBUTE_NAME = "showButtonsLabel";
    
    private static final OrderingComponentRendererBase.ControlsHelper[] SHUTTLE_HELPERS = PickListControlsHelper.HELPERS;
    
    private boolean isSelectedList;
    
    private boolean isAvailableList;
     
    
    protected List<SelectItem> selectItemsForSelectedList(FacesContext facesContext, UIComponent uiComponent, List<SelectItem> selectItemList, List<Object> lookupList) {
		
    	List<SelectItem> selectItemForSelectedValues = new ArrayList<SelectItem>();
	
		for (Object lookupItem: lookupList) {
		    
			for (SelectItem selectItem: selectItemList) {
		    	
				if(selectItem instanceof SelectItemGroup) {

					SelectItem[] items = ((SelectItemGroup) selectItem).getSelectItems();
					for (int j = 0; j < items.length; j++) {
						if(lookupItem.equals(items[j].getValue())) {
							selectItemForSelectedValues.add(items[j]);
						}
					}
					
				} else {
					
					if (lookupItem.equals(selectItem.getValue())) {
						selectItemForSelectedValues.add(selectItem);
					}
					
				}
				
		    }
		    
		}
		
		return selectItemForSelectedValues;
    }
    
    protected List<Object> getValuesList(UIPickList pickList) {
    	
    	List <Object> valuesList = new ArrayList<Object>();
    	
    	Object value = pickList.getValue();

    	if(null == value || "".equals(value)) {
    		return valuesList;
    	}
    	
    	if(value.getClass().isArray()) {
    		int len = Array.getLength(value);
 		   
    		for (int i = 0; i < len; i++) {
    			
    			Object localValue = Array.get(value, i);
    			if(localValue != null) {
    				valuesList.add(localValue);
    			}
    			
 		    }
    		
 		    return valuesList;
    	} else if(value instanceof List) {
    		List <?> list = (List<?>) value;
   		
    		for (Object item: list) {
    			valuesList.add(item);
		    }
		    	
		   	return valuesList;
		
    	} else {
			throw new IllegalArgumentException("Error: value of UIPickList component is not of type Array or List");
		}
    	
    }

    protected List <SelectItem> selectItemsForAvailableList(FacesContext facesContext, UIComponent uiComponent, List<SelectItem> selectItemList,
	    List<SelectItem> selectItemsForSelectedList) {
    	    	
    	  List <SelectItem> processItems = new ArrayList<SelectItem>();	
    	  
    	  for(SelectItem item: selectItemList) {
    		  
    		  if(item instanceof SelectItemGroup) {
    			
    			SelectItem items[] = ((SelectItemGroup)item).getSelectItems();  
    			for (int i = 0; i < items.length; i++) {
					processItems.add(items[i]);
				}
    			
    		  } else {
    			  processItems.add(item);
    		  }
    		  
    	  }
    	
    	  for (SelectItem selectItem: selectItemsForSelectedList) {
              processItems.remove(selectItem);
          }
          
    	  return processItems;
      	
    }

    protected List <SelectItem> 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 <String, String[]> 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 {
    	
    	Object convertedValue = null;
    	
    	if(component instanceof UIPickList) {
    		UIPickList pickList = (UIPickList)component;
    		convertedValue = SelectUtils.getConvertedUISelectManyValue(context, pickList, (String[]) submittedValue); 
    	}
    	
      	return convertedValue;
    }
    
    private void encodeRows(FacesContext context, UIPickList pickList, boolean source) throws IOException {
    	
    	List <SelectItem> selectItemsList = SelectUtils.getSelectItems(context, pickList);

    	Converter converter = pickList.getConverter();
    	
		List <Object> values = getValuesList(pickList); // PickListUtils.getValuesList(context, pickList, converter);
		
		List <SelectItem> selectItemsForSelectedValues = selectItemsForSelectedList(context, pickList, selectItemsList, values);
		List <SelectItem> selectItemsForAvailableList = selectItemsForAvailableList(context, pickList, selectItemsList, selectItemsForSelectedValues);
		
		isSelectedList = !selectItemsForSelectedValues.isEmpty();
		isAvailableList = !selectItemsForAvailableList.isEmpty();
	
		List <SelectItem> selectItemList = null;
		
		if (source) {
		    selectItemList = selectItemsForAvailableList;
		} else {
		    selectItemList = selectItemsForSelectedValues;
		}
	
		if(selectItemList != null) {
			
			SelectItem [] itemsList =  selectItemList.toArray(new SelectItem[selectItemList.size()]);
			
			for (int i = 0; i < itemsList.length; i++) {
			    
				SelectItem selectItem = itemsList[i];
			   
				if (selectItem instanceof SelectItemGroup) {
					
			    	SelectItem[] items = ((SelectItemGroup) selectItem).getSelectItems();
					for (int j = 0; j < items.length; j++) {
					    encodeItem(context, pickList, converter, items[j], source, "group:" + j);
					}
					
			    } else {
			    	
			    	encodeItem(context, pickList, 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);
		writer.writeAttribute(HTML.class_ATTRIBUTE, cellClassName, null);
		
		Object width = component.getAttributes().get("width");
		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 = converter != null ? converter.getAsString(context, component, selectItem.getValue()) : "";
				
		encodeItemValue(context, component, writer, id, itemValue);
		
		writer.endElement(HTML.td_ELEM);
		writer.endElement(HTML.TR_ELEMENT);

    }

    public void encodeTargetRows(FacesContext context, UIPickList pickList) throws IOException {
    	encodeRows(context, pickList, false);
    }

    public void encodeSourceRows(FacesContext context, UIPickList pickList) throws IOException {
    	encodeRows(context, pickList, 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, UIPickList pickList) throws IOException {
    
    	Converter converter = pickList.getConverter();
		List <String> lookupList = new ArrayList<String>();

		if(converter != null) {
    		List <Object> values = getValuesList(pickList);
	    	for(Object value: values) {
	    		lookupList.add(converter.getAsString(context, pickList, value));
	    	}
    	}

		encodeHiddenField(context, pickList, lookupList);
    	
    }

    private void encodeHiddenField(FacesContext context, UIComponent component, List <String> lookupList) throws IOException {

    	String hiddenFieldCliendId = component.getClientId(context) + HIDDEN_SUFFIX;
    	
    	ResponseWriter writer = context.getResponseWriter();

    	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 <String> i = lookupList.iterator(); i.hasNext();) {
    		
    		if (n > 0) {
    			sb.append(",");
    		}
    	
    		String value = 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 <String> 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);
    	boolean enable = false;
    	
    	ResponseWriter writer = context.getResponseWriter();
    	
	
    	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;
    			enable = enable != isDisabled ? true : false;
    			
    		} else if (helper.getBundlePropertyName().equals(PickListControlsHelper.BUNDLE_COPY_ALL_LABEL)) {
    			
    			enable = isAvailableList;
    			enable = enable != isDisabled ? true : 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<String,Object> 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);
		}
		
		String controlId = clientId + helper.getIdSuffix();
		writer.startElement(HTML.DIV_ELEM, component);
		writer.writeAttribute(HTML.id_ATTRIBUTE, controlId, null); // FIXME:
		writer.writeAttribute(HTML.class_ATTRIBUTE, currentStyle, null);
		
		String style = enabled ? "display:block;" : "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, component.getAttributes().get(helper.getTitle()), 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");
    	}
    	
    }
    
}
