package at.gv.egiz.pdfas.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import ognl.Ognl;
import ognl.OgnlException;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * Utility class for ognl evaluation.
 * <a href="http://www.opensymphony.com/ognl/">ognl website</a>
 * @author dferbas
 * @author mhiess
 *
 */
public class OgnlUtil {

	private static final String EXP_END = "}";

   private static final String EXP_START = "${";

   private static final Logger log = Logger.getLogger(OgnlUtil.class);

	private Map context = new HashMap();
	private Object root = null;

	public OgnlUtil() {
		this.init(null);
	}

	public OgnlUtil(Map context) {
		this.init(context);
	}
	
	public OgnlUtil(Object root) {
	   this.root = root;
	   this.init(null);
	}

	/**
	 * Adds the default utils to the context
	 * 
	 * @param context1
	 */
	private void init(Map context1) {
		if (context1 != null) {
			this.context = context1;
		}		
	}

	private List extractExpressions(String text) {

		List expressions = new ArrayList();

		int indexStart = 0;
		int indexEnd = 0;
		String exp;

		while (indexStart != -1) {
			indexStart = text.indexOf(EXP_START, indexStart);
			indexEnd = text.indexOf(EXP_END, indexStart);

			if (indexStart != -1 && indexEnd != -1) {

				exp = text.substring(indexStart + 2, indexEnd);
				log.debug("Found expression in template: " + exp);
				if (!exp.equals("") && exp != null) {
					if (!expressions.contains(exp)) {
						expressions.add(exp);
					} else {
						log.debug("Duplicated expression '" + exp + "' found");
					}
				}

				indexStart = indexEnd;
			}
		}

		return expressions;
	}
	
	public boolean containsExpression(String template) {
	   return template != null && StringUtils.contains(template,"${") && StringUtils.contains(template, "}");
	}

	/**
	 * Compile/evaluate a message with ognl expressions marked with <tt>${expression}</tt> 
	 * 
	 * @param template
	 * @return
	 */
	public String compileMessage(String template) {

		if (this.context != null) {
			String compiledMsg = template;
			List expressions = this.extractExpressions(template);

			String value;
			for (Iterator it = expressions.iterator(); it.hasNext();) {
            String expr = (String) it.next();

				try {
				   if (this.root != null) {
				      value = String.valueOf(Ognl.getValue(expr, this.root));
				   } else {
				      value = String.valueOf(Ognl.getValue(expr, this.context));
				   }
					log.debug("Found value: '" + value + "' for expression: '" + expr + "'");

					if (value == null) {
						value = "";
						log.debug("Set value for expression to: empty string");
					}

					compiledMsg = StringUtils.replace(compiledMsg, EXP_START + expr + EXP_END, value);
				} catch (OgnlException e) {
					log.error(e.getMessage(), e);
				}
			}
			log.debug("Returning compiled message: " + compiledMsg);
			return compiledMsg;
		}
		return null;
	}

	/**
	 * Evaluate an expression as ognl, returning result as object. No placeholders ${ } allowed.
	 * @param ognlExp
	 * @return
	 */
	public Object evaluate(String ognlExp) {

		if (this.context != null) {

			Object value = null;
			try {
				value = Ognl.getValue(ognlExp, this.context);
				log.debug("Found value: '" + value + "' for expression: '" + ognlExp + "'");

			} catch (OgnlException e) {
				log.error(e.getMessage(), e);				
			}
			return value;
		} else return null;		
	}

	/**
	 * This method compiles a map of ognl expression to a map with entries
	 * 
	 * @param toCompile
	 * @return
	 */
	public Map compileExpressions(Map toCompile) {
		if (this.context != null) {
			Map result = new HashMap();

			for (Iterator it = result.entrySet().iterator(); it.hasNext();) {
            Entry entry = (Entry) it.next();            
				try {
					result.put(entry.getKey(), Ognl.getValue(entry.getValue(), this.context));
				} catch (OgnlException e) {
					result.put(entry.getKey(), null);
					log.warn(e.getMessage(), e);
				}
			}
			return result;

		}
		return null;
	}

	public Map getContext() {
		return this.context;
	}

	public void mergeOgnlContext(OgnlUtil ognlUtil) {
		this.context.putAll(ognlUtil.getContext());
	}
}