package core.wicket.ui.button;

import javax.servlet.ServletRequest;

import wicket.Component;
import wicket.markup.ComponentTag;
import wicket.markup.html.form.AbstractButton;
import wicket.markup.html.form.Button;
import wicket.protocol.http.WebRequest;

/**
 * This is the base class for any html element that wants to submit the form via
 * javascript. The javascript strategy works by adding a hidden placeholder
 * field to the form, the onclick handler in the button element will then rename
 * this placeholder to the button's path attribute and submit the form thereby
 * "faking" the variable that would've been added by a regular <input
 * type="submit" ... /> tag.
 * 
 * A button can be marked as default through its constructor. This button will
 * be used if the form has been submitted through the enter key. Default buttons
 * will be ignored if the form also contains any non-javascript buttons. A
 * RuntimeException will be thrown if more then one button is designated as
 * default.
 * 
 * @author ivaynberg@privesec.com
 * 
 */
public abstract class AbstractJSButton extends AbstractButton {
	/**
	 * The name of the hidden placeholder element
	 */
	public static final String PLACEHOLDER_NAME = "jsbuttonplaceholder";

	/**
	 * The default button flag
	 */
	private boolean defaultButton = false;

	/**
	 * @see Button#Button(String)
	 */
	public AbstractJSButton(String id) {
		super(id);
	}

	/**
	 * @see Button#Button(String)
	 * @param defaultButton -
	 *            the default button flag
	 */
	public AbstractJSButton(String id, boolean defaultButton) {
		super(id);
		this.defaultButton = defaultButton;
	}

	/**
	 * @see Button#onComponentTag(ComponentTag)
	 */
	@Override
	protected void onComponentTag(ComponentTag tag) {
		if (tag.getAttributes().containsKey("onclick")) {
			findMarkupStream().throwMarkupException(
					"Component " + getId() + " cannot be applied to a tag "
							+ "with a an existing 'onclick' attribute");
		}

		ServletRequest request = getServletRequest();

		/*
		 * add the placeholder variable ONCE per form using ServletRequest
		 * attributes to keep track
		 */
		String key = getPlaceholderKey();

		if (request.getAttribute(key) == null) {

			/*
			 * we need to figure out what the default button is, if any, and
			 * assign that as the initial name of the hidden placeholder form
			 * field. however, if there are any buttons other then javascript
			 * buttons we do not use the default
			 */

			/*
			 * a placeholder for visitor:
			 * 
			 * flags[0] - null if no non-javascript buttons, Boolean.TRUE if
			 * non-javascript buttons found.
			 * 
			 * flags[1] - default javascript button component, null if none
			 * found
			 */
			final Object[] flags = new Object[2];

			getForm().visitChildren(Button.class, new Component.IVisitor() {

				public Object component(Component component) {
					if (!AbstractJSButton.class.isAssignableFrom(component
							.getClass())) {
						flags[0] = Boolean.TRUE;
						return STOP_TRAVERSAL;
					} else {
						if (flags[1] != null) {
							throw new RuntimeException(
									"form ["
											+ getForm().getId()
											+ "] has more then one default javascript button: ["
											+ ((Component) flags[1]).getId()
											+ "] and [" + component.getId()
											+ "]");
						}
						if (((AbstractJSButton) component).defaultButton == true) {
							flags[1] = component;
						}
					}
					return CONTINUE_TRAVERSAL;
				}

			});

			// figure out the initial name of the placeholder variable
			String placeholderName = PLACEHOLDER_NAME;
			if (flags[0] != Boolean.TRUE) {
				if (flags[1] != null) {
					placeholderName = ((Component) flags[1]).getPath();
				} else {
					placeholderName = getPath(); // if no other buttons and
					// no default - default to this
				}

			}

			getResponse().write(
					new StringBuffer(64).append(
							"<input type=\"hidden\" name=\"").append(
							placeholderName).append("\" value=\"x\"/>")
							.toString());

			request.setAttribute(key, placeholderName);
		}

		// Default processing
		super.onComponentTag(tag);

		// remove the name key set by FormComponent.onComponentTag()
		tag.remove("name");

	}

	/**
	 * @see Button#getOnClickScript()
	 */
	@Override
	protected String getOnClickScript() {
		final String placeholderVar = (String) getServletRequest()
				.getAttribute(getPlaceholderKey());
		return new StringBuffer(64).append("this.form['")
				.append(placeholderVar).append("'].name='").append(getPath())
				.append("';this.form.submit();").toString();
	}

	/**
	 * @return the placeholder variable key unique to this form
	 */
	private String getPlaceholderKey() {
		return getForm().getId() + PLACEHOLDER_NAME;
	}

	/**
	 * Retrieves the ServletRequest object from the current RequestCycle
	 * 
	 * @return ServletRequest
	 */
	private ServletRequest getServletRequest() {
		return ((WebRequest) getRequestCycle().getRequest())
				.getHttpServletRequest();
	}

}
