package org.ajax4jsf.context;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.VariableResolver;

import org.ajax4jsf.Messages;

public abstract class AjaxContext {

	/**
	 * Key for keep request state information in request-scope attributes.
	 */
	public static final String AJAX_CONTEXT_KEY = "ajaxContext";

	public abstract Map getCommonAjaxParameters();

	public abstract String getAjaxActionURL(FacesContext context);

	public abstract String getAjaxActionURL();

	public abstract void setResponseData(Object responseData);

	public abstract Object getResponseData();

	public abstract void setOncomplete(Object oncompleteFunction);

	public abstract Object getOncomplete();

	public abstract void setViewIdHolder(ViewIdHolder viewIdHolder);

	public abstract ViewIdHolder getViewIdHolder();

	public abstract boolean removeRenderedArea(String id);

	public abstract void addRenderedArea(String id);

	public abstract Set getAjaxRenderedAreas();

	public abstract void addComponentToAjaxRender(UIComponent base, String id);

	public abstract void addComponentToAjaxRender(UIComponent component);

	public abstract void addRegionsFromComponent(UIComponent component);

	public abstract Set getAjaxAreasToRender();

	public abstract boolean isAjaxRequest(FacesContext context);

	public abstract boolean isAjaxRequest();

	public abstract void processHeadResources(FacesContext context)
			throws FacesException;

	public abstract void encodeAjaxEnd(FacesContext context,
			UIComponent component) throws IOException;

	public abstract void encodeAjaxBegin(FacesContext context,
			UIComponent component) throws IOException;

	public abstract void renderAjaxRegion(FacesContext context,
			UIComponent component, boolean useFilterWriter)
			throws FacesException;

	public abstract void renderSubmittedAjaxRegion(FacesContext context,
			final boolean useFilterWriter);

	public abstract void renderSubmittedAjaxRegion(FacesContext context);

	public static final String SCRIPTS_PARAMETER = "org.ajax4jsf.framework.HEADER_SCRIPTS";
	public static final String STYLES_PARAMETER = "org.ajax4jsf.framework.HEADER_STYLES";
	public static final String RESPONSE_DATA_KEY = "_ajax:data";
	private static final String SERVICE_RESOURCE = "META-INF/services/"
			+ AjaxContext.class.getName();
	private static final String DEFAULT_CONTEXT_CLASS = "org.ajax4jsf.context.AjaxContextImpl";

	/**
	 * Get instance of current AJAX Context. Instance get by
	 * {@link VariableResolver#resolveVariable(FacesContext, String)} for
	 * variable {@link AjaxContext#AJAX_CONTEXT_KEY}
	 * 
	 * @return memento instance for current request
	 */
	public static AjaxContext getCurrentInstance() {
		FacesContext context = FacesContext.getCurrentInstance();
		return getCurrentInstance(context);
	}

	private static Map ajaxContextClasses = new HashMap();

	/**
	 * Get instance of current AJAX Context. Instance get by
	 * {@link VariableResolver#resolveVariable(FacesContext, String)} for
	 * variable {@link AjaxContext#AJAX_CONTEXT_KEY}
	 * 
	 * @param context
	 *            current FacesContext
	 * @return instance of AjaxContext.
	 */
	public static AjaxContext getCurrentInstance(FacesContext context) {
		if (null == context) {
			throw new NullPointerException("FacesContext is null");
		}
		Map requestMap = context.getExternalContext()
				.getRequestMap();
		AjaxContext ajaxContext = (AjaxContext) requestMap
				.get(AJAX_CONTEXT_KEY);
		if (null == ajaxContext) {
			// TODO Create default implementation.
			// ajaxContext = new AjaxContext();
			ClassLoader contextClassLoader = Thread.currentThread()
					.getContextClassLoader();
			Class clazz;
			synchronized (ajaxContextClasses) {
				clazz = (Class)ajaxContextClasses.get(contextClassLoader);
				if (null == clazz) {
					String factoryClassName = DEFAULT_CONTEXT_CLASS;
					// Pluggable factories.
					InputStream input = null; // loader.getResourceAsStream(SERVICE_RESOURCE);
					input = contextClassLoader
							.getResourceAsStream(SERVICE_RESOURCE);
					// have services file.
					if (input != null) {
						try {
							BufferedReader reader = new BufferedReader(
									new InputStreamReader(input));
							factoryClassName = reader.readLine();

						} catch (Exception e) {
							throw new FacesException(
									"Error to create AjaxContext Instance", e);
						} finally {
							try {
								input.close();
							} catch (IOException e) {
								// Ignore
							}
						}
					}
					try {
						clazz =  Class.forName(
								factoryClassName, false, contextClassLoader);
					} catch (ClassNotFoundException e) {
						throw new FacesException(
								"AjaxContext implementation class "
										+ factoryClassName + " not found ", e);
					}
					ajaxContextClasses.put(contextClassLoader, clazz);

				}
			}
			try {
				ajaxContext = (AjaxContext)clazz.newInstance();
			} catch (InstantiationException e) {
				throw new FacesException(
						"Error to create AjaxContext Instance", e);
			} catch (IllegalAccessException e) {
				throw new FacesException(
						"No access to AjaxContext constructor", e);
			}
			requestMap.put(AJAX_CONTEXT_KEY, ajaxContext);
		}
		return ajaxContext;
	}

	public AjaxContext() {
		super();
	}

	public abstract void release();

	public abstract Map getResponseDataMap();

	public abstract void setAjaxRequest(boolean b);

	public abstract boolean isSelfRender();

	public abstract void setSelfRender(boolean b);

	public abstract String getSubmittedRegionClientId(FacesContext context);

	public abstract void saveViewState(FacesContext context) throws IOException;
}