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

import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;

import org.ajax4jsf.Messages;
import org.ajax4jsf.component.AjaxSupport;
import org.ajax4jsf.component.EventValueExpression;
import org.ajax4jsf.component.JavaScriptParameter;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSLiteral;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.util.HtmlUtil;

/**
 * JSF component class
 * 
 */
public abstract class UIComponentControl extends UIComponentBase 
            implements AjaxSupport {
    
    public static final String COMPONENT_TYPE = "org.richfaces.ComponentControl";
    
    public static final String COMPONENT_FAMILY = "org.richfaces.ComponentControl";
    
    private static final Log log = LogFactory.getLog(UIComponentControl.class);
    
    private boolean disableDefault = false;
    private boolean disableDefaultSet = false;
    
    /**
     * @return JavaScript eventString. Rebuild on every call, since can be in
     *         loop ( as in dataTable ) with different parameters.
     */
    public String getEventString() {
        String targetId = HtmlUtil.idsToIdSelector(getFor());
        
        targetId = HtmlUtil.expandIdSelector(targetId, this, FacesContext
                .getCurrentInstance());
        
        JSFunction invocation = new JSFunction("Richfaces.componentControl.performOperation");
        invocation.addParameter(new JSReference("event"));
        addOptions(invocation, getEvent(), targetId, getOperation());
        
        return invocation.toScript();
    }
    
    private static boolean isContextMenuEvent(String event) {
    	return ("contextmenu".equalsIgnoreCase(event) || "oncontextmenu".equalsIgnoreCase(event));
    }
    
	public void addOptions(JSFunction function, String event, String targetId, String operation) {
        function.addParameter(event);
        function.addParameter(targetId);
        function.addParameter(operation);

		boolean parametersEncoded = false;

		String params = getEncodedParametersMap();
		if (params.length() != 0) {
			parametersEncoded = true;
			//parameters should be evaluated in time of componentControl execution, not at page rendering
			//e.g. event.clientX, that's why they're rendered in function
			JSFunctionDefinition parametersFunction = new JSFunctionDefinition().
				addToBody("return{").addToBody(params).addToBody("}");
			
			function.addParameter(parametersFunction);
		}

		boolean disableDefault = isDisableDefault();
		boolean isOnContextMenu = isContextMenuEvent(event);
		if (isOnContextMenu ^ disableDefault) {
			if (!parametersEncoded) {
				function.addParameter(JSLiteral.EMPTY_HASH);
			}
			function.addParameter(disableDefault);
		}
	}
    
    public String getEncodedParametersMap() {
        StringBuilder result = new StringBuilder();
        
        boolean shouldClose = false;
        
        String params = this.getParams();
        if (params != null && params.trim().length() != 0) {
            result.append(params);
            shouldClose = true;
        }
        
        for (UIComponent child : this.getChildren()) {
            if (!(child instanceof UIParameter)) {
                continue;
            }
            
            String name = ((UIParameter) child).getName();
            Object value = ((UIParameter) child).getValue();
            if (null == name) {
                FacesContext context = FacesContext.getCurrentInstance();
                throw new IllegalArgumentException(Messages.getMessage(
                        Messages.UNNAMED_PARAMETER_ERROR, this
                                .getClientId(context)));
            }
            
            boolean escape = true;
            if (child instanceof JavaScriptParameter) {
                JavaScriptParameter actionParam = (JavaScriptParameter) child;
                escape = !actionParam.isNoEscape();
            }
            
            if (shouldClose) {
                result.append(", ");
            }
            
            ScriptUtils.addEncodedString(result, name);
            result.append(": ");
            result.append(ScriptUtils.toScript(escape ? value
                    : new JSReference(value.toString())));
            
            shouldClose = true;
        }
        
        return result.toString();
    }
    
    public abstract String getEvent();
    
    public abstract void setEvent(String event);
    
    public abstract String getFor();
    
    public abstract void setFor(String value);
    
    public abstract String getParams();
    
    public abstract void setParams(String value);
    
    public abstract String getOperation();
    
    public abstract void setOperation(String value);
    
    public abstract String getAttachTo();
    
    public abstract void setAttachTo(String value);
    
    public boolean isDisableDefault() {
    	if (this.disableDefaultSet) {
    	    return (this.disableDefault);
    	}
    	
    	ValueExpression ve = getValueExpression("disableDefault");
    	if (ve != null) {
    	    Boolean value = null;
    	    
    	    try {
    			value = (Boolean) ve.getValue(getFacesContext().getELContext());
    	    } catch (ELException e) {
    			throw new FacesException(e);
    	    }
    	    
    	    if (null != value) {
        	    return value.booleanValue();
    	    }
    	}

	    return isContextMenuEvent(getEvent());
    }

    public void setDisableDefault(boolean disableDefault) {
    	this.disableDefaultSet = true;
    	this.disableDefault = disableDefault;
    }
    
    protected String replaceClientIds(FacesContext context,
            UIComponent component, String selector) {
        return HtmlUtil.expandIdSelector(selector, component, context);
    }
    
    /**
     * After nornal setting <code>parent</code> property in case of created
     * component set Ajax properties for parent.
     * 
     * @see javax.faces.component.UIComponentBase#setParent(javax.faces.component.UIComponent)
     */
    public void setParent(UIComponent parent) {
        super.setParent(parent);
        if (parent != null && parent.getFamily() != null) {
            if (log.isDebugEnabled()) {
                log.debug(Messages.getMessage(Messages.CALLED_SET_PARENT,
                        parent.getClass().getName()));
            }
            
            // TODO If this comopnent configured, set properties for parent
            // component.
            // NEW created component have parent, restored view - null in My
            // faces.
            // and SUN RI not call at restore saved view.
            // In other case - set in restoreState method.
            // if (parent.getParent() != null)
            if (log.isDebugEnabled()) {
                log.debug(Messages
                        .getMessage(Messages.DETECT_NEW_COMPONENT));
            }
            setParentProperties(parent);
        }
    }
    
    public void setParentProperties(UIComponent parent) {
        String event = getEvent();
        if (event == null || event.length() == 0) {
            return;
        }
        
        String attachTo = getAttachTo();
        if (attachTo == null || attachTo.length() == 0) {
            parent.setValueExpression(event, new EventValueExpression(this));
        } else {
            ValueExpression vb = parent.getValueExpression(event);
            if (vb instanceof EventValueExpression) {

                // TODO check if that's EventValueBinding for us
                parent.setValueExpression(event, null);
            }
        }
    }
    
    public abstract void setName(String name);
    
    public abstract String getName();
    
    public abstract void setAttachTiming(String attachTiming);
    
    public abstract String getAttachTiming();
    
    @Override
    public Object saveState(FacesContext context) {
    	Object[] state = new Object[3];
    	state[0] = super.saveState(context);
    	state[1] = this.disableDefault ? Boolean.TRUE : Boolean.FALSE;
    	state[2] = this.disableDefaultSet ? Boolean.TRUE : Boolean.FALSE;

    	return state;
    }
    
    @Override
    public void restoreState(FacesContext context, Object stateObject) {
    	Object[] state = (Object[]) stateObject;
    	super.restoreState(context, state[0]);
    	this.disableDefault = ((Boolean) state[1]).booleanValue();
    	this.disableDefaultSet = ((Boolean) state[2]).booleanValue();
    }
}
