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

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.faces.component.ActionSource;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIParameter;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.servlet.http.HttpServletResponse;

import org.ajax4jsf.Messages;
import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.component.AjaxContainer;
import org.ajax4jsf.component.AjaxLoadBundleComponent;
import org.ajax4jsf.component.AjaxSupport;
import org.ajax4jsf.component.AjaxViewRoot;
import org.ajax4jsf.component.JavaScriptParameter;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.util.ServicesUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author shura
 * 
 * Some utilites for render AJAX components.
 */
public class AjaxRendererUtils {

	/**
	 * Name Javasript function for submit AJAX request
	 */
	public static final String AJAX_FUNCTION_NAME = "A4J.AJAX.Submit";

	/**
	 * Attribute for keep clientId of status component
	 */
	public static final String STATUS_ATTR_NAME = "status";

	/**
	 * Attribute for keep JavaScript function name for call after complete
	 * request.
	 */
	public static final String ONCOMPLETE_ATTR_NAME = "oncomplete";
	
	/**
	 * Attribute for keep JavaScript function name for call after complete
	 * request.
	 */
	public static final String ONCOMPLETE_CONTENT_ID = "org.ajax4jsf.oncomplete";
	
	/**
	 * Attribute for keep JavaScript function name for call before updating
	 * DOM tree.
	 */
	public static final String ONBEFOREDOMUPDATE_ATTR_NAME = "onbeforedomupdate";
	

	/**
	 * Attribute to keep
	 */
	public static final String LIMITTOLIST_ATTR_NAME = "limitToList";

	private static Log log = LogFactory.getLog(AjaxRendererUtils.class);

	public static final String AJAX_REGIONS_ATTRIBUTE = "reRender";

	private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();

	private static final String VALUE_ATTR = "value";

	public static final String AJAX_AREAS_RENDERED = "org.ajax4jsf.areas.rendered";

	private static final String AJAX_SINGLE_ATTR = "ajaxSingle";

	public static final String AJAX_QUEUE_ATTR = "eventsQueue";

	public static final String AJAX_DELAY_ATTR = "requestDelay";

	private static final Object AJAX_ABORT_ATTR = "ignoreDupResponses";

	/**
	 * Static class - protect constructor TODO - make as subclass of chameleon
	 * RendererUtils.
	 * 
	 */
	private AjaxRendererUtils() {

	}

	/**
	 * Build JavaScript onclick event for given component
	 * 
	 * @param uiComponent -
	 *            component for build event
	 * @param facesContext
	 * @return <code>StringBuffer</code> with Javascript code
	 */
	public static StringBuffer buildOnClick(UIComponent uiComponent,
			FacesContext facesContext) {
		return buildOnEvent(uiComponent, facesContext, HTML.onclick_ATTRIBUTE);
	}

	/**
	 * Build JavaScript event for component
	 * 
	 * @param uiComponent -
	 *            component for build event
	 * @param facesContext
	 * @param eventName -
	 *            name of event
	 * @return <code>StringBuffer</code> with Javascript code TODO - for
	 *         key-based events build list of supported keys
	 */
	public static StringBuffer buildOnEvent(UIComponent uiComponent,
			FacesContext facesContext, String eventName) {
		StringBuffer onEvent = new StringBuffer();
		if (null != eventName) {
			String commandOnEvent = (String) uiComponent.getAttributes().get(
					eventName);
			if (commandOnEvent != null) {
				onEvent.append(commandOnEvent);
				onEvent.append(';');
			}
		}
		JSFunction ajaxFunction = buildAjaxFunction(uiComponent, facesContext);
		// Create formal parameter for non-input elements ???
		// Link Control pseudo-object
		// Options map. Possible options for function call :
		// control - name of form control for submit.
		// name - name for link control \
		// value - value of control. - possible replace by parameters ?
		// single true/false - submit all form or only one control.
		// affected - array of element's ID for update on responce.
		// oncomplete - function for call after complete request.
		// status - id of request status component.
		// parameters - map of parameters name/value for append on request.
		// TODO
		// sync true/false - run script in sync mode.
		// ..........
		ajaxFunction.addParameter(buildEventOptions(facesContext, uiComponent));

		// appendAjaxSubmitParameters(facesContext, uiComponent, onEvent);
		ajaxFunction.appendScript(onEvent);
		if (uiComponent instanceof AjaxSupport) {
			AjaxSupport support = (AjaxSupport) uiComponent;
			if (support.isDisableDefault()) {
				onEvent.append("; return false;");
			}
		}
		log.debug(Messages.getMessage(Messages.BUILD_ONCLICK_INFO, uiComponent
				.getId(), onEvent.toString()));
		return onEvent;

	}

