package it.mice.voila.runtime.springmvc.taglibs;

import it.mice.voila.runtime.springmvc.bean.PageStatus;
import it.mice.voila.runtime.web.filter.SubmitButtonFilter;
import it.mice.voila.runtime.web.menu.MenuFilterManager;

import java.net.MalformedURLException;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.tags.form.InputTag;
import org.springframework.web.servlet.tags.form.TagWriter;


public class ButtonTag extends org.springframework.web.servlet.tags.form.ButtonTag {

	/**
	 * serial version UID
	 */
	private static final long serialVersionUID = 3232323232L;
	
	private String value = null;
	private String id = null;
	private String protectIfPageStatusIs = null;
	private String protectIfPageStatusIsNot = null;
    private String skipValidation = null;
    private String submitParameter = null;
    private String menuFilterManagerBeanName = "menuFilterManager";
    private String surroundWithTag = "";
    private boolean jumpHere = false;
    private String alt = null;
    private String type = null;
    
	private TagWriter tagWriter;

    /**
     * Possible values are:
     * 1) leave;
     * 2) hide;
     * 3) disable (default).
     */
    private String ifNotAuthorized = "hide";

	@Override
	public String getType() {
		return type;
	}

    public void setType(String type) {
		this.type = type;
	}

	/**
	 * Set the value of the '<code>alt</code>' attribute.
	 * May be a runtime expression.
	 * @param alt the alt.
	 */
	public void setAlt(String alt) {
		this.alt = alt;
	}

	/**
	 * Get the value of the '<code>alt</code>' attribute.
	 * @return the result.
	 */
	protected String getAlt() {
		return this.alt;
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}

	public String getIfNotAuthorized() {
		return ifNotAuthorized;
	}

	public void setIfNotAuthorized(String ifNoAuthorized) {
		this.ifNotAuthorized = ifNoAuthorized;
	}

	public String getSubmitParameter() {
		return submitParameter;
	}

	public void setSubmitParameter(String submitParameter) {
		this.submitParameter = submitParameter;
	}

	public String getMenuFilterManagerBeanName() {
		return menuFilterManagerBeanName;
	}

	public void setMenuFilterManagerBeanName(String menuFilterManagerBeanName) {
		this.menuFilterManagerBeanName = menuFilterManagerBeanName;
	}

	public String getSkipValidation() {
		return skipValidation;
	}

	public void setSkipValidation(String skipValidation) {
		this.skipValidation = skipValidation;
	}

	private boolean isSkipValidation() throws JspException {
		if (getSkipValidation() == null) {
			return isRenderAsLink();
		}
		return evaluateBoolean("skipValidation", getSkipValidation());
	}

	private boolean isRenderAsLink() throws JspException {
		return "link".equals(getType());
	}

	public String getSurroundWithTag() {
		return surroundWithTag;
	}

	public void setSurroundWithTag(String surroundWithTag) {
		this.surroundWithTag = surroundWithTag;
	}

	public String getProtectIfPageStatusIs() {
		return protectIfPageStatusIs;
	}

	public void setProtectIfPageStatusIs(String protectIfPageStatusIs) {
		this.protectIfPageStatusIs = protectIfPageStatusIs;
	}

	public String getProtectIfPageStatusIsNot() {
		return protectIfPageStatusIsNot;
	}

	public void setProtectIfPageStatusIsNot(String protectIfPageStatusIsNot) {
		this.protectIfPageStatusIsNot = protectIfPageStatusIsNot;
	}

	/**
	 * @return the jumpHere
	 */
	public boolean isJumpHere() {
		return jumpHere;
	}

	/**
	 * @param jumpHere the jumpHere to set
	 */
	public void setJumpHere(boolean jumpHere) {
		this.jumpHere = jumpHere;
	}

