/**
 * 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.component;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.el.MethodExpression;
import javax.faces.component.UIData;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.event.AjaxListener;
import org.ajax4jsf.event.AjaxSource;
import org.ajax4jsf.renderkit.AjaxRendererUtils;


/**
 * UI implementation for SuggestionBox component.
 *
 * @author shura (latest modification by $Author: alexsmirnov $)
 * @version $Revision: 1.5 $ $Date: 2007/03/01 22:37:50 $
 */
public abstract class UISuggestionBox extends UIData
        implements AjaxComponent, AjaxSource, ValueHolder {


    private static final class SubmittedValue implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 2032953038329023808L;
	
	private Object suggestionValue;
	private String[] requestedValues;

	public SubmittedValue() {

	}
	
	public SubmittedValue(Object suggestionValue, String[] requestedValues) {
	    super();
	    this.suggestionValue = suggestionValue;
	    this.requestedValues = requestedValues;
	}

	public Object getSuggestionValue() {
	    return suggestionValue;
	}

	public String[] getRequestedValues() {
	    return requestedValues;
	}
    }

    /**
     * Component type.
     */
    public static final String COMPONENT_TYPE = "org.richfaces.SuggestionBox";

    /**
     * Component family.
     */
    public static final String COMPONENT_FAMILY =
            "org.richfaces.SuggestionBox";

    /**
     * TRUE if submitted.
     */
    private transient boolean submitted = false;

    /**
     * Getter for for attribute.
     *
     * id (or full path of id's) of target components, for which this element
     * must provide support.
     * If a target component inside of the same <code>NamingContainer<code>
     * (UIForm, UIData in base implementstions), can be simple value of the
     * "id" attribute.
     * For other cases must include id's of <code>NamingContainer<code>
     * components, separated by ':'. For search from the root of components,
     * must be started with ':'.
     *
     * @return for value from local variable or value bindings
     */
    public abstract Object getSubmitedValue();

    public abstract void setSubmitedValue(Object v);

    public void setSubmitedValue(Object suggestionValue, String[] requestedValues) {
	setSubmitedValue(new SubmittedValue(suggestionValue, requestedValues));
    }

    public abstract String getFor();

    /**
     * Setter for attribute.
     *
     * @param f identifier
     */
    public abstract void setFor(String f);

    /**
     * Getter for suggestionAction.
     *
     * Method calls an expression to get a collection of suggestion data
     * on request. It must have one parameter with a type of Object with
     * content of input component and must return any type allowed for
     * <h:datatable>;
     *
     * @return suggestionAction value from local variable or value bindings
     */
    public abstract MethodExpression getSuggestionAction();

    /**
     * Setter for suggestionAction.
     *
     * @param action {@link javax.el.MethodExpression}
     */
    public abstract void setSuggestionAction(MethodExpression action);

    /**
     * Getter for converter.
     * Id of Converter to be used or reference to a Converter.
     *
     * @return converter value from local variable or value bindings
     */
    public abstract Converter getConverter();

    /**
     * Setter for converter.
     *
     * @param converter {@link javax.faces.convert.Converter}
     */
    public abstract void setConverter(Converter converter);

    /**
     * Getter for rowClasses.
     *
     * @return rowClasses value from local variable or value bindings
     */
    public abstract String getRowClasses();

    /**
     * Getter for rowClasses.
     *
     * @param rowClasses row classes
     */
    public abstract void setRowClasses(String rowClasses);

    /**
     * Getter for selectValueClass.
     *
     * Name of the CSS class for a hidden suggestion entry
     * element (table cell)
     *
     * @return selectValueClass value from local variable or value bindings
     */
    public abstract String getSelectValueClass();

    /**
     * Setter for selectValueClass.
     *
     * @param value class
     */
    public abstract void setSelectValueClass(String value);

    /**
     * Getter for border.
     *
     * This attributes specifies the width (in pixels only) of the frame around a table
     *
     * @return border value from local variable or value bindings
     */
    public abstract String getBorder();

    /**
     * Setter for border.
     *
     * @param value of border
     */
    public abstract void setBorder(String value);

    /**
     * Getter for cellpadding.
     *
     * This attribute specifies the amount of space between the
     * border of the cell and its contents. If the value of
     * this attribute is a pixel length, all four margins
     * should be this distance from the contents. If the value
     * of the attribute is percentage length, the top and
     * bottom margins should be equally separated from the
     * content based on percentage of the available vertical
     * space, and the left and right margins should be equally
     * separated from the content based on percentage of the
     * available horizontal space
     */
    public abstract String getCellpadding();

    /**
     * Setter for cellpadding.
     *
     * @param value of cellpadding
     */
    public abstract void setCellpadding(String value);

    /**
     * Getter for immediate.
     *
     * @return immediate value from local variable or value bindings
     */
    public abstract boolean isImmediate();

    /**
     * Getter for immediate.
     *
     * @param value of immediate
     */
    public abstract void setImmediate(boolean value);

    /**
     * Getter for selfRendered.
     *
     * If true, forces active Ajax region render response
     * directly from stored components tree, bypasses page
     * processing. Can be used for increase performance. Also,
     * must be set to 'true' inside iteration components, such
     * as dataTable
     *
     * @return selfRendered value from local variable or value bindings
     */

    public abstract boolean isSelfRendered();

    /**
     * Getter for selfRendered.
     *
     * @param value for attribute
     */
    public abstract void setSelfRendered(boolean value);

    /**
     * Getter for entryClass.
     *
     * Name of the CSS class for a suggestion entry element (table row)
     *
     * @return entryClass value from local variable or value bindings
     */
    public abstract String getEntryClass();

    /**
     * Setter for entryClass.
     *
     * @param value of entry class
     */
    public abstract void setEntryClass(String value);

    /**
     * Getter for submitted.
     * 
     * @return TRUE if submited
     */
    public boolean isSubmitted() {
        return submitted;
    }

    /**
     * Setter for submitted.
     *
     * @param s of entry class
     */
    public void setSubmitted(boolean s) {
        this.submitted = s;
    }

    public abstract int getZindex();
    
    public abstract void setZindex(int zindex);
    
    public abstract String getNothingLabel();
    
    public abstract void setNothingLabel(String nothingLabel);

    public abstract Object getFetchValue();

    public abstract void setFetchValue(Object value);
    
    /* (non-Javadoc)
     * @see javax.faces.component.UIData#broadcast(javax.faces.event.FacesEvent)
     */
    
    private int rowNumber = -1;
    
    private Object getModelValuesData() {
	int first = getFirst();
	int rows = getRows();
	int rowIndex = getRowIndex();
	
	setFirst(0);
	setRows(0);
	setRowIndex(-1);

	List<Object> results = new ArrayList<Object>();
	int j = 0;
	boolean stop = false;

	while (!stop) {
	    setRowIndex(j++);
	    if (isRowAvailable()) {
		results.add(getRowData());
	    } else {
		stop = true;
	    }
	}

	setFirst(first);
	setRows(rows);
	setRowIndex(rowIndex);
    
	return results;
    }
    
    private Object getRequestedValuesData() {
	SubmittedValue valueHolder = (SubmittedValue) getSubmitedValue();
	FacesContext context = getFacesContext();
	
	if (valueHolder != null) {
	    String[] requestedValues = valueHolder.getRequestedValues();
	    if (requestedValues != null) {
		MethodExpression suggestingAction = getSuggestionAction();
		if (suggestingAction != null) {
		    int first = getFirst();
		    int rows = getRows();
		    int rowIndex = getRowIndex();
		    Object value = getValue();

		    setFirst(0);
		    setRows(0);

		    Map<String, Object> results = new HashMap<String, Object>();
		    for (int i = 0; i < requestedValues.length; i++) {
			String requestedValue = requestedValues[i];
			if (requestedValue != null) {
			    setValue(suggestingAction.invoke(
				    context.getELContext(), new Object[]{requestedValue}));

			    setRowIndex(-1);

			    int j = 0;
			    boolean stop = false;

			    while (!stop) {
				setRowIndex(j++);
				if (isRowAvailable()) {
				    Object fetchValue = getFetchValue();
				    if (fetchValue != null) {
					if (requestedValue.equalsIgnoreCase(fetchValue.toString())) {
					    results.put(requestedValue, getRowData());
					    stop = true;
					}
				    }
				} else {
				    stop = true;
				}
			    }
			}
		    }

		    setFirst(first);
		    setRows(rows);
		    setRowIndex(rowIndex);
		    setValue(value);

		    return results;
		}
	    }
	}

	return null;
    }

    public int getRowNumber() {
		return rowNumber;
	}

	public void setRowNumber(int rowNumber) {
		this.rowNumber = rowNumber;
	}

	public final void broadcast(final FacesEvent event)
            throws AbortProcessingException {
        super.broadcast(event);
        if (event instanceof AjaxEvent) {
            FacesContext context = getFacesContext();
            setupValue(context);

            AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);

            if (this.isUsingSuggestObjects()) {
                Object modelValues = getModelValuesData();
                Object requestedData = getRequestedValuesData();

                if (null != modelValues || null != requestedData) {
        	    Map<String,Object> map = new HashMap<String, Object>();
        	    map.put("suggestionObjects", modelValues);

        	    if (requestedData != null) {
        		map.put("requestedObjects", requestedData);
        	    }

        	    ajaxContext.setResponseData(map);
        	}
            }
            
            AjaxRendererUtils.addRegionsFromComponent(this, context);
            AjaxRendererUtils.addRegionByName(context, this, this.getId());
//            setSubmitted(true);
            if (isSelfRendered()) {
                ajaxContext.setSelfRender(true);
            }
        } else if (event instanceof SelectSuggestionEvent) {
        	setValue(null);
        }
    }

    public void queueEvent(FacesEvent event) {
    	if (event instanceof SelectSuggestionEvent) {
    		event.setPhaseId(PhaseId.RENDER_RESPONSE);
			super.queueEvent(event);
    	} else if (event.getComponent() != this && rowNumber != -1) {
			int prevIndex = getRowIndex();
			setRowIndex(rowNumber);
			super.queueEvent(event);
			setRowIndex(prevIndex);
		} else {
			super.queueEvent(event);
		}
		
	}

	public void processDecodes(FacesContext context) {
		if (!this.isRendered())
			return;
		Map requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String clientId = getClientId(context);
        String rowValue = (String)requestParameterMap.get(clientId+"_selection");
        if (rowValue != null && rowValue.length()!=0) {
        	setRowNumber(Integer.parseInt(rowValue)+getFirst());
            setupValue(context);
            queueEvent(new SelectSuggestionEvent(this));
        } else {
        	setRowNumber(-1);
        }
		super.processDecodes(context);
	}

	public void setupValue(FacesContext context) {
	    	SubmittedValue valueHolder = (SubmittedValue) getSubmitedValue();
	    	Object submittedValue = valueHolder != null ? valueHolder.getSuggestionValue() : null;
	    	
		MethodExpression suggestingAction = getSuggestionAction();
		if (null != suggestingAction) {
		    setValue(suggestingAction.invoke(
		            context.getELContext(), new Object[]{submittedValue}));
		}
	}

	public void addAjaxListener(final AjaxListener listener) {
        addFacesListener(listener);
    }

    public AjaxListener[] getAjaxListeners() {
        return (AjaxListener[]) getFacesListeners(AjaxListener.class);
    }

    public void removeAjaxListener(final AjaxListener listener) {
        removeFacesListener(listener);
    }
    //To support ValueHolder
    public Object getLocalValue() {
    	// TODO Auto-generated method stub
    	return null;
    }
    
    /**
     * Getter for styleClass.
     *
     * Corresponds to the HTML class attribute
     *
     * @return styleClass value from local variable or value bindings
     */
    public abstract String getStyleClass();

    /**
     * Setter for styleClass.
     *
     * @param value class
     */
    public abstract void setStyleClass(String value);
    
    /**
     * Getter for popupClass.
     *
     * HTML CSS class attribute of element for pop-up suggestion content
     *
     * @return popupClass value from local variable or value bindings
     */
    public abstract String getPopupClass();

    /**
     * Setter for popupClass.
     *
     * @param value class
     */
    public abstract void setPopupClass(String value);
    
    /**
     * Getter for style.
     *
     * CSS style(s) is/are to be applied when this component is rendered
     *
     * @return style value from local variable or value bindings
     */
    public abstract String getStyle();

    /**
     * Setter for style.
     *
     * @param value style
     */
    public abstract void setStyle(String value);
    
    /**
     * Getter for popupStyle.
     *
     * HTML CSS style attribute of element for pop-up suggestion content
     *
     * @return popupStyle value from local variable or value bindings
     */
    public abstract String getPopupStyle();

    /**
     * Setter for popupStyle.
     *
     * @param value style
     */
    public abstract void setPopupStyle(String value);
    
    public abstract boolean isUsingSuggestObjects();
    
    public abstract void setUsingSuggestObjects(boolean usingSuggestObjects); 
}
