/**
 * Copyright 2005-2010 hdiv.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.hdiv.dataComposer;

import java.net.URLDecoder;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.config.HDIVConfig;
import org.hdiv.idGenerator.UidGenerator;
import org.hdiv.state.IPage;
import org.hdiv.state.IParameter;
import org.hdiv.state.IState;
import org.hdiv.state.Page;
import org.hdiv.state.Parameter;
import org.hdiv.state.State;

/**
 * <p>
 * It generates the states of each page by storing them in the user session. To be
 * able to associate the request state with the state stored in session, an extra
 * parameter is added to each request, containing the state identifier which makes
 * possible to get the state of the user session.
 * </p>
 * <p>
 * Non editable values are hidden to the client, guaranteeing <b>confidentiality</b>
 * </p>
 * 
 * @see org.hdiv.dataComposer.AbstractDataComposer
 * @see org.hdiv.composer.IDataComposer
 * @author Roberto Velasco
 */
public class DataComposerMemory extends AbstractDataComposer implements IDataComposer {

	/**
	 * Commons Logging instance.
	 */
	private static Log log = LogFactory.getLog(DataComposerMemory.class);

	/**
	 * Dash character
	 */
	protected static final String DASH = "-";

	/**
	 * Page with the possible requests or states
	 */
	protected IPage page;

	/**
	 * State that represents all the data of a request or a form existing in a page
	 * <code>page</code>
	 */
	protected IState state;

	/**
	 * Last parameter treated by the compose method
	 */
	protected IParameter lastParameter;

	/**
	 * Represents the identifier of each possible state stored in the page
	 * <code>page</code>.
	 */
	protected int requestCounter = 0;

	/**
	 * States stack to store all states of the page <code>page</code>
	 */
	protected Stack statesStack;

	/**
	 * HDIV configuration object.
	 */
	protected HDIVConfig hdivConfig;
	
	/**
	 * Unique id generator
	 */
	protected UidGenerator uidGenerator;


	/**
	 * DataComposerMemory initialization with HTTP session wrapper and new stack to
	 * store all states of the page <code>page</code>.
	 */
	public void init() {
		this.page = new Page();
		this.statesStack = new Stack();
	}

	/**
	 * It generates a new encoded value for the parameter <code>parameter</code>
	 * and the value <code>value</code> passed as parameters. The returned value
	 * guarantees the confidentiality in the encoded and memory strategies if
	 * confidentiality indicator <code>confidentiality</code> is true.
	 * 
	 * @param parameter HTTP parameter name
	 * @param value value generated by server
	 * @param editable parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @return Codified value to send to the client
	 */
	public String compose(String parameter, String value, boolean editable) {
		return this.compose(parameter, value, editable, false);
	}

	/**
	 * It generates a new encoded value for the parameter <code>parameter</code>
	 * and the value <code>value</code> passed as parameters. The returned value
	 * guarantees the confidentiality in the encoded and memory strategies if
	 * confidentiality indicator <code>confidentiality</code> is true.
	 * 
	 * @param action target action
	 * @param parameter HTTP parameter name
	 * @param value value generated by server
	 * @param editable parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @return Codified value to send to the client
	 */
	public String compose(String action, String parameter, String value, boolean editable) {

		return this.compose(action, parameter, value, editable, false, "UTF-8");
	}

	/**
	 * Adds a new IParameter object, generated from the values passed as parameters,
	 * to the current state <code>state</code>. If confidentiality is activated it
	 * generates a new encoded value that will be returned by the server for the
	 * parameter <code>parameter</code> in the encoded and memory strategies.
	 * 
	 * @param parameter HTTP parameter
	 * @param value value generated by server
	 * @param editable Parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @param isActionParam parameter added in action attribute
	 * @return Codified value to send to the client
	 */
	public String compose(String parameter, String value, boolean editable, boolean isActionParam) {

		return this.compose(parameter, value, editable, isActionParam, "UTF-8");
	}

	/**
	 * It generates a new encoded value for the parameter <code>parameter</code>
	 * and the value <code>value</code> passed as parameters. The returned value
	 * guarantees the confidentiality in the encoded and memory strategies if
	 * confidentiality indicator <code>confidentiality</code> is true.
	 * 
	 * @param parameter HTTP parameter name
	 * @param value value generated by server
	 * @param editable parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @param editableName editable name (text or textarea)
	 * @return Codified value to send to the client
	 * @since HDIV 1.1
	 */
	public String compose(String parameter, String value, boolean editable, String editableName) {

		return this.compose(parameter, value, editable, editableName, false, "UTF-8");
	}