	@Override
	protected int writeTagContent(TagWriter tagWriter) throws JspException {
		if (isJumpHere()) {
			String jumpHereParm = "jumpTo=" + resolveId();
			if (getSubmitParameter() == null) {
				setSubmitParameter(jumpHereParm);
			} else {
				setSubmitParameter(getSubmitParameter() + "&amp;" + jumpHereParm);
			}
		}
		
        String finalUrl = null;
		try {
			finalUrl = buildFinalUrl();
		} catch (Exception e) {
			logger.error("Error calculating SubmitTag final URI. Skipping authorization check...", e);
		}
        boolean isAuthorizationDenied = false;

		if (!ifNotAuthorized.equals("leave") && finalUrl != null) {
	        MenuFilterManager menuFilterManager =
            	(MenuFilterManager)WebApplicationContextUtils
            	.getRequiredWebApplicationContext(
            			this.pageContext.getServletContext())
            			.getBean(getMenuFilterManagerBeanName());
		    isAuthorizationDenied = menuFilterManager.disableMenuItem(finalUrl);
		}
		
		if (isAuthorizationDenied) {
		    if (ifNotAuthorized.equals("hide")) {
		    	return SKIP_BODY;
		    } else {
		        protectField();
		    }
		} else {
			PageStatus pageStatus = TagUtils.getPageStatus(getNestedPath());
		    if (protectIfPageStatusIs != null && pageStatus.equals(protectIfPageStatusIs)) {
		        protectField();
		    } else if (protectIfPageStatusIsNot != null && !pageStatus.equals(protectIfPageStatusIsNot)) {
		        protectField();
		    }
		}
        
        if (!StringUtils.isEmpty(getSurroundWithTag())) {
        	tagWriter.startTag(getSurroundWithTag());
        	tagWriter.writeAttribute("class", isRenderAsLink() ? "link" : "submit");
        }
        
        if (isRenderAsLink()) {
        	renderAsLink(tagWriter, finalUrl);
        } else {
        	super.writeTagContent(tagWriter);
        }

		tagWriter.forceBlock();
		this.tagWriter = tagWriter;
		return EVAL_BODY_INCLUDE;
	}

	/**
	 * Closes the '{@code button}' block tag.
	 */
	@Override
	public int doEndTag() throws JspException {
		if (!StringUtils.isEmpty(getSurroundWithTag())) {
        	tagWriter.endTag();
        }

		tagWriter.endTag();
		return EVAL_PAGE;
	}

	private String buildFinalUrl() throws JspException {
		String finalUrl = null;
    	String baseUri = buildRequestUri();
    	URL baseURL = buildBaseURL();
		HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); 
        FormTag formTag = (FormTag)pageContext.getAttribute(FormTag.FORM_BEAN_NAME, PageContext.REQUEST_SCOPE);
        if (isRenderAsLink()) {
            if (getPath().startsWith("/")) {
            	finalUrl = getPath();
            } else {
				if (formTag != null) {
					finalUrl = formTag.getActionUrl() + getPath();
					if (finalUrl.startsWith(request.getContextPath())) {
						finalUrl = finalUrl.substring(request.getContextPath().length());
					}
				} else {
	            	URL currentUrl = buildURL(baseURL, baseUri);
	            	currentUrl = buildURL(currentUrl, getPath());
	            	finalUrl = currentUrl.toExternalForm().substring(baseURL.toExternalForm().length() - 1);
				}
            }
        } else {
            String methodName = "";
            int startMethodName = getPath().lastIndexOf(SubmitButtonFilter.BUTTON_SUBMIT_PREFIX);
            if (startMethodName > -1) {
                methodName = getPath().substring(startMethodName + SubmitButtonFilter.BUTTON_SUBMIT_PREFIX.length());
            }
            if (formTag != null) {
                String formAction = formTag.getActionUrl();
                if (formAction.startsWith("/")) {
                	//URL currentUrl = buildURL(baseURL, getRequestContext().getContextUrl(formAction));
                	URL currentUrl = buildURL(baseURL, formAction);
                	currentUrl = buildURL(currentUrl, methodName);
                	finalUrl = currentUrl.toExternalForm().substring(baseURL.toExternalForm().length() - 1);
                } else {
                	URL currentUrl = buildURL(baseURL, baseUri);
                	currentUrl = buildURL(currentUrl, formAction);
                	currentUrl = buildURL(currentUrl, methodName);
                	finalUrl = currentUrl.toExternalForm().substring(baseURL.toExternalForm().length() - 1);
                }
            }
        }
        finalUrl = appendSubmitParameters(finalUrl);
        
