package de.brightbyte.yates;

import java.io.UnsupportedEncodingException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import de.brightbyte.data.Functor;
import de.brightbyte.db.DatabaseUtil;
import de.brightbyte.util.CollectionUtils;
import de.brightbyte.xml.XmlUtil;

public class ResultSetRenderer implements YatesMacro {
	
	public static final ResultSetRenderer instance = new ResultSetRenderer();

	private Map<String, Object> tableAttributes = new HashMap<String, Object>();
	private Map<String, String> columnLabels = new HashMap<String, String>();
	private Map<String, Map<String, Object>> columnAttributes = new HashMap<String, Map<String, Object>>();
	private Map<String, String> columnFormats = new HashMap<String, String>();
	private Map<String, Functor<?, ?>> columnTransformations = new HashMap<String, Functor<?, ?>>();
	
	protected String blobEncoding = null; 
	
	public void setTableAttribute(String attr, Object value) {
		tableAttributes.put(attr, value); //TODO: merge styles?
	}

	public void setColumnLabel(String col, String label) {
		columnLabels.put(col, label);
	}

	public void setColumnFormat(String col, String format) {
		columnFormats.put(col, format);
	}

	public void setColumnTrqansformation(String col, Functor<?, ?> t) {
		columnTransformations.put(col, t);
	}

	public void setColumnAttribute(String col, String attr, Object value) {
		Map<String, Object> a = columnAttributes.get(col);
		if (a==null) {
			a = new HashMap<String, Object>();
			columnAttributes.put(col, a);
		}
		
		a.put(attr, value); //TODO: merge styles?
	}
	
	protected Map<String, Object> getAttributes(YatesParameters parameters) throws YatesException {
		Map<String, Object> paramAttributes = new HashMap<String, Object>();
		paramAttributes.put("id", parameters.getParameter("id", null));
		paramAttributes.put("class", parameters.getParameter("class", null));
		paramAttributes.put("style", parameters.getParameter("style", null));
		
		return paramAttributes;
	}

	public String render(YatesParameters parameters) throws YatesException {
		YatesHtml s = new YatesHtml();
		
		Map<String, Object> paramAttributes = getAttributes(parameters);
		ResultSet rs = (ResultSet)parameters.getParameter("data", parameters.getSubject());
		render(rs, paramAttributes, s);
		
		return s.toString();
	}

	public void render(ResultSet rs, Map<String, Object> attrib, YatesHtml s) throws YatesException {
		try {
			ResultSetMetaData meta = rs.getMetaData();
			
			appendTableStart(s, attrib);
			appendTableHead( meta, s );
			
			while (rs.next()) {
				appendTableRow( rs, meta, s );
			}
			
			appendTableEnd(s);
		} catch (SQLException e) {
			throw new YatesException(e);
		}		
	}

	protected void appendTableStart(YatesHtml s, Map<String, Object> attrib) throws YatesException {
		s.startTag("table", tableAttributes, attrib);
	}

	protected void appendTableHead(ResultSetMetaData meta, YatesHtml s) throws SQLException {
		s.startTag("tr");
		int c = meta.getColumnCount();
		for (int i = 1; i<=c; i++) {
			s.appendEscapedTag("th", getColumnLabel(meta, i), getCellAttributes(meta, i));
		}
		s.endTag("tr");
	}

	protected void appendTableRow(ResultSet rs, ResultSetMetaData meta, YatesHtml s) throws SQLException {
		s.startTag("tr");
		int c = meta.getColumnCount();
		for (int i = 1; i<=c; i++) {
			s.appendEscapedTag("td", getColumnValue(meta, i, rs), getCellAttributes(meta, i));
		}
		s.endTag("tr");
	}

	protected void appendTableEnd(YatesHtml s) {
		s.endTag("table");
	}

	private String getColumnLabel(ResultSetMetaData meta, int i) throws SQLException {
		String n = meta.getColumnName(i);
		String label = columnLabels.get(n);
		if (label == null) label = meta.getColumnLabel(i);
		if (label == null) label = n;
		
		label = XmlUtil.escape(label); //TODO: allow this to be a link, for sorting!
		
		return label;
	}

	private Map<String, Object> getCellAttributes(ResultSetMetaData meta, int i) throws SQLException {
		Map<String, Object> attr = new HashMap<String, Object>(); //XXX: avoid creating this every time...
		String n = meta.getColumnName(i);
		String cls = "column-"+n;
		
		if (DatabaseUtil.isNumeric( meta, i )) {
			cls += " number-column";
		}
		
		attr.put("class", cls);
		
		Map<String, Object> a = columnAttributes.get(n);
		if (a!=null) attr = CollectionUtils.deepMerge(attr, a);
			
		return attr;
	}
	
	private String getColumnValue(ResultSetMetaData meta, int i, ResultSet rs) throws SQLException {
		String n = meta.getColumnName(i);
		Object v = rs.getObject(i);
		
		if (v==null) return ""; //XXX: what should null look like? configure?
		
		Functor t = columnTransformations.get(n);
		if (t!=null) {
			v = t.apply(v);
		}
		else if (v instanceof byte[]) {
			if (blobEncoding!=null) {
				try {
					v = new String((byte[])v, blobEncoding);
				} catch (UnsupportedEncodingException e) {
					throw new RuntimeException(e);
				}
			}
			//TODO: else hex-dump?
		}

		String s;
		String f = columnFormats.get(n);
		if (f==null) f = DatabaseUtil.getColumnFormat( meta, i );
		
		if (f!=null) s = String.format(f, v);
		else s = String.valueOf(v); 
	
		s = XmlUtil.escape(s); //TODO: allow injection of HTML here!
		
		return s;
	}

	public String getBlobEncoding() {
		return blobEncoding;
	}

	public void setBlobEncoding(String blobEncoding) {
		this.blobEncoding = blobEncoding;
	}

}
