/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 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.views.java;

import nuts.core.lang.StringEscapes;
import nuts.core.lang.Strings;
import nuts.exts.struts2.components.Anchor;
import nuts.exts.struts2.components.CheckboxList;
import nuts.exts.struts2.components.DatePicker;
import nuts.exts.struts2.components.DateTimePicker;
import nuts.exts.struts2.components.Property;
import nuts.exts.struts2.components.Radio;
import nuts.exts.struts2.components.Select;
import nuts.exts.struts2.components.TextField;
import nuts.exts.struts2.components.TimePicker;
import nuts.exts.struts2.util.StrutsContextUtils;
import nuts.exts.xwork2.util.ContextUtils;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.struts2.components.template.TemplateRenderingContext;
import org.apache.struts2.util.ComponentUtils;

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

public abstract class AbstractTemplateRenderer implements TemplateRenderer {
	private String base;
	private String suri;

	private TextProvider text;

	protected TemplateRenderingContext context;
	protected ValueStack stack;
	protected Map<String, Object> params;
	protected Writer writer;

	/**
	 * @param context context
	 */
	@SuppressWarnings("unchecked")
	public AbstractTemplateRenderer(TemplateRenderingContext context) {
		this.context = context;
		this.stack = context.getStack();
		this.params = context.getParameters();
		this.writer = context.getWriter();
	}

	protected String base() {
		if (base == null) {
			base = StrutsContextUtils.getServletRequest().getContextPath();
			if ("/".equals(base)) {
				base = "";
			}
		}
		return base;
	}

	protected String suri(String uri, String version) {
		if (suri == null) {
			suri = (String)params.get("statics");
			if (suri == null) {
				suri = StrutsContextUtils.getStaticBasePath(stack);
			}
		}
		if (Strings.isNotEmpty(version)) {
			uri += "?v=" + version;
		}
		return suri + uri;
	}

	protected String suri(String uri) {
		return suri(uri, null);
	}
	
	protected String sgif() {
		return suri("/nuts/images/s.gif");
	}

	protected String uri(String uri) {
		return base() + uri;
	}

	protected String ticon(String i) {
		if (i.startsWith("n-icon-")) {
			i = "n-icon " + i;
		}
		else if (i.startsWith("ui-icon-")) {
			i = "ui-icon " + i;
		}
		else if (i.startsWith("icon-")) {
			String[] ss = Strings.split(i);
	
			i = getText(ss[0]);
			if (i.startsWith("n-icon-")) {
				ss[0] = "n-icon " + i;
			}
			else if (i.startsWith("ui-icon-")) {
				ss[0] = "ui-icon " + i;
			}
			else {
				ss[0] = i;
			}
			
			i = Strings.join(ss, " ");
		}
		return i;
	}
	
	protected String button(String text, String icon) {
		return button(text, icon, null, (Map<String, String>)null);
	}

	protected String button(String text, String icon, String sicon, String onclick) {
		Map<String, String> attrs = null;
		if (onclick != null) {
			attrs = new HashMap<String, String>();
			attrs.put("onclick", onclick);
		}
		return button(text, icon, sicon, attrs);
	}
	
	protected String button(String text, String icon, String sicon, Map<String, String> attrs) {
		String cssClass = null;
		if (attrs != null) {
			cssClass = attrs.remove("cssClass");
		}
		
		StringBuilder sb = new StringBuilder();
		sb.append("<button class=\"n-button n-submit")
		  .append(cssClass == null ? "" : " " + cssClass)
		  .append("\" type=\"submit\"");
		
		if (attrs != null) {
			for (Entry<String, String> en : attrs.entrySet()) {
				sb.append(" ")
				  .append(en.getKey())
				  .append("=\"")
				  .append(html(en.getValue()))
				  .append("\"");
			}
		}
		sb.append('>');

		if (icon != null) {
			sb.append(icon(icon + " n-button-icon"));
		}
		sb.append("<span class=\"n-button-text\">");
		sb.append(text);
		sb.append("</span>");

		if (sicon != null) {
			sb.append(icon(icon + " n-button-icon"));
		}
		sb.append("</button>");
		
		return sb.toString();
	}
	
	protected String xicon(String c) {
		return icon(ticon(c));
	}

	protected String icon(String c) {
		return "<img class=\"" + c + "\" src=\"" + sgif() + "\"/>";
	}