        if (logger.isDebugEnabled()) {
            logger.debug("SubmitTag calculated url associated to the button and to be checked about security autorization is: " + finalUrl);
        }
		return finalUrl;
	}

	/**
	 * @param finalUrl
	 * @return
	 */
	private String appendSubmitParameters(String finalUrl) {
		if (!StringUtils.isEmpty(getSubmitParameter())) {
        	String prefix = (finalUrl.indexOf("?") > -1 ? "&amp;" : "?");
        	finalUrl = finalUrl + prefix + getSubmitParameter();
        }
		return finalUrl;
	}

	private URL buildBaseURL() throws JspException {
		HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); 
		String baseURL = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
		return buildURL(null, baseURL);
	}

	private String buildRequestUri() throws JspException {
		String baseURL = getRequestContext().getRequestUri();
		return baseURL;
	}

	private URL buildURL(URL base, String relativePath) throws JspException {
		try {
			return new URL(base, relativePath);
		} catch (MalformedURLException e) {
			throw new JspException(e);
		}
	}

	private void renderAsLink(TagWriter tagWriter, String finalUrl) throws JspException {
		tagWriter.startTag("a");
		if (isDisabled()) {
			tagWriter.writeAttribute("href", "#");
		} else {
			HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); 
			tagWriter.writeAttribute("href", request.getContextPath() + finalUrl);
		}
		writeOptionalAttributes(tagWriter);

		// custom optional attributes
		writeOptionalAttribute(tagWriter, InputTag.ALT_ATTRIBUTE, getAlt());

		// custom optional attributes
		writeOptionalAttribute(tagWriter, "id", getId());
		
		tagWriter.appendValue(TagUtils.getMessage(getRequestContext(), evaluate("value", getValue())));
	}

	@Override
	protected void writeValue(TagWriter tagWriter) throws JspException {
		if (isRenderAsLink()) {
			return;
		}
		tagWriter.writeAttribute("value", TagUtils.getMessage(getRequestContext(), evaluate("value", getValue())));
	}

	@Override
	public String getTitle() {
		String title = super.getTitle();
		if (title != null) {
			return TagUtils.getMessage(getRequestContext(), title);
		} else {
			return null;
		}
	}

	/**
	 * Gets the appropriate CSS class to use based on the state of the current
	 * {@link org.springframework.web.servlet.support.BindStatus} object.
	 */
	protected String resolveCssClass() throws JspException {
		String cssAddOn = "button " + (isRenderAsLink() ? "buttonLink " : "buttonSubmit ");
		if (getCssClass() != null) {
			return cssAddOn + getCssClass();
		}
		return cssAddOn;
	}

	public String getName() {
		try {
			if (!isRenderAsLink()) {
				return appendSubmitParameters(getPath());
			}
			return getPath();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void protectField() throws JspException {
		setDisabled("true");
	}

	/**
	 * Determine the '<code>id</code>' attribute value for this tag,
	 * autogenerating one if none specified.
	 * @see #getId()
	 * @see #autogenerateId()
	 */
	@Override
	protected String resolveId() throws JspException {
		if (id == null) {
			id = super.resolveId();
			id = TagUtils.normalizeId(id);
		}
		return id;
	}

	/**
	 * Autogenerate the '<code>id</code>' attribute value for this tag.
	 * <p>The default implementation simply delegates to {@link #getName()},
	 * deleting invalid characters (such as "[" or "]").
	 */
	protected String autogenerateId() throws JspException {
		String result = super.autogenerateId();
		int i = result.indexOf("?");
		if (i > -1) {
			return result.substring(0, i);
		}
		return result;
	}

	@Override
	public void doFinally() {
		setDisabled("false");
		setTabindex(null);
		setProtectIfPageStatusIs(null);
		setProtectIfPageStatusIsNot(null);
		setSkipValidation(null);
		setSubmitParameter(null);
		setMenuFilterManagerBeanName("menuFilterManager");
		setSurroundWithTag("");
		setOnclick(null);
		setAlt(null);
		id = null;
		jumpHere = false;

		super.doFinally();
	}
}
