/*
 * This file is part of Nuts Framework.
 * Copyright (C) 2009 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 *
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.struts2.components;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Calendar;
import java.util.Date;

import nuts.core.lang.StringEscapeUtils;
import nuts.core.lang.StringUtils;
import nuts.exts.xwork2.util.ContextUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.components.ContextBean;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.util.ValueStack;

/**
 * <!-- START SNIPPET: javadoc -->
 *
 * Used to get the property of a <i>value</i>, which will default to the top of
 * the stack if none is specified.
 *
 * <!-- END SNIPPET: javadoc -->
 *
 * <p/>
 *
 *
 * <!-- START SNIPPET: params -->
 *
 * <ul>
 * <li>default (String) - The default value to be used if <u>value</u> attribute
 * is null</li>
 * <li>escape (Boolean) - Escape HTML. Default to true</li>
 * <li>value (Object) - value to be displayed</li>
 * </ul>
 *
 * <!-- END SNIPPET: params -->
 *
 *
 * <pre>
 * &lt;!-- START SNIPPET: example --&gt;
 *
 * &lt;s:push value=&quot;myBean&quot;&gt;
 *     &lt;!-- Example 1: --&gt;
 *     &lt;s:property value=&quot;myBeanProperty&quot; /&gt;
 *
 *     &lt;!-- Example 2: --&gt;TextUtils
 *     &lt;s:property value=&quot;myBeanProperty&quot; default=&quot;a default value&quot; /&gt;
 * &lt;/s:push&gt;
 *
 * &lt;!-- END SNIPPET: example --&gt;
 * </pre>
 *
 * <pre>
 * &lt;!-- START SNIPPET: exampledescription --&gt;
 *
 * Example 1 prints the result of myBean's getMyBeanProperty() method.
 * Example 2 prints the result of myBean's getMyBeanProperty() method and if it is null, print 'a default value' instead.
 *
 * &lt;!-- END SNIPPET: exampledescription --&gt;
 * </pre>
 *
 *
 * <pre>
 * &lt;!-- START SNIPPET: i18nExample --&gt;
 *
 * &lt;s:property value=&quot;getText('some.key')&quot; /&gt;
 *
 * &lt;!-- END SNIPPET: i18nExample --&gt;
 * </pre>
 *
 */
@StrutsTag(
		name = "property",
		tldBodyContent = "empty",
		tldTagClass = "nuts.exts.struts2.views.jsp.PropertyTag",
		description = "Print out expression which evaluates against the stack")
public class Property extends ContextBean {
	private static final Log log = LogFactory.getLog(Property.class);

	/**
	 * Constructor
	 * 
	 * @param stack value stack
	 */
	public Property(ValueStack stack) {
		super(stack);
	}

	/**
	 * PASSWORD_FORMAT = "password-format";
	 */
	public static final String PASSWORD_FORMAT = "password-format";

	/**
	 * ESCAPE_HTML = "html";
	 */
	public static final String ESCAPE_HTML = "html";
	
	/**
	 * ESCAPE_PHTML = "phtml";
	 */
	public static final String ESCAPE_PHTML = "phtml";
	
	/**
	 * ESCAPE_JAVASCRIPT = "js";
	 */
	public static final String ESCAPE_JAVASCRIPT = "js";
	
	/**
	 * ESCAPE_CSV = "csv";
	 */
	public static final String ESCAPE_CSV = "csv";
	
	/**
	 * ESCAPE_XML = "xml";
	 */
	public static final String ESCAPE_XML = "xml";
	
	/**
	 * DEFAULT_PASSWORD_FORMAT = "******";
	 */
	public static final String DEFAULT_PASSWORD_FORMAT = "******";

	private String defaultValue;
	private String name;
	private String format;
	private Object value;
	private String escape = ESCAPE_HTML;

	/**
	 * @see org.apache.struts2.components.Component#start(java.io.Writer)
	 */
	public boolean start(Writer writer) {
		boolean result = super.start(writer);

		String actualValue = formatValue();

		try {
			if (actualValue == null) {
				actualValue = defaultValue;
			}

			if (actualValue != null) {
				write(writer, actualValue);
			}
		}
		catch (IOException e) {
			log.warn("Could not print out value '" + actualValue + "'", e);
		}

		return result;
	}

