/*
 * 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.components;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import nuts.core.beans.PropertyUtils;
import nuts.core.io.CsvWriter;
import nuts.core.lang.ExceptionUtils;
import nuts.core.lang.StringUtils;

import org.apache.commons.logging.LogFactory;
import org.apache.struts2.components.Component;
import org.apache.struts2.util.MakeIterator;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import com.opensymphony.xwork2.util.ValueStack;

/**
 * Render CSV.
 * 
 * Example:
 * 
 * <pre>
 * &lt;s:csv list=&quot;list&quot; columns=&quot;columns&quot; columnKeys=&quot;columnKyes&quot; seperator=&quot;,&quot; quotechar='&quot;' escapechar='&quot;'/&gt;
 * 
 * </pre>
 * 
 */
@StrutsTag(
		name = "csv", 
		tldTagClass = "nuts.exts.struts2.views.jsp.CsvTag", 
		description = "Render CSV")
public class Csv extends Component {

	private static final org.apache.commons.logging.Log log = LogFactory.getLog(Csv.class);

	protected Object list;
	protected Object columns;

	protected String separator;
	protected String quotechar;
	protected String escapechar;

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

	private Object getBeanProperty(Object bean, String name) {
		try {
			return PropertyUtils.getProperty(bean, name);
		}
		catch (Exception e) {
			return null;
		}
	}

	private Map<String, Map> codemaps = new HashMap<String, Map>();
	
	private String getCodeText(Object cm, Object v) {
		if (cm instanceof String) {
			Map m = codemaps.get(cm);
			if (m == null) {
				m = (Map)stack.findValue((String)cm, Map.class);
				codemaps.put((String)cm, m);
			}
			cm = m;
		}

		if (cm instanceof Map) {
			v = ((Map)cm).get(v);
			return v == null ? null : v.toString(); 
		}
		
		return null;
	}

	/**
	 * @see org.apache.struts2.components.Component#end(java.io.Writer, java.lang.String)
	 */
	public boolean end(Writer writer, String body) {
		evaluateParams();

		ValueStack stack = getStack();

		@SuppressWarnings("resource")
		CsvWriter cw = new CsvWriter(writer);
		if (StringUtils.isNotEmpty(separator)) {
			cw.setSeparator(separator.charAt(0));
		}
		if (StringUtils.isNotEmpty(quotechar)) {
			cw.setQuotechar(quotechar.charAt(0));
		}
		if (StringUtils.isNotEmpty(escapechar)) {
			cw.setEscapechar(escapechar.charAt(0));
		}
		
		try {
			ArrayList<Object> line = new ArrayList<Object>();
			for (Iterator it = MakeIterator.convert(columns); it.hasNext();) {
				Object c = it.next();
				Object hidden = getBeanProperty(c, "hidden");
				if (!Boolean.TRUE.equals(hidden)) {
					String name = (String)getBeanProperty(c, "name");
					String header = (String)getBeanProperty(c, "header");
					if (header == null) {
						header = name;
					}
					line.add(header);
				}
			}
			cw.writeNext(line);

			if (list != null) {
				for (Iterator it = MakeIterator.convert(list); it.hasNext();) {
					Object d = it.next();
					line.clear();
					for (Iterator it2 = MakeIterator.convert(columns); it2.hasNext();) {
						Object c = it2.next();
						Object hidden = getBeanProperty(c, "hidden");
						if (Boolean.TRUE.equals(hidden)) {
							continue;
						}

						String name = (String)getBeanProperty(c, "name");
						
						String format = null;

						Object oFormat = getBeanProperty(c, "format");
						if (oFormat != null && !(oFormat instanceof String)) {
							format = (String)getBeanProperty(oFormat, "type");
						}
						
						String value = null;
						if ("code".equals(format)) {
							Object v = getBeanProperty(d, name);
							Iterator iv = MakeIterator.convert(v);
							if (iv != null) {
								Object codemap = getBeanProperty(oFormat, "codemap");
								StringBuilder sb = new StringBuilder();
								while (iv.hasNext()) {
									sb.append(getCodeText(codemap, iv.next()));
									if (iv.hasNext()) {
										sb.append(' ');
									}
								}
								value = sb.toString();
							}
						}
						else if ("expression".equals(format)) {
							try {
								stack.push(d);
								Object exp = getBeanProperty(oFormat, "expression");
								if (exp instanceof String) {
									Object v = stack.findValue((String)exp);
									if (v != null) {
										Property p = new Property(stack);
										try {
											p.setValue(v);
											p.setEscape(null);
											value = p.formatValue();
										}
										finally {
											p.getComponentStack().pop();
										}
									}
								}
							}
							finally {
								stack.pop();
							}
						}
						else {
							try {
								stack.push(d);
								Property p = new Property(stack);
								p.setName(name);
								p.setFormat(format);
								p.setEscape(null);
								value = p.formatValue();
							}
							finally {
								stack.pop();
							}
						}
						line.add(value);
					}
					cw.writeNext(line);
				}
			}
			cw.flush();
		}
		catch (Exception e) {
			log.error("csv", e);
			try {
				writer.write(ExceptionUtils.getStackTrace(e));
			}
			catch (IOException e2) {
			}
		}
		finally {
		}

		return super.end(writer, "");
	}

	/**
	 * Evaluate parameters
	 */
	protected void evaluateParams() {
		Object value;

		// list
		if (list == null) {
			list = parameters.get("list");
		}

		value = null;
		if (list instanceof String) {
			value = findValue((String) list);
			if (!MakeIterator.isIterable(value)) {
				value = null;
			}
		}
		else if (MakeIterator.isIterable(list)) {
			value = list;
		}
		list = value;


		// columns
		if (columns == null) {
			columns = parameters.get("columns");
		}

		value = null;
		if (columns instanceof String) {
			value = findValue((String)columns);
		}
		else if (MakeIterator.isIterable(columns)) {
			value = columns;
		}

        if (value == null) {
			fieldError("columns", "The requested columns '" + columns + "' could not be resolved as a collection/array/map/enumeration/iterator type.", null);
		}
		else {
			addParameter("columns", value);
		}


		if (separator != null) {
			separator = findString(separator);
		}
		if (quotechar != null) {
			quotechar = findString(quotechar);
		}
		if (escapechar != null) {
			escapechar = findString(escapechar);
		}
	}

	/**
	 * @param list the list to set
	 */
	@StrutsTagAttribute(description = "the property for the list")
	public void setList(Object list) {
		this.list = list;
	}

	/**
	 * @param columns the columns to set
	 */
	@StrutsTagAttribute(description = "the property for the columns")
	public void setColumns(Object columns) {
		this.columns = columns;
	}

	/**
	 * @param separator the separator to set
	 */
	@StrutsTagAttribute(description = "the property for separator")
	public void setSeparator(String separator) {
		this.separator = separator;
	}

	/**
	 * @param quotechar the quotechar to set
	 */
	@StrutsTagAttribute(description = "the property for quotechar")
	public void setQuotechar(String quotechar) {
		this.quotechar = quotechar;
	}

	/**
	 * @param escapechar the escapechar to set
	 */
	@StrutsTagAttribute(description = "the property for escapechar")
	public void setEscapechar(String escapechar) {
		this.escapechar = escapechar;
	}

}
