package com.hammurapi.common;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

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

/**
 * This class handles <code>script</code> protocol/schema and evaluates expressions using Java Scripting framework. 
 * Expression language extension is passed as authority and expression is passed as path, e.g. <code>script://java/myObject.toString()</code>.
 * Evaluation result is converted to input stream in the following way: 
 * a) If it is input stream then it is returned as is. 
 * b) If it is Reader then it is wrapped into input stream.
 * c) If it is null, then null is returned.
 * d) Result is converted to String with toString() method and string bytes are returned as input stream.  
 * @author Pavel Vlasov
 *
 */
public class ScriptURLStreamHandler extends URLStreamHandler {

	public static final String SCRIPT_PROTOCOL = "script";
	private Bindings bindings;
	private ScriptEngineManager scriptEngineManager;	
	
	/**
	 * 
	 * @param classLoader ClassLoader to use to load script engines and other classes.
	 * @param bindings Bindings for evaluating script expression.
	 */
	public ScriptURLStreamHandler(ClassLoader classLoader, Map<String, Object> bindings) {
		this.bindings = bindings==null ? new SimpleBindings() : new SimpleBindings(bindings);
		this.scriptEngineManager = new ScriptEngineManager(classLoader);
	}
	
	/**
	 * 
	 * @param bindings Bindings for evaluating script expression.
	 */
	public ScriptURLStreamHandler(Map<String, Object> bindings) {
		this.bindings = bindings==null ? new SimpleBindings() : new SimpleBindings(bindings);
		this.scriptEngineManager = new ScriptEngineManager();
	}
	
	// For testing.
	public static void main(String[] args) throws Exception {
		Map<String, Object> env = new HashMap<String, Object>();
		env.put("z", "366");
		ScriptURLStreamHandler suh = new ScriptURLStreamHandler(null, env);
		URL testURL = new URL(null, "script://js/z.length", suh);
		InputStream is = testURL.openStream();
		byte[] buf = new byte[40];
		is.read(buf);
		System.out.println(Arrays.toString(buf));
		
	}
	@Override
	protected URLConnection openConnection(final URL u) throws IOException {
		if (u==null || !SCRIPT_PROTOCOL.equals(u.getProtocol())) {
			return null;
		}
		return new URLConnection(u) {
			
			@Override
			public void connect() throws IOException {
				// Nothing to do - everything is done in "getInputStream()".
				
			}
			
			@Override
			public InputStream getInputStream() throws IOException {
				String authority = u.getHost();
				if (authority==null) {
					return null;
				}
				
				ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension(authority);
				if (scriptEngine==null) {
					throw new IOException("Cannot create script engine for extension "+authority);
				}
				
				String expr = u.getPath();
				if (expr.startsWith("/")) {
					expr = expr.substring(1);
				}
				try {
					Object res = scriptEngine.eval(expr, bindings);
					if (res==null) {
						return null;
					}
					if (res instanceof InputStream) {
						return (InputStream) res;
					}
					
					if (res instanceof Reader) {
						StringWriter sw = new StringWriter();
						char[] cbuf = new char[4096];
						int l;
						while ((l=((Reader) res).read(cbuf))!=-1) {
							sw.write(cbuf, 0, l);
						}
						((Reader) res).close();
						sw.close();
						res = sw.toString();
					}
					
					return new ByteArrayInputStream(res.toString().getBytes());
				} catch (ScriptException e) {
					throw new IOException("Cannot evaluate "+expr, e);
				}
			}
			
			
		};
	}
	
}


