/**
 * 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.util.HashMap;
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 javax.faces.event.ActionEvent;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.AjaxComponentRendererBase;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.resource.InternetResource;
import org.richfaces.component.UIToolTip;
import org.richfaces.skin.Skin;

//import com.gargoylesoftware.htmlunit.protocol.javascript.JavaScriptURLConnection;
//import com.sun.el.lang.FunctionMapperImpl.Function;

public class ToolTipRenderer extends AjaxComponentRendererBase{

	// 
	// Declarations
	//
	private final InternetResource[] styles = {
						getResource("/org/richfaces/renderkit/html/css/tooltip.xcss")
	};

private InternetResource[] stylesAll = null;

protected InternetResource[] getStyles() {
	synchronized (this) {
		if (stylesAll == null) {
			InternetResource[] rsrcs = super.getStyles();
			boolean ignoreSuper = rsrcs == null || rsrcs.length == 0;
			boolean ignoreThis = styles == null || styles.length == 0;
			
			if (ignoreSuper) {
				if (ignoreThis) {
					stylesAll = new InternetResource[0];	
				} else {
					stylesAll = styles;
				}
			} else {
				if (ignoreThis) {
					stylesAll = rsrcs;
				} else {
					java.util.Set rsrcsSet = new java.util.LinkedHashSet();

					for (int i = 0; i < rsrcs.length; i++ ) {
						rsrcsSet.add(rsrcs[i]);
					}

					for (int i = 0; i < styles.length; i++ ) {
						rsrcsSet.add(styles[i]);
					}

					stylesAll = (InternetResource[]) rsrcsSet.toArray(new InternetResource[rsrcsSet.size()]);
				}
			}
		}
	}
	
	return stylesAll;
}
	private final InternetResource[] scripts = {
			new org.ajax4jsf.javascript.PrototypeScript(),
			new org.ajax4jsf.javascript.AjaxScript(),
			getResource("/org/richfaces/renderkit/html/scripts/jquery/jquery.js"),
			getResource("/org/richfaces/renderkit/html/scripts/utils.js"),
			getResource("/org/richfaces/renderkit/html/scripts/tooltip.js")
	};

private InternetResource[] scriptsAll = null;

protected InternetResource[] getScripts() {
	synchronized (this) {
		if (scriptsAll == null) {
			InternetResource[] rsrcs = super.getScripts();
			boolean ignoreSuper = rsrcs == null || rsrcs.length == 0;
			boolean ignoreThis = scripts == null || scripts.length == 0;
			
			if (ignoreSuper) {
				if (ignoreThis) {
					scriptsAll = new InternetResource[0];	
				} else {
					scriptsAll = scripts;
				}
			} else {
				if (ignoreThis) {
					scriptsAll = rsrcs;
				} else {
					java.util.Set rsrcsSet = new java.util.LinkedHashSet();

					for (int i = 0; i < rsrcs.length; i++ ) {
						rsrcsSet.add(rsrcs[i]);
					}

					for (int i = 0; i < scripts.length; i++ ) {
						rsrcsSet.add(scripts[i]);
					}

					scriptsAll = (InternetResource[]) rsrcsSet.toArray(new InternetResource[rsrcsSet.size()]);
				}
			}
		}
	}
	
	return scriptsAll;
}
	
	protected Class getComponentClass() {
		return org.richfaces.component.UIToolTip.class;
	}

	public String getBgColor(FacesContext context, UIComponent component){
		Skin skin = getSkin(context);
		String color = (String) skin.getParameter(context,"headerBackgroundColor");
		return color;
	}

	public String getColor(FacesContext context, UIComponent component){
		Skin skin = getSkin(context);
		String color = (String) skin.getParameter(context,"headerTextColor");
		return color;
	}
	
	
	public Map<String, Object> buildEventOptions(FacesContext context, UIComponent component, String targetId){
        Map<String, Object> eventOptions = AjaxRendererUtils.buildEventOptions(context,	component);
        String jsVarName = "_toolTip";
        
        //after element is subsituted in DOM tree, we have to re-create
        //it's JS-reference, cause old one is already invalid
		
        String clientId = component.getClientId(context);
		String oncompleteTooltip = ";" + "{" + 
			"var " + jsVarName + " = $('" + clientId + "').component;" +
        	jsVarName + ".toolTipContent = $('" + clientId + "content');" +
        	jsVarName + ".displayDiv();" + 
        	"}";
        
        // before element will be substituted in DOM tree, we need to hide toolTipe to avoid blinking
        String fireBeforeUpdateDOM = ";" + "{ var " + jsVarName + " = $('" + clientId + "').component;" + jsVarName + ".toolTip.style.display = 'none'; }";
        
       
        //enable ajaxSingle mode, i.e. we do not need to submit all form controls to get tooltip content
        eventOptions.put("control", JSReference.THIS);
              
        if(eventOptions.containsKey("oncomplete")){
        	JSFunctionDefinition onComplete = (JSFunctionDefinition)eventOptions.get("oncomplete");
        	onComplete.addToBody(oncompleteTooltip);
        	eventOptions.put("oncomplete", onComplete);
        } else {
			JSFunctionDefinition onComplete = new JSFunctionDefinition();
			onComplete.addParameter("request");
			onComplete.addParameter("showEvent");
			onComplete.addParameter("data");
			onComplete.addToBody(oncompleteTooltip);
			eventOptions.put("oncomplete", onComplete);
        }

        if(eventOptions.containsKey(AjaxRendererUtils.ONBEFOREDOMUPDATE_ATTR_NAME)){
        	JSFunctionDefinition beforeUpdate = (JSFunctionDefinition)eventOptions.get(AjaxRendererUtils.ONBEFOREDOMUPDATE_ATTR_NAME);
        	beforeUpdate.addToBody(fireBeforeUpdateDOM);
        	eventOptions.put(AjaxRendererUtils.ONBEFOREDOMUPDATE_ATTR_NAME, beforeUpdate);
        } else {
			JSFunctionDefinition beforeUpdate = new JSFunctionDefinition();
			beforeUpdate.addParameter("request");
			beforeUpdate.addParameter("showEvent");
			beforeUpdate.addParameter("data");
			beforeUpdate.addToBody(fireBeforeUpdateDOM);
			eventOptions.put(AjaxRendererUtils.ONBEFOREDOMUPDATE_ATTR_NAME, beforeUpdate);
        }
        
        
        
        return eventOptions;
	}
	
	public void insertScript(FacesContext context, UIComponent component) throws IOException{
		
		UIToolTip toolTip = (UIToolTip)component;

		StringBuffer ret = new StringBuffer();
		ret.append("<script ");
		ret.append("type=\"text/javascript\" ");
		ret.append("id =\"script" + component.getClientId(context)+"\">\n");
		StringBuffer script = new StringBuffer();
		//
		String jsVar = constructJSVariable(context, component);
		
		script.append(jsVar).append(";\n");
		
		ret.append(script.toString());
		
		ret.append("\n</script>");
		ResponseWriter writer 	= context.getResponseWriter();
		writer.write(ret.toString());
		
	}
	
	public Map <String, Object> getParamsMap(FacesContext context, UIToolTip toolTip) {
	    List <UIComponent> children = toolTip.getChildren();
	    Map <String, Object> paramsMap = new HashMap<String, Object>();
	    for(UIComponent child: children ) {
		if(child instanceof UIParameter) {
		    UIParameter param = (UIParameter)child;
		    paramsMap.put(param.getName(),param.getValue());
		}
	    }
	    return paramsMap;
	}
	
	private String getTargetId(FacesContext context, UIComponent component) {
		UIToolTip toolTip = (UIToolTip) component;
		String forValue = toolTip.getFor();
		
		if (forValue != null && forValue.length() != 0) {
			UIComponent targetComponent = getUtils().findComponentFor(component, forValue);
			if (targetComponent != null) {
				return targetComponent.getClientId(context);
			} else {
				return forValue;
			}
		} else {
			return component.getParent().getClientId(context);
		}
	}

	public String constructJSVariable(FacesContext context, UIComponent component) {
		UIToolTip toolTip = (UIToolTip)component;
		String targetClientId = getTargetId(context, component);
		StringBuffer ret = new StringBuffer();
		String comma = ",";
		String quot = "\"";
		String eventShow = (toolTip.isAttached()) ? toolTip.getShowEvent() : "";
		String eventHide = (toolTip.isAttached()) ? toolTip.getHideEvent() : "";
		if(eventShow.startsWith("on")){
			eventShow = eventShow.substring(2);
		}
		if(eventHide.startsWith("on")){
			eventHide = eventHide.substring(2);
		}
		
		Map<JSReference, Object> eventsMap = new HashMap<JSReference, Object>();
		eventsMap.put(new JSReference("showEvent"), eventShow);
		eventsMap.put(new JSReference("hideEvent"), eventHide);
		eventsMap.put(new JSReference("delay"), new Integer(toolTip.getShowDelay()));
		eventsMap.put(new JSReference("hideDelay"), new Integer(toolTip.getHideDelay()));
		
		JSFunctionDefinition ajaxFunc = new JSFunctionDefinition("event", "ajaxOptions");
		JSFunction function = AjaxRendererUtils.buildAjaxFunction(component, context);
		JSReference ref = new JSReference("ajaxOptions");
		function.addParameter(ref);
		ajaxFunc.addToBody(function);
				
		Map<String, Object> ajaxOptions = buildEventOptions(context, toolTip, targetClientId);
		ajaxOptions.putAll(getParamsMap(context, toolTip));
		JSFunctionDefinition completeFunc = getUtils().getAsEventHandler(context, component, "oncomplete", "; return true;");
		JSFunctionDefinition hideFunc = getUtils().getAsEventHandler(context, component,"onhide", "; return true;");
		JSFunctionDefinition showFunc = getUtils().getAsEventHandler(context, component,"onshow", "; return true;");
		
		Map<JSReference, Object> funcMap = new HashMap<JSReference, Object>();
		funcMap.put(new JSReference("oncomplete"), completeFunc);
		funcMap.put(new JSReference("onhide"), hideFunc);
		funcMap.put(new JSReference("onshow"), showFunc);
		
		
		ret.append("new ToolTip(" + ScriptUtils.toScript(eventsMap)).append(comma).append(ScriptUtils.toScript(funcMap))
		.append(comma).append(quot).append( toolTip.getClientId(context)).append(quot).append(comma).
		append(quot).append(targetClientId).append(quot).append(comma).
		append(quot).append(toolTip.getMode()).append(quot).append(comma).
		append(toolTip.isDisabled()).append(comma).
		append(quot).append(toolTip.getDirection()).append(quot).append(comma).
		append(toolTip.isFollowMouse()).append(comma).
		append(toolTip.getHorizontalOffset()).append(comma).
		append(toolTip.getVerticalOffset()).append(comma).
		append(ajaxFunc.toScript()).append(comma).append(ScriptUtils.toScript(ajaxOptions)).append(");");
		
		return ret.toString();
	}
	
	
	protected void doDecode(FacesContext context, UIComponent component) {

		UIToolTip tooltip = (UIToolTip)component;
		
		String clientId = tooltip.getClientId(context);
		
		if(context.getExternalContext().getRequestParameterMap().containsKey(clientId)){
			if("ajax".equals(tooltip.getMode())){
				new AjaxEvent(component).queue();
				new ActionEvent(component).queue();
			}
		}
	}

	public void encodeTooltipText(FacesContext context, UIToolTip component) throws IOException {
		 ResponseWriter responseWriter = context.getResponseWriter();
		 responseWriter.startElement(component.getUsedElementType(), component);
		 responseWriter.writeAttribute("id", component.getClientId(context) + "content", null);
		 if("ajax".equals(component.getMode())){
			 // we want to avoid rendering toolTip content during initialy page displaying
			 AjaxContext ajaxContext = AjaxContext.getCurrentInstance();
			 if(ajaxContext != null && ajaxContext.getAjaxAreasToRender().contains(component.getClientId(context) + "content")){
		 		 responseWriter.write(component.getValue() != null ? component.getValue().toString() : "");
				 super.renderChildren(context, component);
			 }
		 } else {
			 // client mode
	 		 responseWriter.write(component.getValue() != null ? component.getValue().toString() : "");
			 super.renderChildren(context, component);
		 }
		 responseWriter.endElement(component.getUsedElementType());
	}

	
	protected void doEncodeBegin(ResponseWriter writer, FacesContext context,
			UIComponent component) throws IOException {

		UIToolTip toolTip = (UIToolTip)component;
		Class rendererClass = null;
		try{
			if("block".equals(toolTip.getLayout())){
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRendererBlock");
			} else if("inline".equals(toolTip.getLayout())){
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRenderer");
			} else {
				throw new FacesException("Only \"block\" or \"inline\" values can be established to attribute \"layout\".");
			}
			ToolTipRenderer renderer = (ToolTipRenderer)rendererClass.newInstance(); 
			renderer.doEncodeBegin(writer, context, component);
			
		} catch(Exception e){
			throw new FacesException(e.getMessage(), e);
		}
	}

	
	protected void doEncodeChildren(ResponseWriter writer,
			FacesContext context, UIComponent component) throws IOException {

		UIToolTip toolTip = (UIToolTip)component;
		Class rendererClass = null;
		try{
			if("block".equals(toolTip.getLayout())){
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRendererBlock");
			} else {
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRenderer");
			}
			
			ToolTipRenderer renderer = null;
			
			renderer = (ToolTipRenderer)rendererClass.newInstance();
			
			renderer.doEncodeChildren(writer, context, component);
		} catch(Exception e){
			throw new FacesException(e.getMessage(), e);
		}
		
		
	}

	
	protected void doEncodeEnd(ResponseWriter writer, FacesContext context,
			UIComponent component) throws IOException {

		UIToolTip toolTip = (UIToolTip)component;
		Class rendererClass = null;
		try{
			if("block".equals(toolTip.getLayout())){
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRendererBlock");
			} else {
				rendererClass = Class.forName("org.richfaces.renderkit.html.HtmlToolTipRenderer");
			}
			ToolTipRenderer renderer = (ToolTipRenderer)rendererClass.newInstance(); 
			renderer.doEncodeEnd(writer, context, component);
		} catch (Exception e) {
			throw new FacesException(e.getMessage(), e);
		}
	}
	
	
	/* (non-Javadoc)
	 * @see javax.faces.render.Renderer#getRendersChildren()
	 */
	public boolean getRendersChildren() {
		return true;
	}

	
}

