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

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.ajax4jsf.component.JavaScriptParameter;
import org.ajax4jsf.javascript.JSBind;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.renderkit.RendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.resource.InternetResource;
import org.richfaces.component.UIContextMenu;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.renderkit.TemplateEncoderRendererBase;
import org.xml.sax.ContentHandler;

/**
 * @author Maksim Kaszynski
 *
 */
public class ContextMenuRendererBase extends TemplateEncoderRendererBase {

    /**
     * Constant for "immediate" attach timing option
     */
    private static final String IMMEDIATE = "immediate";

    /**
     * Constant for "onAvailable" attach timing option
     */
    private static final String ON_AVAILABLE = "onavailable";

    /**
     * Constant for "onload" attach timing option
     */
    private static final String ON_LOAD = "onload";
    
    private final ContextMenuRendererDelegate delegate = 
		new ContextMenuRendererDelegate();

	private final InternetResource[] ownScripts  = {
		getResource("/org/richfaces/renderkit/html/scripts/json/json-dom.js"),
		getResource("/org/richfaces/renderkit/html/scripts/utils.js"),
		getResource("/org/richfaces/renderkit/html/scripts/context-menu.js"),
		new org.ajax4jsf.javascript.PrototypeScript(),
		new org.ajax4jsf.javascript.AjaxScript(),
		getResource("/org/richfaces/renderkit/html/scripts/available.js"),
		getResource("/org/richfaces/renderkit/html/scripts/jquery/jquery.js"),
		getResource("/org/richfaces/renderkit/html/scripts/jquery.utils.js")

	};
	
	private final InternetResource[] scripts;
	
	public ContextMenuRendererBase() {
		InternetResource[] delegateScripts = delegate.getScripts();
		scripts = new InternetResource[delegateScripts.length + ownScripts.length];
		
		System.arraycopy(delegateScripts, 0, scripts, 0, delegateScripts.length);
		System.arraycopy(ownScripts, 0, scripts, delegateScripts.length, ownScripts.length);
	}
	
	protected InternetResource[] getScripts() {
		return scripts;
	}
	
	protected InternetResource[] getStyles() {
		return delegate.getStyles();
	}
	
	protected Class getComponentClass() {
		return delegate.getComponentClass();
	}


	protected void doEncodeBegin(ResponseWriter writer, FacesContext context,
			UIComponent component) throws IOException {
		ensureParentPresent(component);
		checkAttachTimingValidity(context, component);
		writer.startElement(HTML.DIV_ELEM, component);
		writer.writeAttribute(HTML.id_ATTRIBUTE, component.getClientId(context), "id");
	}

	private boolean isEmpty(String s) {
		return s == null || s.trim().length() == 0;
	}
	
	private void ensureParentPresent(UIComponent component) {
		UIComponent parent = component.getParent();
		UIContextMenu menu = (UIContextMenu) component;
		
		if (parent != null) {
			if (!HtmlUtil.shouldWriteId(parent) && menu.isAttached() && isEmpty(menu.getAttachTo())) {
				throw new FacesException(
						"Context menu cannot be attached to the component with id = "
						+ parent.getId()
						+ ", because a client identifier of the component won't be rendered onto the page. Please, set the identifier.");
			}
		} else {
			throw new FacesException(
					"Parent component is null for ContextMenu "
							+ component.getId());
		}
	}
	
	/**
	 * Perform validation of the contextMenu configuration.
	 * 
	 * @param component - menu component
	 */
	protected void checkAttachTimingValidity(FacesContext context, UIComponent component) {
		UIContextMenu menu = (UIContextMenu) component;
		String attachTiming = menu.getAttachTiming();
	    if (!ON_LOAD.equals(attachTiming) && !IMMEDIATE.equals(attachTiming) && !ON_AVAILABLE.equals(attachTiming)) {
	    	context.getExternalContext().log(attachTiming + " value of attachTiming attribute is not a legal one for component: "
							+ MessageUtil.getLabel(context, component)
							+ ". Default value was applied.");
	    	menu.setAttachTiming(ON_AVAILABLE);
	    }
	}
	
	public void renderChildren(FacesContext context, UIComponent component)
			throws IOException {
		delegate.encodeChildren(context, component);
	}
	