	/**
	 * It generates a new encoded value for the parameter <code>parameter</code>
	 * and the value <code>value</code> passed as parameters. The returned value
	 * guarantees the confidentiality in the encoded and memory strategies if
	 * confidentiality indicator <code>confidentiality</code> is true.
	 * 
	 * @param action target action
	 * @param parameter parameter name
	 * @param value value generated by server
	 * @param editable parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select,...)
	 * @param isActionParam parameter added in action attribute
	 * @param charEncoding character encoding
	 * @return Codified value to send to the client
	 */
	public String compose(String action, String parameter, String value, boolean editable,
			boolean isActionParam, String charEncoding) {

		this.setAction(action);
		return this.compose(parameter, value, editable, isActionParam, charEncoding);
	}

	/**
	 * Adds a new IParameter object, generated from the values passed as parameters,
	 * to the current state <code>state</code>. If confidentiality is activated it
	 * generates a new encoded value that will be returned by the server for the
	 * parameter <code>parameter</code> in the encoded and memory strategies.
	 * 
	 * @param parameter HTTP parameter
	 * @param value value generated by server
	 * @param editable Parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @param isActionParam parameter added in action attribute
	 * @param charEncoding character encoding
	 * @return Codified value to send to the client
	 */
	public String compose(String parameter, String value, boolean editable, boolean isActionParam,
			String charEncoding) {

		return this.compose(parameter, value, editable, null, isActionParam, charEncoding);
	}

	/**
	 * Adds a new IParameter object, generated from the values passed as parameters,
	 * to the current state <code>state</code>. If confidentiality is activated it
	 * generates a new encoded value that will be returned by the server for the
	 * parameter <code>parameter</code> in the encoded and memory strategies.
	 * 
	 * @param parameter HTTP parameter
	 * @param value value generated by server
	 * @param editable Parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @param editableName editable name (text or textarea)
	 * @param isActionParam parameter added in action attribute
	 * @param charEncoding character encoding
	 * @return Codified value to send to the client
	 * @since HDIV 1.1
	 */
	public String compose(String parameter, String value, boolean editable, String editableName,
			boolean isActionParam, String charEncoding) {

		this.composeParameter(parameter, value, editable, editableName, isActionParam, charEncoding);
		
		if (this.hdivConfig.isStartParameter(parameter)) {
			return value;
		}
		
		if (this.isUserDefinedNonValidationParameter(parameter)) {
			return value;
		}
		
		if (Boolean.FALSE.equals(this.hdivConfig.getConfidentiality())) {
			return value;
		}

		if (this.getAction() != null) {
			if (this.hdivConfig.isStartPage(this.getAction())) {
				return value;
			}
		}

		return (this.lastParameter.getCount() - 1) + "";
	}
	
	/**
	 * Checks if the parameter <code>parameter</code> is defined by the user
	 * as a no required validation parameter for the action
	 * <code>this.target</code>.
	 * 
	 * @param parameter parameter name
	 * @return True If it is parameter that needs no validation. False
	 *         otherwise.
	 * @since HDIV 2.0.6
	 */
	private boolean isUserDefinedNonValidationParameter(String parameter) {
		
		String actionWithoutContextPath = this.getAction();
		if (actionWithoutContextPath.startsWith("/")) {
			int secondSlash = actionWithoutContextPath.indexOf("/", 1);
			if (secondSlash > 0) {
				actionWithoutContextPath = actionWithoutContextPath.substring(secondSlash);	
			}			
		}

		if (this.hdivConfig.isParameterWithoutValidation(actionWithoutContextPath, parameter)) {

			if (log.isDebugEnabled()) {
				log.debug("parameter " + parameter + " doesn't need validation. It is user defined parameter.");
			}
			return true;
		}		
		return false;
	}	

	/**
	 * Adds a new IParameter object, generated from the values passed as parameters,
	 * to the current state <code>state</code>.
	 * 
	 * @param parameter HTTP parameter
	 * @param value value generated by server
	 * @param editable Parameter type: editable(textbox, password,etc.) or non
	 *            editable (hidden, select, radio, ...)
	 * @param editableName editable parameter name (text or textarea)
	 * @param isActionParam parameter added in action attribute
	 * @param charEncoding character encoding
	 * @return Codified value to send to the client
	 * @since HDIV 1.1
	 */
	private void composeParameter(String parameter, String value, boolean editable,
			String editableName, boolean isActionParam, String charEncoding) {

		// we decoded value before store it in state.
		String decodedValue = this.getDecodedValue(value, charEncoding);
		
		if (this.state.existParameter(parameter)) {

			if ((this.lastParameter == null) || (!this.lastParameter.getName().equals(parameter))) {
				this.lastParameter = this.state.getParameter(parameter);
			}

			// add a new value to the parameter
			this.lastParameter.addValue(decodedValue);

		} else {
			// create a new parameter and add to the request
			this.lastParameter = new Parameter();

			this.lastParameter.addValue(decodedValue);
			this.lastParameter.setName(parameter);
			this.lastParameter.setEditable(editable);
			this.lastParameter.setEditableDataType(editableName);

			this.lastParameter.setActionParam(isActionParam);

			this.state.addParameter(parameter, lastParameter);
		}
	}