	public String formatValue() {
		String actualValue = null;
		
		if (name == null) {
			name = "top";
		}
		else {
			name = stripExpressionIfAltSyntax(name);
		}

		if (format == null) {
			actualValue = prepare(getStringValue());
		}
		else {
			Object av = value != null ? value : getStack().findValue(name);

			if (av == null) {
			}
			else if ("password".equalsIgnoreCase(format)) {
				TextProvider tp = ContextUtils.findTextProviderInStack(getStack());
				actualValue = tp.getText(PASSWORD_FORMAT, DEFAULT_PASSWORD_FORMAT);
			}
			else if ("link".equalsIgnoreCase(format)) {
				String s = StringEscapeUtils.escapeHtml(av.toString());
				if (StringUtils.isNotEmpty(s)) {
					actualValue = "<a href=\"" + s + "\">" + s + "</a>";
				}
			}
			else if ("extlink".equalsIgnoreCase(format)) {
				String s = StringEscapeUtils.escapeHtml(av.toString());
				if (StringUtils.isNotEmpty(s)) {
					actualValue = "<a target=\"_blank\" href=\"" + s + "\">" + s + "</a>";
				}
			}
			else {
				if (av instanceof Boolean) {
					StringWriter sw = new StringWriter();
					CBoolean b = new CBoolean(getStack());
					b.setValue(av);
					b.setFormat(format);
					b.start(sw);
					b.end(sw, "");
					actualValue = sw.toString();
				}
				else if (av instanceof Date || av instanceof Calendar) {
					StringWriter sw = new StringWriter();
					CDate d = new CDate(getStack());
					d.setValue(av);
					d.setFormat(format);
					d.start(sw);
					d.end(sw, "");
					actualValue = sw.toString();
				}
				else if (av instanceof Number) {
					StringWriter sw = new StringWriter();
					CNumber n = new CNumber(getStack());
					n.setValue(av);
					n.setFormat(format);
					n.start(sw);
					n.end(sw, "");
					actualValue = sw.toString();
				}
				else if (av instanceof String) {
					actualValue = prepare((String)av);
				}
				else {
					actualValue = prepare(getStringValue());
				}
			}
		}
		return actualValue;
	}
	
	private String getStringValue() {
		String av;
		// exception: don't call findString(), since we don't want the
		// expression parsed in this one case. it really
		// doesn't make sense, in fact.
		if (value != null) {
			getStack().push(value);
			try {
				av = (String)getStack().findValue("top", String.class);
			}
			finally {
				getStack().pop();
			}
		}
		else {
			av = (String)getStack().findValue(name, String.class);
		}
		return av;
	}

	private void write(Writer writer, String value) throws IOException {
		if (getVar() == null) {
			writer.write(value);
		}
		else {
			putInContext(value);
		}
	}

	private String prepare(String value) {
		String result = value;
		if (value != null) {
			if (ESCAPE_HTML.equals(escape)) {
				result = StringEscapeUtils.escapeHtml(result);
			}
			else if (ESCAPE_PHTML.equals(escape)) {
				result = StringEscapeUtils.escapePhtml(result);
			}
			else if (ESCAPE_JAVASCRIPT.equals(escape)) {
				result = StringEscapeUtils.escapeJavaScript(result);
			}
			else if (ESCAPE_CSV.equals(escape)) {
				result = StringEscapeUtils.escapeCsv(result);
			}
			else if (ESCAPE_XML.equals(escape)) {
				result = StringEscapeUtils.escapeXml(result);
			}
		}
		return result;
	}

	/**
	 * @param defaultValue the defaultValue to set
	 */
	@StrutsTagAttribute(description = "The default value to be used if <u>value</u> attribute is null")
	public void setDefault(String defaultValue) {
		this.defaultValue = defaultValue;
	}

	/**
	 * @param escape the escape to set
	 */
	@StrutsTagAttribute(description = " Whether to escape HTML", defaultValue = "html")
	public void setEscape(String escape) {
		this.escape = escape;
	}

	/**
	 * @param value the value to set
	 */
	@StrutsTagAttribute(description = "Value to be displayed", type = "Object")
	public void setValue(Object value) {
		this.value = value;
	}

	/**
	 * @param name the name to set
	 */
	@StrutsTagAttribute(description = "Name of value to be displayed", type = "String", defaultValue = "&lt;top of stack&gt;")
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @param format the format to set
	 */
	@StrutsTagAttribute(description = "format to be used", type = "String")
	public void setFormat(String format) {
		this.format = format;
	}

}