	public void encodeChildren(FacesContext context, UIComponent component)
	throws IOException {
		UIContextMenu menu = (UIContextMenu) component;
		ResponseWriter writer = context.getResponseWriter();
		
		String event = menu.getEvent();
		
		if (event == null) {
			throw new FacesException("Attribute 'event' is not set for component " + component.getClientId(context));
		}
		
		writer.startElement("script", component);
		getUtils().writeAttribute(writer, "type", "text/javascript" );
		writer.writeText("var contextMenu = new Richfaces.ContextMenu('", null);
		writer.writeText(component.getClientId(context), null);
		writer.writeText("', ", null);
		writer.writeText(menu.getShowDelay() + ", ", null);
		writeScriptBody(context, component, true);
		writer.writeText(")", null);
		writer.writeText(";", null);
		writer.writeText(getClientAttachmentOptions(context, menu), null);
		
		if (menu.isDisableDefaultMenu()) {
			writer.writeText("Richfaces.disableDefaultHandler('", null);
			writer.writeText(event, null);
			writer.writeText("');", null);
		}
		
		writer.endElement("script");
	}

	private String getClientAttachmentOptions(FacesContext context, UIContextMenu contextMenu) {
	    // if contextMenu is not attached (it can be called by JS API)
	    if(!contextMenu.isAttached()) {
		return "";
	    }

	    String attachTo = contextMenu.getAttachTo();
	    String attachTiming = contextMenu.getAttachTiming();

	    boolean isImmediate = attachTiming.equals(IMMEDIATE);
	    boolean isOnLoad = attachTiming.equals(ON_LOAD);
	    boolean isOnAvailable = attachTiming.equals(ON_AVAILABLE);

	    if (!(isImmediate || isOnLoad || isOnAvailable)) {
		// unknown value of property "attachTiming"
		return "";
	    }

	    String pattern = "\\s*,\\s*";
	    // "attachTo" attribute may contain several ids split by ","
	    List<String> attachToIds = new ArrayList<String>();
	    String[] splitAttachTo = attachTo.split(pattern);
	    for (String tempId : splitAttachTo) {
		if(tempId.length() > 0) {
		    attachToIds.add(tempId);
		}
	    }

	    String baseJSFucntionName = "contextMenu.attachToElementById";
	    
	    // if attribute "attachTo" is not defined,
	    // attach contextMenu to the parent component
	    if(attachToIds.size() == 0) {
		UIComponent parentComponent = contextMenu.getParent();
		String clientId = parentComponent.getClientId(context);
		if (clientId != null) {
		    attachToIds.add(clientId);
		    baseJSFucntionName = "contextMenu.attachToParent";
		}
	    }

	    StringBuilder attachContextMenuBuffer = new StringBuilder();

	    // collect parameters
	    Map<String, Object> params = new LinkedHashMap<String, Object>();
	    for (UIComponent kid : contextMenu.getChildren()) {
		if (kid instanceof UIParameter) {
		    UIParameter parameter = (UIParameter) kid;
		    String name = parameter.getName();
		    Object value = parameter.getValue();

		    if ((parameter instanceof JavaScriptParameter) && ((JavaScriptParameter) parameter).isNoEscape()) {
			value = new JSReference(String.valueOf(value));
		    }

		    params.put(name, value);
		}
	    }

	    for (String attachToId : attachToIds) {
		JSFunction attachContextMenuFunction = new JSFunction(baseJSFucntionName);
		UIComponent target = RendererUtils.getInstance().findComponentFor(context, contextMenu, attachToId);
		String clientId = (target != null) ? target.getClientId(context) : attachToId;			

		attachContextMenuFunction.addParameter(clientId);
		attachContextMenuFunction.addParameter(contextMenu.getEvent());
		attachContextMenuFunction.addParameter(params);

		if (isImmediate) {
		    attachContextMenuBuffer.append(attachContextMenuFunction.toScript());
		} else {
			if (isOnAvailable) {
				JSFunction availableFunction = new JSFunction("Richfaces.onAvailable");
			    availableFunction.addParameter(clientId);
				availableFunction.addParameter(new JSBind(attachContextMenuFunction, "contextMenu"));
			    attachContextMenuBuffer.append(availableFunction.toScript());
			    
			} else if (isOnLoad) {
			    JSFunction onloadFunction = new JSFunction("jQuery(document).ready");
			    onloadFunction.addParameter(new JSBind(attachContextMenuFunction, "contextMenu"));

			    attachContextMenuBuffer.append(onloadFunction.toScript());
			}
		}

		attachContextMenuBuffer.append(";");
	    }

	    return attachContextMenuBuffer.toString();
	}
	
	protected void doEncodeEnd(ResponseWriter writer, FacesContext context,
			UIComponent component) throws IOException {
		writer.endElement(HTML.DIV_ELEM);
	}
	
	protected ContentHandler createContentHandler(Writer writer) {
		return new ContextMenuContentHandler(writer, "Richfaces.evalMacro(\"", "\", context)");
	}
}