	protected String icon(String c, String onclick) {
		return "<img class=\"" + c + " n-cpointer\" src=\"" 
			+ sgif() + "\"" 
			+ " onclick=\"" + onclick + "\"/>";
	}

	protected String defs(Object s) {
		return s == null ? "" : s.toString();
	}

	protected String join(String ... ss) {
		StringBuilder sb = new StringBuilder();
		for (String s : ss) {
			if (Strings.isNotEmpty(s)) {
				if (sb.length() > 0) {
					sb.append(' ');
				}
				sb.append(s);
			}
		}
		return sb.toString();
	}

	protected String defs(String s) {
		return Strings.defaultString((String)s);
	}

	protected String defs(String val, String def) {
		return Strings.defaultString((String)val, def);
	}

	protected String jsstr(Object s) {
		return StringEscapes.escapeJavaScript((String)s);
	}

	protected String jsstr(String s) {
		return StringEscapes.escapeJavaScript(s);
	}

	protected String html(Object s) {
		return StringEscapes.escapeHtml((String)s);
	}

	protected String html(String s) {
		return StringEscapes.escapeHtml(s);
	}

	protected String phtml(String s) {
		return StringEscapes.escapePhtml(s);
	}

	protected void writeJsc(String jsc) throws IOException {
		write("<script type=\"text/javascript\">");
		write(jsc);
		write("</script>");
	}
	protected void writeJs(String js) throws IOException {
		write("<script src=\"");
		write(js);
		write("\" type=\"text/javascript\"></script>");
	}

	protected void writeCss(String css) throws IOException {
		writeCss(css, null);
	}
	
	protected void writeCss(String css, String cls) throws IOException {
		write("<link");
		if (cls != null) {
			write(" class=\"");
			write(cls);
			write("\"");
		}
		write(" href=\"");
		write(css);
		write("\" rel=\"stylesheet\" type=\"text/css\" media=\"all\"/>");
	}

	protected void writeHidden(String id, String name) throws IOException {
		Attributes ha = new Attributes();
		ha.add("type", "hidden")
		  .add("id", id)
		  .add("name", name)
		  .add("value", findString(name));
		xtag("input", ha);
	}

	protected void writeHidden(String id, String name, String value) throws IOException {
		Attributes ha = new Attributes();
		ha.add("type", "hidden")
		  .add("id", id)
		  .add("name", name)
		  .add("value", value, false);
		xtag("input", ha);
	}

