/**
 *
 */

package org.richfaces.component;

import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import org.ajax4jsf.Messages;
import org.ajax4jsf.component.AjaxSupport;
import org.ajax4jsf.component.EventValueBinding;
import org.ajax4jsf.component.JavaScriptParameter;
import org.ajax4jsf.javascript.JSFunction;
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 static final Pattern BACKSLASHES = Pattern.compile("\\\\", Pattern.LITERAL);
	private static final String QUOTED_REPLACEMENT = "\\\\";

	/**
	 * @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"));
		//FIXME: Maksim
		//Replacement looks ugly - move that functionality to HtmlUtil
		invocation.addParameter(replaceBackSlashes(targetId));
		invocation.addParameter(getOperation());
		invocation.addParameter(new JSReference("{" + getEncodedParametersMap() + "}"));
		invocation.addParameter(Boolean.valueOf(isDisableDefault()));
		
		return invocation.toScript();


	}

	protected String replaceBackSlashes(String src) {
		return BACKSLASHES.matcher(src).replaceAll(QUOTED_REPLACEMENT);
	}
	
	public String getEncodedParametersMap() {
		StringBuffer result = new StringBuffer();
		
		boolean shouldClose = false;

		String params = this.getParams();
		if (params != null && params.trim().length() != 0) {
			result.append(params);
			shouldClose = true;
		}
		
		for (Iterator it = this.getChildren().iterator(); it.hasNext();) {
			UIComponent child = (UIComponent) it.next();
			if (child instanceof UIParameter) {
				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);

	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 (null != parent && 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();
		String attachTo = getAttachTo();
		
		if (event != null && event.length() != 0) {
			if (attachTo == null || attachTo.length() == 0) {
				parent.setValueBinding(event, new EventValueBinding(this));
			} else {
				ValueBinding vb = parent.getValueBinding(event);
				if (vb instanceof EventValueBinding) {
					//TODO check if that's EventValueBinding for us
					parent.setValueBinding(event, null);
				}
			}
		}
	}
	
	public abstract void setName(String name);
	public abstract String getName();

	public abstract void setAttachTiming( String attachTiming);
	public abstract String getAttachTiming();
}
