package com.hammurapi.common;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Locale;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

/**
 * This class resolves and evaluates scripts for a class. 
 * Scripts and properties for script parameterization are resolved traversing class hierarchy.
 * A special resource with .binder extension is used to create custom bindings for
 * script evaluation. This resource shall contain a fragment of java code. This code shall reference a
 * single parameter 'bindings' to access existing bindings and create new ones.
 * 
 * The primary purpose of this class is to be a facility for flexible rendering/code generation.
 * @author Pavel Vlasov
 *
 */
public class ScriptProcessor {
	
	private ResourceLoader rl;
	private ScriptEngineManager scriptEngineManager;

	public ScriptProcessor(ResourceLoader resourceLoader, ClassLoader scriptEnginesClassLoader) {
		this.rl = resourceLoader;
		this.scriptEngineManager = scriptEnginesClassLoader==null ? new ScriptEngineManager() : new ScriptEngineManager(scriptEnginesClassLoader);
	}
	
	public Object eval(String scriptName, Map<String, Object> bindings, Locale locale) throws ScriptException {
		for (ScriptEngineFactory sef: scriptEngineManager.getEngineFactories()) {
			for (String extension: sef.getExtensions()) {
				InputStream scriptStream = rl.getResourceStream(scriptName, locale, extension);
				if (scriptStream!=null) {
					SimpleBindings sBindings = new SimpleBindings();
					Map<String, Object> props = rl.getProperties(scriptName, locale, "properties");
					sBindings.putAll(props);
					if (bindings!=null) {
						sBindings.putAll(bindings);
					}
					InputStream binderStream = rl.getResourceStream(scriptName, locale, "binder");
					if (binderStream!=null) {
						ScriptEngine be = scriptEngineManager.getEngineByExtension("java");
						if (be==null) {
							throw new ScriptException("Cannot evaluate binder - java script engine not found");
						}
						SimpleBindings bb = new SimpleBindings();
						bb.put("bindings", sBindings);
						be.eval(new InputStreamReader(binderStream), bb);
					}
					return sef.getScriptEngine().eval(new InputStreamReader(scriptStream), sBindings);
				}
			}
		}
		throw new ScriptException("Script resource not found: "+scriptName+" in "+rl);
	}

}