	/**
	 * @param facesContext
	 * @param uiComponent
	 * @return
	 */
	public static Map buildEventOptions(FacesContext facesContext,
			UIComponent uiComponent) {
		String clientId = uiComponent.getClientId(facesContext);
		Map componentAttributes = uiComponent.getAttributes();
		Map options = new HashMap();
		Map parameters = new HashMap();
		// UIForm form = getNestingForm(uiComponent);
		// "input" - if assigned to html input element.
		boolean input = (uiComponent instanceof AjaxSupport && uiComponent
				.getParent() instanceof EditableValueHolder)
				|| uiComponent instanceof EditableValueHolder;
		// Action component - button etc.
		boolean action = (uiComponent instanceof AjaxSupport && uiComponent
				.getParent() instanceof ActionSource)
				|| uiComponent instanceof ActionSource;
		// TODO - disable ( or not ? ) submission of action for disabled default
		// ?
		// if ( action && uiComponent instanceof AjaxSupport) {
		// AjaxSupport support = (AjaxSupport) uiComponent;
		// if (support.isDisableDefault()) {
		// action = false;
		// }
		// }

		boolean ajaxSingle = Boolean.TRUE.equals(componentAttributes
				.get(AJAX_SINGLE_ATTR));
		// For input components in single mode or without form submit input
		// control )
		if (ajaxSingle && input) { // || action
			// TODO - inside form, put reference :
			// document.forms['form'].controls['control']
			options.put("control", JSReference.THIS);
		}
		// Control value for submit
		String controlName;
		Object controlValue;
		// TODO - make compatible with JSF RI/MyFaces ? use submittedValue ( if
		// any ) for UIInput, converted value for ValueHolder.
		controlName = clientId;
		controlValue = clientId;
		parameters.put(controlName, controlValue);
		// Move processing of a 'single' attribute to a server-side.
//		if (ajaxSingle) {
//			options.put("single", JSReference.TRUE);
//		}
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(facesContext);
		// Setup action URL. For portlet environment, it will be different from
		// page.
		options.put("actionUrl", ajaxContext.getAjaxActionURL(facesContext));
		// Add application-wide Ajax parameters
		parameters.putAll(ajaxContext.getCommonAjaxParameters());
		// add child parameters
		for (Iterator it = uiComponent.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) {
					throw new IllegalArgumentException(Messages.getMessage(
							Messages.UNNAMED_PARAMETER_ERROR, uiComponent
									.getClientId(facesContext)));
				}
				boolean escape = true;
				if (child instanceof JavaScriptParameter) {
					JavaScriptParameter actionParam = (JavaScriptParameter) child;
					escape = !actionParam.isNoEscape();
				}
				if (escape) {
					parameters.put(name, value);
				} else {
					parameters.put(name, new JSReference(value.toString()));
					// if(it.hasNext()){onEvent.append(',');};
					// renderAjaxLinkParameter( name,
					// value, onClick, jsForm, nestingForm);
				}
			}
		}
		if (!parameters.isEmpty()) {
			options.put("parameters", parameters);
		}
		// parameter to render only current list of areas.
		if (isAjaxLimitToList(uiComponent)) {
			Set ajaxAreas = getAjaxAreas(uiComponent);
			Set areasIds = new HashSet();
			if (null != ajaxAreas) {
				for (Iterator iter = ajaxAreas.iterator(); iter.hasNext();) {
					String id = (String) iter.next();
					UIComponent comp = uiComponent.findComponent(id);
					if (null != comp) {
						areasIds.add(comp.getClientId(facesContext));
					} else {
						areasIds.add(id);
					}
				}
			}
			options.put("affected", areasIds);
		}
		String oncomplete = getAjaxOncomplete(uiComponent);
		if (null != oncomplete) {
			JSFunctionDefinition function = new JSFunctionDefinition();
			function.addParameter("request");
			function.addParameter("event");
			function.addParameter("data");
			function.addToBody(oncomplete);

			options.put(ONCOMPLETE_ATTR_NAME, function);
		}
		
		String beforeupdate = getAjaxOnBeforeDomUpdate(uiComponent);
		if (null != beforeupdate) {
			JSFunctionDefinition function = new JSFunctionDefinition();
			function.addParameter("request");
			function.addParameter("event");
			function.addParameter("data");
			function.addToBody(beforeupdate);

			options.put(ONBEFOREDOMUPDATE_ATTR_NAME, function);
		}
		
		
		String status = getAjaxStatus(uiComponent);
		if (null != status) {
			options.put("status", status);
		}
		String queue = (String) componentAttributes.get(AJAX_QUEUE_ATTR);
		Integer requestDelay = (Integer) componentAttributes
				.get(AJAX_DELAY_ATTR);
		if (null != requestDelay && requestDelay.intValue() > 0) {
			options.put(AJAX_DELAY_ATTR, requestDelay);
			if (null == queue) {
				queue = clientId;
			}
		}
		Boolean ignoreDupResponses = (Boolean) componentAttributes
				.get(AJAX_ABORT_ATTR);
		if (null != ignoreDupResponses && ignoreDupResponses.booleanValue()) {
			options.put(AJAX_ABORT_ATTR, JSReference.TRUE);
			if (null == queue) {
				queue = clientId;
			}
		}
		if (null != queue) {
			options.put(AJAX_QUEUE_ATTR, queue);
		}
		// request timeout.
		Integer timeout = (Integer) componentAttributes.get("timeout");
		if (null != timeout && timeout.intValue() > 0) {
			options.put("timeout", timeout);
		}
		// Encoding for requests
		String encoding = (String) componentAttributes.get("encoding");
		if (null != encoding) {
			options.put("encoding", encoding);
		}
		return options;
	}

	/**
	 * Create call to Ajax Submit function with first two parameters
	 * 
	 * @param uiComponent
	 * @param facesContext
	 * @param functionName
	 *            TODO
	 * @return
	 */
	public static JSFunction buildAjaxFunction(UIComponent uiComponent,
			FacesContext facesContext) {
		JSFunction ajaxFunction = buildAjaxFunction(uiComponent, facesContext,
				AJAX_FUNCTION_NAME);
		// client-side script must have reference to event-enabled object.
		ajaxFunction.addParameter(new JSReference("event"));
		return ajaxFunction;
	}

	/**
	 * Create call to Ajax Submit function with first two parameters
	 * 
	 * @param uiComponent
	 * @param facesContext
	 * @param functionName
	 *            TODO
	 * @return
	 */
	public static JSFunction buildAjaxFunction(UIComponent uiComponent,
			FacesContext facesContext, String functionName) {
		JSFunction ajaxFunction = new JSFunction(functionName);
		UIComponent nestingContainer = (UIComponent) findAjaxContainer(
				facesContext, uiComponent);
		ajaxFunction.addParameter(nestingContainer.getClientId(facesContext));
		// build form name or ActionUrl for script
		UIComponent nestingForm = getNestingForm(uiComponent);
		if (null == nestingForm) {
			ajaxFunction.addParameter(JSReference.NULL);
		} else {
			ajaxFunction.addParameter(nestingForm.getClientId(facesContext));
		}
		return ajaxFunction;
	}

	/**
	 * Append common parameters ( array of affected areas, status area id, on
	 * complete function ) to JavaScript event string.
	 * 
	 * @param uiComponent
	 * @param onClick -
	 *            buffer with JavaScript code eg... AJAX.Submit(form,this
	 */
	// public static void appendAjaxSubmitParameters(FacesContext facesContext,
	// UIComponent uiComponent, StringBuffer onClick)
	// {
	// Set ajaxAreas = getAjaxAreas(uiComponent);
	// onClick.append(',');
	// // parameter to render only current list of areas.
	// if (isAjaxLimitToList(uiComponent) && ajaxAreas != null &&
	// ajaxAreas.size() > 0)
	// {
	// onClick.append('[');
	// Iterator areas = ajaxAreas.iterator();
	// boolean first = true;
	// while (areas.hasNext())
	// {
	// String element = (String) areas.next();
	// UIComponent component = uiComponent.findComponent(element);
	// if (null != component)
	// {
	// if (!first)
	// {
	// onClick.append(',');
	// }
	// else
	// {
	// first = false;
	// }
	// onClick.append('\'');
	// onClick.append(component.getClientId(facesContext));
	// onClick.append('\'');
	// }
	// }
	// onClick.append("]");
	// }
	// else
	// {
	// onClick.append("null");
	// }
	// // insert id of request status element.
	// onClick.append(',');
	// String status = getAjaxStatus(uiComponent);
	// if (null != status)
	// {
	// onClick.append('\'').append(status).append('\'');
	// }
	// else
	// {
	// onClick.append("null");
	// }
	// // insert function name for call after completed request
	// onClick.append(',');
	// String oncomplete = getAjaxOncomplete(uiComponent);
	// if (null != oncomplete)
	// {
	// onClick.append(oncomplete);
	// }
	// else
	// {
	// onClick.append("null");
	// }
	//
	// }
	/**
	 * Get list of clientId's for given component
	 * 
	 * @param uiComponent
	 * @return List of areas Id's , updated by this component.
	 */
	public static Set getAjaxAreas(UIComponent uiComponent) {
		Object areas;
		if (uiComponent instanceof AjaxComponent) {
			areas = ((AjaxComponent) uiComponent).getReRender();

		} else {
			areas = uiComponent.getAttributes().get(
					AjaxRendererUtils.AJAX_REGIONS_ATTRIBUTE);
		}
		return asSet(areas);
	}

	/**
	 * Convert parameter ( Collection, List, array, String, comma-separated
	 * String ) to list of srings. TODO - when move to JDK 5, change to
	 * List&lt;String&gt;
	 * 
	 * @param valueToSet -
	 *            obect for convert to List.
	 * @return - list of strings.
	 */
	public static Set asSet(Object valueToSet) {

		if (null != valueToSet) {
			// Simplest case - set.
			if (valueToSet instanceof Set) {
				return (Set) valueToSet;
			}
			// Other collections.
			else if (valueToSet instanceof Collection) {
				return new HashSet((Collection) valueToSet);
			}
			// Array
			else if (OBJECT_ARRAY_CLASS.isAssignableFrom(valueToSet.getClass())) {
				return new HashSet(Arrays.asList((Object[]) valueToSet));
			}
			// Tokenize string.
			else if (valueToSet instanceof String) {
				String areasString = (String) valueToSet;
				if (areasString.indexOf(",") > 0) {
					return new HashSet(Arrays.asList(areasString.trim().split(
							"(\\s)*,(\\s)*")));
				} else {
					Set areasSet = new HashSet(5);
					areasSet.add(areasString.trim());
					return areasSet;
				}

			}
		}
		return null;
	}

	/**
	 * Get status area Id for given component.
	 * 
	 * @param component
	 * @return clientId of status area, or <code>null</code>
	 */
	public static String getAjaxStatus(UIComponent component) {
		String statusId;
		if (component instanceof AjaxComponent) {
			statusId = ((AjaxComponent) component).getStatus();

		} else {
			statusId = (String) component.getAttributes().get(STATUS_ATTR_NAME);
		}
		if (null != statusId) {
			UIComponent status = null;
			UIComponent currentParent = component;
			try {
				// Check the naming container of the current
				// component for component identified by
				// 'forComponent'
				while (currentParent != null) {
					// If the current component is a NamingContainer,
					// see if it contains what we're looking for.
					status = currentParent.findComponent(statusId);
					if (status != null)
						break;
					// if not, start checking further up in the view
					currentParent = currentParent.getParent();
				}
			} catch (Exception e) {
				// ignore - log the warning
			}
			if (null != status) {
				statusId = status
						.getClientId(FacesContext.getCurrentInstance());
			} else {
				log.warn(Messages.getMessage(
						Messages.AJAX_STATUS_COMPONENT_NOT_FOWND_WARNING,
						component.getId()));
			}
		}
		return statusId;
	}

	/**
	 * Get function name for call on completed ajax request.
	 * 
	 * @param component
	 *            for wich calculate function name
	 * @return name of JavaScript function or <code>null</code>
	 */
	public static String getAjaxOncomplete(UIComponent component) {
		if (component instanceof AjaxComponent) {
			return ((AjaxComponent) component).getOncomplete();

		}
		return (String) component.getAttributes().get(ONCOMPLETE_ATTR_NAME);
	}

	/**
	 * Get function name for call before update DOM.
	 * 
	 * @param component
	 *            for wich calculate function name
	 * @return name of JavaScript function or <code>null</code>
	 */
	public static String getAjaxOnBeforeDomUpdate(UIComponent component) {
		if (component instanceof AjaxComponent) {
			return ((AjaxComponent) component).getOnbeforedomupdate();

		}
		return (String) component.getAttributes().get(ONBEFOREDOMUPDATE_ATTR_NAME);
	}
	
	
	/**
	 * Calculate, must be component render only given areas, or all sended from
	 * server.
	 * 
	 * @param component
	 * @return <code>true</code> if client must render ONLY given areas.
	 */
	public static boolean isAjaxLimitToList(UIComponent component) {
		boolean result = false;
		if (component instanceof AjaxComponent) {
			result = ((AjaxComponent) component).isLimitToList();

		} else {
			try {
				result = ((Boolean) component.getAttributes().get(
						LIMITTOLIST_ATTR_NAME)).booleanValue();
			} catch (NullPointerException e) {
				// NullPointer - ignore ...
			} catch (ClassCastException e1) {
				// not Boolean - false ...
			}
		}
		return result;
	}

	/**
	 * Replacement for buggy in MyFaces <code>RendererUtils</code>
	 * 
	 * @param component
	 * @return
	 */
	public static String getAbsoluteId(UIComponent component) {
		if (component == null)
			throw new NullPointerException(Messages
					.getMessage(Messages.COMPONENT_NULL_ERROR_2));

		StringBuffer idBuf = new StringBuffer();

		idBuf.append(component.getId());

		UIComponent parent = component;

		while ((parent = parent.getParent()) != null) {
			if (parent instanceof NamingContainer) {
				idBuf.insert(0, NamingContainer.SEPARATOR_CHAR);
				idBuf.insert(0, parent.getId());
			}
		}
		idBuf.insert(0, NamingContainer.SEPARATOR_CHAR);
		log.debug(Messages.getMessage(Messages.CALCULATE_COMPONENT_ID_INFO,
				component.getId(), idBuf.toString()));
		return idBuf.toString();
	}

	/**
	 * Find nested form for given component
	 * 
	 * @param component
	 * @return nested <code>UIForm</code> component, or <code>null</code>
	 */
	public static UIComponent getNestingForm(UIComponent component) {
		UIComponent parent = component;
		// Search enclosed UIForm or ADF UIXForm component
		while (parent != null
				&& !(parent instanceof UIForm)
				&& !("org.apache.myfaces.trinidad.Form".equals(parent
						.getFamily()))
				&& !("oracle.adf.Form".equals(parent.getFamily()))) {
			parent = parent.getParent();
		}

		return parent;
	}

	protected static String getAjaxActionUrl(FacesContext facesContext) {
	    	return AjaxContext.getCurrentInstance(facesContext).getAjaxActionURL(facesContext);
	}

	/**
	 * @param facesContext
	 * @param uiComponent
	 * @return
	 */
	public static org.ajax4jsf.component.AjaxContainer findAjaxContainer(
			FacesContext facesContext, UIComponent uiComponent) {
		UIComponent parent = uiComponent.getParent();
		while (parent != null
				&& !(parent instanceof org.ajax4jsf.component.AjaxContainer)) {
			parent = parent.getParent();
		}

		org.ajax4jsf.component.AjaxContainer nestingContainer = null;
		if (parent != null) {
			// link is nested inside a form
			nestingContainer = (org.ajax4jsf.component.AjaxContainer) parent;
		} else if (facesContext.getViewRoot() instanceof AjaxViewRoot) {
			nestingContainer = (AjaxContainer) facesContext.getViewRoot();
		}
		return nestingContainer;
	}

	/**
	 * Encode rendered areas as special HTML tag ( span in current release )
	 * 
	 * @param context
	 * @param component
	 * @throws IOException
	 */
	public static void encodeAreas(FacesContext context,
			UIComponent component) throws IOException {
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
		ExternalContext externalContext = context.getExternalContext();
		Map requestMap = externalContext.getRequestMap();
		Set rendered = ajaxContext.getAjaxRenderedAreas();
		StringBuffer senderString = new StringBuffer();
		// write special area for list of rendered elements. Client-side
		// Java
		// Script
		// read this structure for update areas of DOM tree.
		ResponseWriter out = context.getResponseWriter();
		// Create <span> element to keep list rendered aread ( in title
		// attribute )
		// More right will create special namespace for such
		// information,
		// but I want to keep simple html ( xhtml ) document - on case
		// I have troubles with microsoft XMLHTTP validations.
		out
				.startElement(AjaxContainerRenderer.AJAX_RESULT_GROUP_TAG,
						component);
		out.writeAttribute(HTML.NAME_ATTRIBUTE,
				AjaxContainerRenderer.AJAX_UPDATE_HEADER, null);
		for (Iterator it = rendered.iterator(); it.hasNext();) {
			String id = (String) it.next();
			// out.startElement(AJAX_RESULT_TAG, component);
			// out.writeText(id,null);
			// out.endElement(AJAX_RESULT_TAG);
			senderString.append(id);
			if (it.hasNext()) {
				senderString.append(',');
			}
		}
		out.writeAttribute(AjaxContainerRenderer.AJAX_RESULT_GROUP_ATTR,
				senderString, null);
		out.endElement(AjaxContainerRenderer.AJAX_RESULT_GROUP_TAG);
		// For sequences and client-saved states.

		out.startElement(AjaxContainerRenderer.AJAX_VIEW_STATE_TAG, component);
		out.writeAttribute(HTML.id_ATTRIBUTE,
				AjaxContainerRenderer.AJAX_VIEW_STATE_ID, null);
		writeState(context);
		out.endElement(AjaxContainerRenderer.AJAX_VIEW_STATE_TAG);
		// Write rendered flag to html <meta>
		out
				.startElement(AjaxContainerRenderer.AJAX_RESULT_GROUP_TAG,
						component);
		out.writeAttribute(HTML.id_ATTRIBUTE,
				AjaxContainerRenderer.AJAX_FLAG_HEADER, null);
		out.writeAttribute(HTML.NAME_ATTRIBUTE,
				AjaxContainerRenderer.AJAX_FLAG_HEADER, null);
		out.writeAttribute(AjaxContainerRenderer.AJAX_RESULT_GROUP_ATTR,
				"true", null);
		out.endElement(AjaxContainerRenderer.AJAX_RESULT_GROUP_TAG);
		// set response header with list of rendered ID's
		Object response = externalContext.getResponse();
		// Use reflection for send responce headers - we can get
		// different responces classes
		// for different environment ( portal, cocoon etc )
		if (response instanceof HttpServletResponse) {
			HttpServletResponse httpResponse = (HttpServletResponse) response;
//			httpResponse.setHeader(AjaxContainerRenderer.AJAX_UPDATE_HEADER,
//					senderString.toString());
			httpResponse.setHeader(AjaxContainerRenderer.AJAX_FLAG_HEADER,
					"true");
		} else {
			try {
				Method setHeadergMethod = response.getClass()
						.getMethod("setHeader",
								new Class[] { String.class, String.class });
//				setHeadergMethod.invoke(response, new Object[] {
//						AjaxContainerRenderer.AJAX_UPDATE_HEADER,
//						senderString.toString() });
				setHeadergMethod.invoke(response, new Object[] {
						AjaxContainerRenderer.AJAX_FLAG_HEADER, "true" });
			} catch (Exception e) {
				log
						.error(Messages
								.getMessage(Messages.DETECTING_ENCODING_DISABLED_ERROR));
				log.error(Messages.getMessage(
						Messages.OBTAIN_RESPONSE_SET_HEADER_ERROR, e));
			}
		}
		Map responseDataMap = ajaxContext.getResponseDataMap();
		// Get data serializer instance
		AJAXDataSerializer serializer = (AJAXDataSerializer) ServicesUtils
				.getServiceInstance(AJAXDataSerializer.SERVICE);
		// Put data to JavaScript handlers, inside <span> elements.
		for (Iterator dataIterator = responseDataMap.keySet().iterator(); dataIterator
				.hasNext();) {
			Object dataKey = dataIterator.next();
			out.startElement(HTML.SPAN_ELEM, component);
			out.writeAttribute(HTML.id_ATTRIBUTE, dataKey, null);
			String dataString = serializer.asString(responseDataMap
					.get(dataKey));
			out.write(dataString);
			out.endElement(HTML.SPAN_ELEM);
		}
		// Include active 'oncomplete' function content :
		Object oncomplete = ajaxContext.getOncomplete();
		if(null != oncomplete){
			out.startElement(HTML.SPAN_ELEM, component);
			out.writeAttribute(HTML.id_ATTRIBUTE, ONCOMPLETE_CONTENT_ID, null);
			out.writeText(oncomplete,null);
			out.endElement(HTML.SPAN_ELEM);			
		}
		// For self-rendered case, we use own methods for replace stateKey by
		// real value
		// in XML filter.
		// if(ajaxContext.isSelfRender()){
		// saveViewState(context, out);
		// }
		requestMap.put(AJAX_AREAS_RENDERED, "true");
	}

	/**
	 * Write state saving markers to context, include MyFaces view sequence.
	 * 
	 * @param context
	 * @throws IOException
	 */
	public static void writeState(FacesContext context) throws IOException {
		context.getApplication().getViewHandler().writeState(context);
	}

	/**
	 * Encode declaration for AJAX response. Render &lt;html&gt;&lt;body&gt;
	 * 
	 * @param context
	 * @param component
	 * @throws IOException
	 */
	public static void encodeAjaxBegin(FacesContext context,
			UIComponent component) throws IOException {
		// AjaxContainer ajax = (AjaxContainer) component;
		ResponseWriter out = context.getResponseWriter();
		// DebugUtils.traceView("ViewRoot in AJAX Page encode begin");
		out.startElement("html", component);
		// TODO - html attributes. lang - from current locale ?
		Locale locale = context.getViewRoot().getLocale();
		out.writeAttribute(HTML.lang_ATTRIBUTE, locale.toString(), "lang");
		out.startElement("body", component);
	}

	/**
	 * End encoding of AJAX response. Render tag with included areas and close
	 * &lt;/body&gt;&lt;/html&gt;
	 * 
	 * @param context
	 * @param component
	 * @throws IOException
	 */
	public static void encodeAjaxEnd(FacesContext context, UIComponent component)
			throws IOException {
		// AjaxContainer ajax = (AjaxContainer) component;
		ResponseWriter out = context.getResponseWriter();
		// DebugUtils.traceView("ViewRoot in AJAX Page encode begin");

		encodeAreas(context, component);
			out.endElement("body");
			out.endElement("html");
	}

	/**
	 * Find all instances of {@link UILoadBundle} in view tree and load bundles
	 * to request-scope map.
	 * 
	 * @param context
	 * @throws IOException
	 */
	public static void loadBundles(FacesContext context) {
		// TODO - performanse improove - don't seek by all components tree.
		loadBundles(context, context.getViewRoot());

	}

	/**
	 * Recursive helper for {@link #loadBundles(FacesContext)}
	 * 
	 * @param context
	 * @param component
	 * @throws IOException
	 */
	private static void loadBundles(FacesContext context, UIComponent component) {
		// Iterate over cildrens
		for (Iterator iter = component.getChildren().iterator(); iter.hasNext();) {
			UIComponent child = (UIComponent) iter.next();
			loadCildBundles(context, child);
		}
		// Iterate over facets
		for (Iterator iter = component.getFacets().values().iterator(); iter
				.hasNext();) {
			UIComponent child = (UIComponent) iter.next();
			loadCildBundles(context, child);
		}
	}

	/**
	 * @param context
	 * @param child
	 */
	private static void loadCildBundles(FacesContext context, UIComponent child) {
		if (child instanceof AjaxLoadBundleComponent) {
			try {
				child.encodeBegin(context);
			} catch (IOException e) {
				// DO nothing - really, LoadBundle don't can throw exceptions.
			}
		} else {
			loadBundles(context, child);
		}
	}

	/**
	 * @param facesContext
	 * @return
	 */
	public static boolean isAjaxRequest(FacesContext facesContext) {
		
		return AjaxContext.getCurrentInstance(facesContext).isAjaxRequest(facesContext);
	}

	/**
	 * @param facesContext
	 * @param component
	 * @param id
	 */
	public static void addRegionByName(FacesContext facesContext, UIComponent component, String id) {

		AjaxContext.getCurrentInstance(facesContext).addComponentToAjaxRender(component, id);
	}
	/**
	 * @param facesContext
	 * @param component
	 * @param id
	 */
	public static void addRegionsFromComponent(UIComponent component, FacesContext facesContext) {

		AjaxContext.getCurrentInstance(facesContext).addRegionsFromComponent(component);
	}

}
