/**
 * 
 */
package org.ajax4jsf.portlet.application;

import java.io.Serializable;
import java.util.Map;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeDefaultViewNotSpecifiedException;

import org.ajax4jsf.portlet.AjaxFacesPortlet;
import org.apache.commons.collections.map.LRUMap;

/**
 * @author asmirnov
 * 
 */
public class PortletStateHolder implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 3040262189850562853L;

	private static final String STATE_HOLDER = PortletStateHolder.class
			.getName();

	private static final String[] modes = { PortletMode.VIEW.toString(),
			PortletMode.EDIT.toString(), PortletMode.HELP.toString() };

	private static final String DEFAULT = "default-";

	private PortletMode lastMode = PortletMode.VIEW;

	/**
	 * View states for a different portlet modes, Views and serial numbers.
	 */
	private Map states = new LRUMap();

	/**
	 * Private constructor - instance must be stored in the session, and created
	 * on nessesary by factory method.
	 * 
	 * @param context
	 */
	private PortletStateHolder() {
	}

	/**
	 * Get instance of portlet states, stored in the session. If no previsious
	 * instance in session, create default value.
	 * 
	 * @param context
	 * @return
	 */
	public static PortletStateHolder getInstance(FacesContext context) {
		ExternalContext externalContext = context.getExternalContext();
		Object session = externalContext.getSession(true);
		Map sessionMap = externalContext.getSessionMap();
		PortletStateHolder instance = null;
		synchronized (session) {
			instance = (PortletStateHolder) sessionMap.get(STATE_HOLDER);
			if (null == instance) {
				// Create and store in session new state holder.
				instance = new PortletStateHolder();
				Object request = externalContext.getRequest();
				if (request instanceof PortletRequest) {
					PortletRequest portletRequest = (PortletRequest) request;
					instance.setLastMode(portletRequest.getPortletMode());
				}
				sessionMap.put(STATE_HOLDER, instance);
			}
		}
		return instance;
	}

	/**
	 * Calculate viewId for a portlet content.
	 * 
	 * @param context
	 * @return
	 */
	public String getViewId(FacesContext context) {
		String viewId = null;
		// First - check for a existing viewId.
		if (null != context.getViewRoot()) {
			viewId = context.getViewRoot().getViewId();
		}
		// Second - find stored viewId for a current state and namespace.
		if (null == viewId) {
			viewId = getWindowState(context).getViewId();
		}
		// If none of above happen, try to get default viewId for a current
		// mode.
		if (null == viewId) {
			viewId = (String) context.getExternalContext().getRequestMap().get(Bridge.DEFAULT_VIEWID);
		}
		if (null == viewId) {
			throw new BridgeDefaultViewNotSpecifiedException("could'n determine portlet view id");
		}
		return viewId;
	}

	/**
	 * Return class contained saved JSF states for a current window.
	 * 
	 * @param context
	 * @return
	 */
	public PortletViewState getWindowState(FacesContext context) {
		PortletMode portletMode = AjaxFacesPortlet.getPortletMode(context);
		if (null == portletMode) {
			portletMode = lastMode;
		}
		StateKey windowKey = new StateKey(portletMode);
		PortletViewState state = (PortletViewState) states.get(windowKey);
		if (null == state) {
			state = new PortletViewState();
			states.put(windowKey, state);
		}
		return state;
	}

	/**
	 * @author asmirnov
	 * 
	 */
	private static class StateKey implements Serializable {
		/**
		 * 
		 */
		private static final long serialVersionUID = -1490104706512592990L;

		private PortletMode mode;

		private String namespace;
		
		

		public StateKey(PortletMode mode) {
			super();
			this.mode = mode;
		}

		/**
		 * @param mode
		 * @param namespace
		 */
		public StateKey(PortletMode mode, String namespace) {
			this.mode = mode;
			this.namespace = namespace;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		public int hashCode() {
			final int PRIME = 31;
			int result = 1;
			result = PRIME * result + ((mode == null) ? 0 : mode.hashCode());
			result = PRIME * result
					+ ((namespace == null) ? 0 : namespace.hashCode());
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			final StateKey other = (StateKey) obj;
			if (mode == null) {
				if (other.mode != null)
					return false;
			} else if (!mode.equals(other.mode))
				return false;
			if (namespace == null) {
				if (other.namespace != null)
					return false;
			} else if (!namespace.equals(other.namespace))
				return false;
			return true;
		}

	}

	/**
	 * @return the lastMode
	 */
	public PortletMode getLastMode() {
		return lastMode;
	}

	/**
	 * @param lastMode the lastMode to set
	 */
	public void setLastMode(PortletMode lastMode) {
		this.lastMode = lastMode;
	}
}
