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

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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

import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSLiteral;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.renderkit.HeaderResourcesRendererBase;
import org.ajax4jsf.renderkit.RendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.renderkit.RendererUtils.ScriptHashVariableWrapper;
import org.richfaces.component.UIEffect;
import org.richfaces.json.JSONTokener;

/**
 * @author Nick Belaevski
 *         mailto:nbelaevski@exadel.com
 *         created 09.08.2007
 *
 */
public class EffectRendererBase extends HeaderResourcesRendererBase {

	/* (non-Javadoc)
	 * @see org.ajax4jsf.renderkit.RendererBase#getComponentClass()
	 */
	protected Class getComponentClass() {
		return UIEffect.class;
	}
	
	public String convertParameters(FacesContext context, UIEffect effect) throws IOException {
		String params = effect.getParams();
		if (params == null) {
			return null;
		}

		StringBuffer buffer = new StringBuffer("{" + params + "}");
		try {
			replace(context, effect, buffer);
			return buffer.toString();
		} catch (Exception e) {
			IOException exception = new IOException(e.getMessage());
			exception.initCause(e);
			
			throw exception;
		}
	}
	
	private static void replace(FacesContext context, UIComponent effect, StringBuffer s) throws Exception {
		JSONTokener x = new JSONTokener(s.toString());
        char c;
        String key;

        if (x.nextClean() != '{') {
            throw x.syntaxError("A JSONObject text must begin with '{'");
        }
        for (;;) {
            int idx;
        	c = x.nextClean();
            switch (c) {
            case 0:
                throw x.syntaxError("A JSONObject text must end with '}'");
            case '}':
                return;
            default:
                x.back();
            	idx = x.getMyIndex();
            	//System.out.println(s.substring(x.getMyIndex()));
                key = x.nextValue().toString();
            }

            /*
             * The key is followed by ':'. We will also tolerate '=' or '=>'.
             */

            c = x.nextClean();
            if (c == '=') {
                if (x.next() != '>') {
                    x.back();
                }
            } else if (c != ':') {
                throw x.syntaxError("Expected a ':' after a key");
            }
            
            if ("id".equals(key)) {
            	Object value = x.nextValue();
				UIComponent component = RendererUtils.getInstance().findComponentFor(effect, value.toString());
				if (component != null) {
					value = component.getClientId(context);
				}

            	
            	s.replace(idx, x.getMyIndex(), "'id': '" + value + "'");
            	
            	return ;
            } else {
            	x.nextValue();
            }

            /*
             * Pairs are separated by ','. We will also tolerate ';'.
             */

            switch (x.nextClean()) {
            case ';':
            case ',':
                if (x.nextClean() == '}') {
                    return;
                }
                x.back();
                break;
            case '}':
                return;
            default:
                throw x.syntaxError("Expected a ',' or '}'");
            }
        }
	}	
	
	private String findComponentId(String id, FacesContext context, UIComponent component) {
		String result = null;
		if (! "".equals(id)) {
			UIComponent comp = getUtils().findComponentFor(component,id);
        	if (comp != null) {
        		String cid= comp.getClientId(context);
        		result = cid;
        	}
		}
		return result;
	}
	
	public void writeScript (FacesContext context, UIComponent component) throws IOException {
		
		Map<String, Object> attributes = component.getAttributes();
		String attachObj = "";
		String attachId = "";
		
		String id = (String) attributes.get("for");
		if (! "".equals(id)) {
			attachId = findComponentId(id, context, component);
			if (attachId == null) {
	    		// if no corresponded component id,  may be it is non-jsf id.
	        	// So, returning the id as is
				attachId = id;
				 // it might be the DOM object
				attachObj = id;				
			}
		}
		
		String event = (String)attributes.get("event");
        Boolean needsFunction = new Boolean(! "".equals(attributes.get("name")) && "".equals(event));
        Boolean needsObserver = new Boolean(! "".equals(event) && ! "".equals(attachId) );
        
		if (needsFunction || needsObserver) {
		
			String targetObj = "";
			String targetId = "";
			
			id = (String) attributes.get("targetId");
			targetId = findComponentId(id, context, component);
			if (targetId == null) {
				// if no corresponded component id,  may be it is non-jsf id.
				// So, returning the id as is
				targetId = id;
				// it might be the DOM object
				targetObj = id;
			}
			
			Map<String, Object> options = new HashMap<String, Object>();
			getUtils().addToScriptHash(options, "targetObj", targetObj,  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "attachObj", attachObj,  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "targetId", targetId,  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "attachId", attachId,  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "type", attributes.get("type"),  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "event", attributes.get("event"),  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "name", attributes.get("name"),  null , ScriptHashVariableWrapper.DEFAULT);
			getUtils().addToScriptHash(options, "params", new JSLiteral(convertParameters(context, (UIEffect)component)),  null , ScriptHashVariableWrapper.DEFAULT);
			
			JSFunction function = new JSFunction("Richfaces.effect.create");
			if (!options.isEmpty()) {
				function.addParameter(options);
			}
			
			ResponseWriter writer = context.getResponseWriter();
			
			writer.startElement(HTML.SCRIPT_ELEM, component);
			getUtils().writeAttribute(writer, HTML.TYPE_ATTR, "text/javascript");
			writer.writeText(function.toScript(), component, null);
			writer.endElement(HTML.SCRIPT_ELEM);
		}
	}
}