	protected Anchor getAnchorTag() throws IOException {
		Anchor a = new Anchor(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(a);
		return a;
	}

	protected void writeTextField(String cssClass,
			String name, String id, String value) throws IOException {
		TextField tf = new TextField(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(tf);
		
		tf.setCssClass(cssClass);
		tf.setName(name);
		tf.setId(id);
		tf.setValue(value);
		
		tf.start(writer);
		tf.end(writer, "");
	}

	protected void writeSelect(String cssClass,
			String name, String id, Object list, String value,
			String emptyOption, String onchange) throws IOException {
		Select select = new Select(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(select);
		
		select.setCssClass(cssClass);
		select.setName(name);
		select.setId(id);
		select.setList(list);
		select.setValue(value);
		select.setOnchange(onchange);
		select.setEmptyOption(emptyOption);

		select.start(writer);
		select.end(writer, "");
	}
	
	protected void writeCheckboxList(String cssClass,
			String name, String id, Object list, String value) throws IOException {
		CheckboxList cl = new CheckboxList(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(cl);
		
		cl.setCssClass(cssClass);
		cl.setName(name);
		cl.setId(id);
		cl.setList(list);
		cl.setValue(value);
		cl.setMultiselectIntercept(Boolean.FALSE.toString());

		cl.start(writer);
		cl.end(writer, "");
	}
	
	protected void writeRadio(String cssClass,
			String name, String id, Object list, String value) throws IOException {
		Radio r = new Radio(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(r);
		
		r.setCssClass(cssClass);
		r.setName(name);
		r.setId(id);
		r.setList(list);
		r.setValue(value);

		r.start(writer);
		r.end(writer, "");
	}
	
	protected void writeDatePicker(String cssClass,
			String name, String id, String format,
			String value, String options) throws IOException {
		DatePicker dp = new DatePicker(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(dp);
		
		dp.setCssClass(cssClass);
		dp.setName(name);
		dp.setId(id);
		dp.setFormat(format);
		dp.setValue(value);
		dp.setOptions(options);
		
		dp.start(writer);
		dp.end(writer, "");
	}
	
	protected void writeDateTimePicker(String cssClass,
			String name, String id, String format,
			String value) throws IOException {
		DateTimePicker dp = new DateTimePicker(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(dp);
		
		dp.setCssClass(cssClass);
		dp.setName(name);
		dp.setId(id);
		dp.setFormat(format);
		dp.setValue(value);
		
		dp.start(writer);
		dp.end(writer, "");
	}
	
	protected void writeTimePicker(String cssClass,
			String name, String id, String format,
			String value) throws IOException {
		TimePicker dp = new TimePicker(stack,
				StrutsContextUtils.getServletRequest(),
				StrutsContextUtils.getServletResponse());

		StrutsContextUtils.getContainer(stack).inject(dp);
		
		dp.setCssClass(cssClass);
		dp.setName(name);
		dp.setId(id);
		dp.setFormat(format);
		dp.setValue(value);
		
		dp.start(writer);
		dp.end(writer, "");
	}

	protected void write(String s) throws IOException {
		if (s != null) {
			writer.write(s);
		}
	}
	
	protected void writeIfExists(String s, Object v) throws IOException {
		if (v != null) {
			write(s);
		}
	}
	
	protected void body(String text) throws IOException {
		body(text, true);
	}

	protected void body(String text, boolean encode) throws IOException {
		write(encode ? html(text) : text);
	}

	protected void xtag(String name) throws IOException {
		stag(name, null, true);
    }

	protected void xtag(String name, Attributes attrs) throws IOException {
		stag(name, attrs, true);
    }

	protected void stag(String name, Attributes attrs) throws IOException {
		stag(name, attrs, false);
	}
	
	protected void stag(String name, Attributes attrs, boolean end) throws IOException {
		write("<");
		write(name);
		if (attrs != null) {
			for (Entry<String, String> en : attrs.entrySet()) {
				write(" ");
				write(en.getKey());
				write("=\"");
				write(en.getValue());
				write("\"");
			}
		}

		if (end) {
			write("/");
		}
		write(">");
	}

	public void etag(String name) throws IOException {
		write("</");
		write(name);
		write(">");
	}

	public abstract void render() throws Exception;

	protected TextProvider getTextProvider() {
		if (text == null) {
			text = ContextUtils.findTextProviderInStack(stack);
		}
		return text;
	}
	
	protected String getText(String key) {
		return getTextProvider().getText(key);
	}
	
	protected String getText(String key, String defaultValue) {
		return getTextProvider().getText(key, defaultValue);
	}
	
	protected String findString(String expr) {
		return (String)findValue(expr, String.class);
	}

	protected Object findValue(String expr) {
		if (expr == null) {
			return null;
		}

		return stack.findValue(ComponentUtils.stripExpressionIfAltSyntax(stack, expr));
	}

	protected Object findValue(String expr, Class<?> toType) {
		if (expr == null) {
			return null;
		}
		expr = ComponentUtils.stripExpressionIfAltSyntax(stack, expr);
		return stack.findValue(expr, toType);
	}

	protected void putInContext(String key, Object value) {
		stack.getContext().put(key, value);
	}
	
	protected Object findInContext(String key) {
		return stack.getContext().get(key);
	}

	public String formatValue() {
		return formatValue(null);
	}
	
	public String formatValue(String escape) {
		Object value = params.get("nameValue");
		if (value != null) {
			String format = (String)params.get("format");
			Property p = new Property(stack);
			try {
				p.setValue(value);
				p.setFormat(format);
				if (escape != null) {
					p.setEscape(escape);
				}
				return p.formatValue();
			}
			finally {
				p.getComponentStack().pop();
			}
		}
		else {
			return "";
		}
	}

	public String formatRawValue(Object value) {
		return formatRawValue(value, null);
	}
	
	public String formatRawValue(Object value, String escape) {
		if (value != null) {
			Property p = new Property(stack);
			try {
				p.setValue(value);
				if (escape != null) {
					p.setEscape(escape);
				}
				return p.formatValue();
			}
			finally {
				p.getComponentStack().pop();
			}
		}
		else {
			return "";
		}
	}
	
	public String nameValue() {
		Object value = params.get("nameValue");
		if (value != null) {
			Property p = new Property(stack);
			try {
				p.setValue(value);
				p.setEscape(null);
				return p.formatValue();
			}
			finally {
				p.getComponentStack().pop();
			}
		}
		else {
			return defs(null);
		}
	}
}