	/**
	 * Creates a new parameter called <code>newParameter</code> and adds all the
	 * values of <code>oldParameter</code> stored in the state to it.
	 * 
	 * @param oldParameter name of the parameter stored in the state
	 * @param newParameter name of the new parameter
	 */
	public void mergeParameters(String oldParameter, String newParameter) {

		IParameter storedParameter = this.state.getParameter(oldParameter);

		if (storedParameter.getValues().size() > 0) {

			this.composeParameter(newParameter, storedParameter.getValuePosition(0), false, "",	false, "UTF-8");

			String currentValue = null;
			// We check the parameters since the second position because the first 
			// value has been used to create the parameter
			for (int i = 1; i < storedParameter.getValues().size(); i++) {

				currentValue = storedParameter.getValuePosition(i);
				this.lastParameter.addValue(currentValue);
			}
		}
	}

	/**
	 * Decoded <code>value</code> using input <code>charEncoding</code>.
	 * 
	 * @param value value to decode
	 * @param charEncoding character encoding
	 * @return value decoded
	 */
	private String getDecodedValue(String value, String charEncoding) {

		String decodedValue = null;
		try {
			decodedValue = URLDecoder.decode(value, charEncoding);
		} catch (Exception e) {
			decodedValue = value;
		}

		return (decodedValue == null) ? "" : decodedValue;
	}

	/**
	 * It is called by each request or form existing in the page returned by the
	 * server. It creates a new state to store all the parameters and values of the
	 * request or form.
	 */
	public void beginRequest() {

		this.state = new State();
		this.state.setAction(this.getAction());

		String currentRequestCounter = String.valueOf(requestCounter);
		this.state.setId(currentRequestCounter);
		this.statesStack.push(this.state);

		requestCounter++;
		this.lastParameter = null;
	}

	/**
	 * It is called in the pre-processing stage of each request or form existing in
	 * the page returned by the server, as long as the destiny of the request is an
	 * action. It creates a new state to store all the parameters and values of the
	 * request or form.
	 * 
	 * @param action action name
	 * @see org.hdiv.dataComposer.DataComposerMemory#beginRequest()
	 */
	public void beginRequest(String action) {

		this.setAction(action);
		this.beginRequest();
	}

	/**
	 * It is called in the pre-processing stage of each request or form existing in
	 * the page returned by the server. It adds the state of the treated request or
	 * form to the page <code>page</code> and returns and identifier compoded by
	 * the page identifier and the state identifier.
	 * 
	 * @return Identifier composed by the page identifier and the state identifier.
	 */
	public String endRequest() {

		this.state = (IState) this.statesStack.pop();

		this.state.setPageId(this.getPage().getName());
		this.page.addState(this.state);

		String id = this.getPage().getName() + DASH + this.state.getId() + DASH
				+ this.getHdivStateSuffix();

		this.updateComposerState();

		return id;
	}

	/**
	 * Obtains the suffix to add to the _HDIV_STATE_ parameter in the memory version.
	 * 
	 * @return Returns suffix added to the _HDIV_STATE_ parameter in the memory
	 *         version.
	 * @since HDIV 1.1
	 */
	public String getHdivStateSuffix() {
		return this.page.getRandomToken();
	}

	/**
	 * Updates the state <code>state</code> of <code>this</code> with the state
	 * in the first position of the state stack. If the state stack is empty it does
	 * nothing.
	 */
	public void updateComposerState() {

		if (this.statesStack.size() > 0) {
			this.state = (IState) this.statesStack.lastElement();
		}
	}

	/**
	 * It is called in the pre-processing stage of each user request assigning a new
	 * page identifier to the page.
	 */
	public void startPage() {

		if (this.page.getName() == null) {
			this.initPageId();
			this.page.setName(this.getPageId());
			this.page.setRandomToken(this.uidGenerator.generateUid().toString());
		}
	}	

	/**
	 * This method is called in the pre-processing stage of each user request to add
	 * an IPage object, which represents the page to show by the server, with all its
	 * states to the user session.
	 */
	public void endPage() {
		
		if(this.page.getStates().size() > 0){
			this.getSession().addPage(this.getPageId(), this.page);
		}else{
			log.debug("The page ["+this.page.getName()+"] has no states, is not stored in session");
		}
		
	}	

	/**
	 * @return IPage which represents the page in memory.
	 */
	public IPage getPage() {
		return this.page;
	}

	/**
	 * @param page The page to set.
	 */
	public void setPage(IPage page) {
		this.page = page;
	}

	/**
	 * @param hdivConfig The HDIV configuration object to set.
	 */
	public void setHdivConfig(HDIVConfig hdivConfig) {
		this.hdivConfig = hdivConfig;
	}

	/**
	 * Adds the flow identifier to the page of type <code>IPage</code>.
	 * @since HDIV 2.0.3
	 */
	public void addFlowId(String id) {
		this.page.setFlowId(id);
	}

	public void setUidGenerator(UidGenerator uidGenerator) {
		this.uidGenerator = uidGenerator;
	}	
	
}