/*
 * 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.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.SimpleBindings;

import net.morilib.awk.nano.namespace.AwkNamespace;
import net.morilib.awk.nano.value.AwkArray;
import net.morilib.awk.nano.value.AwkFloat;
import net.morilib.awk.nano.value.AwkInteger;
import net.morilib.awk.nano.value.AwkString;
import net.morilib.awk.nano.value.AwkUndefined;
import net.morilib.awk.nano.value.AwkValue;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2014/02/19
 */
public class AwkScriptContext implements ScriptContext {

	//
	private Reader reader;
	private Writer writer, errwriter;
	AwkNamespace global, engine;

	//
	AwkScriptContext(AwkNamespace rt, AwkNamespace eng) {
		global    = rt;
		engine    = eng;
		reader    = new InputStreamReader(System.in);
		writer    = new OutputStreamWriter(System.out);
		errwriter = new OutputStreamWriter(System.err);
	}

	private AwkNamespace _scope(int scope) {
		switch(scope) {
		case ENGINE_SCOPE:  return engine;
		case GLOBAL_SCOPE:  return global;
		default:  throw new IllegalArgumentException();
		}
	}

	private static AwkValue toAwkValue(Object o, boolean nest) {
		Map.Entry<?, ?> t;
		Iterator<?> i;
		AwkArray a;
		List<?> l;

		if(o == null) {
			return AwkUndefined.UNDEF;
		} else if(o instanceof String) {
			return AwkString.valueOf((String)o);
		} else if(o instanceof Integer || o instanceof Long ||
				o instanceof Byte || o instanceof Short) {
			return AwkInteger.valueOf(((Number)o).intValue());
		} else if(o instanceof Character) {
			return AwkInteger.valueOf(((Character)o).charValue());
		} else if(o instanceof Double || o instanceof Float) {
			return AwkFloat.valueOf(((Number)o).doubleValue());
		} else if(o instanceof BigInteger) {
			return AwkInteger.valueOf((BigInteger)o);
		} else if(o instanceof BigDecimal) {
			return AwkFloat.valueOf(((BigDecimal)o).doubleValue());
		} else if(o instanceof Boolean) {
			return AwkInteger.valueOf(o.equals(Boolean.TRUE) ? 1 : 0);
		} else if(!nest && o instanceof Map) {
			a = new AwkArray();
			i = ((Map<?, ?>)o).entrySet().iterator();
			while(i.hasNext()) {
				t = (Map.Entry<?, ?>)i.next();
				a.putArray(t.getKey().toString(),
						toAwkValue(t.getValue(), true));
			}
			return a;
		} else if(!nest && o instanceof List) {
			a = new AwkArray();
			l = (List<?>)o;
			for(int k = 1; k <= l.size(); k++) {
				a.putArray(k + "", toAwkValue(l.get(k - 1), true));
			}
			return a;
		} else if(!nest && o.getClass().isArray()) {
			a = new AwkArray();
			for(int k = 1; k <= Array.getLength(o); k++) {
				a.putArray(k + "",
						toAwkValue(Array.get(o, k - 1), true));
			}
			return a;
		} else {
			return AwkString.valueOf(o.toString());
		}
	}

	/**
	 * 
	 * @param o
	 */
	public static AwkValue toAwkValue(Object o) {
		return toAwkValue(o, false);
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#setBindings(javax.script.Bindings, int)
	 */
	@Override
	public void setBindings(Bindings bindings, int scope) {
		AwkNamespace e;

		e = _scope(scope);
		for(Map.Entry<String, Object> t : bindings.entrySet()) {
			e.assign(t.getKey(), toAwkValue(t.getValue()));
		}
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getBindings(int)
	 */
	@Override
	public Bindings getBindings(int scope) {
		Bindings b = new SimpleBindings();
		AwkNamespace e;

		e = _scope(scope);
		for(Map.Entry<String, AwkValue> t : e.getValueMap().entrySet()) {
			b.put(t.getKey(), t.getValue().toObject());
		}
		return b;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#setAttribute(java.lang.String, java.lang.Object, int)
	 */
	@Override
	public void setAttribute(String name, Object value, int scope) {
		AwkNamespace e;

		e = _scope(scope);
		e.assign(name, toAwkValue(value));
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getAttribute(java.lang.String, int)
	 */
	@Override
	public Object getAttribute(String name, int scope) {
		AwkNamespace e;
		AwkValue v;

		e = _scope(scope);
		return (v = e.get(name)) != null ? v.toObject() : null;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#removeAttribute(java.lang.String, int)
	 */
	@Override
	public Object removeAttribute(String name, int scope) {
		AwkNamespace e;
		AwkValue v;

		e = _scope(scope);
		return (v = e.remove(name)) != null ? v.toObject() : null;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getAttribute(java.lang.String)
	 */
	@Override
	public Object getAttribute(String name) {
		AwkValue v;

		return (v = engine.find(name)) != null ? v.toObject() : null;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getAttributesScope(java.lang.String)
	 */
	@Override
	public int getAttributesScope(String name) {
		if(engine.get(name) != null) {
			return ENGINE_SCOPE;
		} else if(global.get(name) != null) {
			return GLOBAL_SCOPE;
		}
		return -1;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getWriter()
	 */
	@Override
	public Writer getWriter() {
		return writer;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getErrorWriter()
	 */
	@Override
	public Writer getErrorWriter() {
		return errwriter;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#setWriter(java.io.Writer)
	 */
	@Override
	public void setWriter(Writer writer) {
		this.writer = writer;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#setErrorWriter(java.io.Writer)
	 */
	@Override
	public void setErrorWriter(Writer writer) {
		this.errwriter = writer;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getReader()
	 */
	@Override
	public Reader getReader() {
		return reader;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#setReader(java.io.Reader)
	 */
	@Override
	public void setReader(Reader reader) {
		this.reader = reader;
	}

	/* (non-Javadoc)
	 * @see javax.script.ScriptContext#getScopes()
	 */
	@Override
	public List<Integer> getScopes() {
		return Arrays.asList(ENGINE_SCOPE, GLOBAL_SCOPE);
	}

}
