/*
 * Copyright 2009-2010 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.awk.nano.java6;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import net.morilib.awk.nano.AwkException;
import net.morilib.awk.nano.expr.AwkExpression;
import net.morilib.awk.nano.io.AwkFiles;
import net.morilib.awk.nano.namespace.AwkClosureNamespace;
import net.morilib.awk.nano.namespace.AwkNamespace;
import net.morilib.awk.nano.parser.AwkLexer;
import net.morilib.awk.nano.parser.AwkParser;
import net.morilib.awk.nano.value.AwkClass;
import net.morilib.awk.nano.value.AwkFunction;
import net.morilib.awk.nano.value.AwkValue;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2014/02/19
 */
public class AwkScriptEngine extends AbstractScriptEngine
implements Compilable, Invocable {

	//
	private AwkScriptContext context;
	private AwkNamespace global, engine;
	private AwkScriptEngineFactory factory;

	//
	AwkScriptEngine(AwkNamespace g, AwkScriptEngineFactory f) {
		global  = g;
		engine  = new AwkClosureNamespace(g);
		context = new AwkScriptContext(g, engine);
		factory = f;
	}

	/* (non-Javadoc)
	 * @see javax.script.AbstractScriptEngine#getScriptContext(javax.script.Bindings)
	 */
	@Override
	protected ScriptContext getScriptContext(Bindings nn) {
		AwkScriptContext c;

		c = new AwkScriptContext(global,
				new AwkClosureNamespace(engine));
		c.setReader(new InputStreamReader(System.in));
		c.setWriter(new OutputStreamWriter(System.out));
		c.setErrorWriter(new OutputStreamWriter(System.err));
		c.setBindings(nn, ScriptContext.ENGINE_SCOPE);
		return c;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptEngine#eval(java.lang.String, javax.script.ScriptContext)
	 */
	@Override
	public Object eval(String script,
			ScriptContext context) throws ScriptException {
		return eval(new StringReader(script), context);
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptEngine#eval(java.io.Reader, javax.script.ScriptContext)
	 */
	@Override
	public Object eval(Reader reader,
			ScriptContext context) throws ScriptException {
		AwkFiles f = new AwkFiles(context.getReader(),
				context.getWriter(), context.getErrorWriter());
		AwkExpression p;
		AwkValue r;
		AwkLexer l;

		try {
			l = new AwkLexer(reader);
			p = AwkParser.parseStatement(engine, l);
			p = p.compileInternally();
			r = p.eval(engine, f);
			return r.toObject();
		} catch(IOException e) {
			throw new ScriptException(e);
		} catch(AwkException e) {
			throw new ScriptException(e);
		}
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptEngine#createBindings()
	 */
	@Override
	public Bindings createBindings() {
		return new SimpleBindings();
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptEngine#getFactory()
	 */
	@Override
	public ScriptEngineFactory getFactory() {
		return factory;
	}

	/* (non-Javadoc)
	 * @see javax.script.Compilable#compile(java.lang.String)
	 */
	@Override
	public CompiledScript compile(String script) throws ScriptException {
		return compile(new StringReader(script));
	}

	/* (non-Javadoc)
	 * @see javax.script.Compilable#compile(java.io.Reader)
	 */
	@Override
	public CompiledScript compile(Reader script) throws ScriptException {
		AwkExpression p;
		AwkLexer l;

		try {
			l = new AwkLexer(script);
			p = AwkParser.parseStatement(engine, l);
			p = p.compileInternally();
			return new AwkCompiledScript(p, this);
		} catch(IOException e) {
			throw new ScriptException(e);
		} catch(AwkException e) {
			throw new ScriptException(e);
		}
	}

	//
	private List<AwkValue> toList(Object... args) {
		List<AwkValue> l = new ArrayList<AwkValue>();

		for(int k = 0; k < l.size(); k++) {
			l.add(AwkScriptContext.toAwkValue(args[k]));
		}
		return l;
	}

	/* (non-Javadoc)
	 * @see javax.script.Invocable#invokeMethod(java.lang.Object, java.lang.String, java.lang.Object[])
	 */
	@Override
	public Object invokeMethod(Object dieser, String name,
			Object... args) throws ScriptException, NoSuchMethodException {
		AwkNamespace s;
		AwkFunction f;
		AwkFiles o;
		AwkClass c;

		if(!(dieser instanceof AwkClass)) {
			throw new NoSuchMethodException();
		}

		c = (AwkClass)dieser;
		s = c.getNamespace();
		if((f = s.findFunction(name)) == null) {
			throw new NoSuchMethodException();
		} else {
			o = new AwkFiles(context.getReader(), context.getWriter(),
					context.getErrorWriter());
			return f.apply(s, o, toList(args));
		}
	}

	/* (non-Javadoc)
	 * @see javax.script.Invocable#invokeFunction(java.lang.String, java.lang.Object[])
	 */
	@Override
	public Object invokeFunction(String name,
			Object... args) throws ScriptException, NoSuchMethodException {
		AwkFunction f;
		AwkFiles o;

		if((f = engine.findFunction(name)) == null) {
			throw new NoSuchMethodException();
		} else {
			o = new AwkFiles(context.getReader(), context.getWriter(),
					context.getErrorWriter());
			return f.apply(engine, o, toList(args));
		}
	}

	/* (non-Javadoc)
	 * @see javax.script.Invocable#getInterface(java.lang.Class)
	 */
	@Override
	public <T> T getInterface(Class<T> klasse) {
		throw new IllegalArgumentException();
	}

	/* (non-Javadoc)
	 * @see javax.script.Invocable#getInterface(java.lang.Object, java.lang.Class)
	 */
	@Override
	public <T> T getInterface(Object dieser, Class<T> klasse) {
		throw new IllegalArgumentException();
	}

}
