/**
 * Authors: k.inaba
 * License: NYSL 0.9982 http://www.kmonos.net/nysl/
 *
 * Evaluator for Polemy programming language.
 */
module polemy.eval;
import polemy._common;
import polemy.failure;
import polemy.ast;
import polemy.parse;
import polemy.value;
import polemy.layer;
import polemy.value;
import polemy.valueconv;
import std.signals;

/// Objects for maitaining global environment and evaluation of expression on it
class Evaluator
{
public:
	/// Initialize evaluator with empty context
	this() { theContext = new Table; }

	/// Evaluate the AST
	Value evalAST(AST e)
	{
		return macroAndEval(e, ValueLayer, theContext, OverwriteCtx);
	}

	/// Evaluate the string
	Value evalString(S,T...)(S str, T fn_ln_cn)
	{
		return evalAST(parseString(str,fn_ln_cn));
	}

	/// Evaluate the file
	Value evalFile(S,T...)(S filename, T ln_cn)
	{
		return evalAST(parseFile(filename,ln_cn));
	}

	/// Get the global context
	Table globalContext()
	{
		return theContext;
	}

private:
	Table theContext;

	enum : bool { CascadeCtx=false, OverwriteCtx=true };

	LayerEval getLayerEvaluator(Layer lay)
	{
		if( lay == ValueLayer )
			return new ValueLayerEval;
		if( lay == RawMacroLayer )
			return new RawMacroLayerEval;
		if( lay == MacroLayer )
			return new MacroLayerEval;
		return new UserDefinedLayerEval(lay);
	}

	abstract class LayerEval
	{
		/// Concrete layers should implement these
		Layer currentLayer();
		Value eval_( Die e, Table ctx, bool ctxMod );///
		Value eval_( Str e, Table ctx, bool ctxMod );///
		Value eval_( Int e, Table ctx, bool ctxMod );///
		Value eval_( Var e, Table ctx, bool ctxMod );///
		Value eval_( Lay e, Table ctx, bool ctxMod );///
		Value eval_( Let e, Table ctx, bool ctxMod );///
		Value eval_( App e, Table ctx, bool ctxMod );///
		Value eval_( Fun e, Table ctx, bool ctxMod );///

		/// dynamic-overload-resolution
		Value eval( AST e, Table ctx, bool ctxMod )
		{
			enum funName = "eval_";                 // modify here to customize
			alias TypeTuple!(e,ctx,ctxMod) params;  // modify here to customize

			alias typeof(__traits(getOverloads, this, funName)) ovTypes;
			alias staticMap!(firstParam, ovTypes)              fstTypes;
			alias DerivedToFront!(fstTypes)             fstTypes_sorted;
			foreach(i, T; fstTypes_sorted)
				static if( is(T == typeof(params[0])) ) {} else if( auto _x = cast(T)params[0] )
					return __traits(getOverloads, this, funName)[i](_x, params[1..$]);

			// modify here to customize the default behavior
			assert(false, text("eval() for ",typeid(e)," [",e.pos,"] is not defined"));
		}

		/// 
		Value invokeFunction(Value _f, AST[] args, Table ctx, LexPosition pos, string callstackmsg)
		{
			if(auto f = cast(FunValue)_f)
			{
				Table newCtx = new Table(f.definitionContext(), Table.Kind.NotPropagateSet);
				foreach(i,p; f.params())
					if( p.layers.empty ) {
						Value v = this.eval(args[i], ctx, CascadeCtx);
						if(v is null) v = ast2table(args[i]);
						newCtx.set(p.name, currentLayer(), v);
					}
					else
						foreach(argLay; p.layers) {
							Layer ll = argLay;
							if( isMacroLayer(argLay) && typeid(this)!=typeid(MacroLayerEval) )
								ll = RawMacroLayer; // explicit @macro invokes (rawmacro)
							Value v = getLayerEvaluator(ll).eval(args[i], ctx, CascadeCtx);
							if(v is null) v = ast2table(args[i]);
							newCtx.set(p.name, argLay, v);
						}
				scope _ = new PushCallStack(pos, callstackmsg);
				return f.invoke(currentLayer(), newCtx, pos);
			}
			throw genex!RuntimeException(pos, text("tried to call non-function: ",_f));
		}

		///
		Value lift(Value v, Table ctx, LexPosition pos)
		{
			Layer lay = currentLayer();

			// functions are automatically lifterd
			if( cast(FunValue) v )
				return v;

			if( !ctx.has(lay, LiftLayer) )
				throw genex!RuntimeException(pos, "lift function for "~lay~" is not registered" );

			// similar to invokeFunction, but with only one argument bound to ValueLayer
			auto _f = ctx.get(lay, LiftLayer, pos);
			if(auto f = cast(FunValue)_f)
			{
				Table newCtx = new Table(f.definitionContext(), Table.Kind.NotPropagateSet);
				auto ps = f.params();
				if( ps.length != 1 )
					throw genex!RuntimeException(pos,
						text("lift function for", lay, " must take exactly one argument of ", ValueLayer));
				if( ps[0].layers.length==0 || ps[0].layers.length==1 && ps[0].layers[0]==ValueLayer )
				{
					newCtx.set(ps[0].name, ValueLayer, v);
					scope _ = new PushCallStack(pos, lay);
					return f.invoke(ValueLayer, newCtx, pos);
				}
				else
					throw genex!RuntimeException(pos,
						text("lift function for", lay, " must take exactly one argument of ", ValueLayer));
			}
			throw genex!RuntimeException(pos,
				text("non-function ", _f, " is registered as the lift function for ", lay));
		}
	}

	/// Evaluator for standard @value semantics
	class ValueLayerEval : LayerEval
	{
		override Layer currentLayer()
		{
			return ValueLayer;
		}
		override Value eval_( Die e, Table ctx, bool ctxMod )
		{
			throw genex!RuntimeException(e.pos, "undefined case");
		}
		override Value eval_( Str e, Table ctx, bool ctxMod )
		{
			return new StrValue(e.data);
		}
		override Value eval_( Int e, Table ctx, bool ctxMod )
		{
			return new IntValue(e.data);
		}
		override Value eval_( Var e, Table ctx, bool ctxMod )
		{
			return ctx.get(e.name, currentLayer(), e.pos);
		}
		override Value eval_( Lay e, Table ctx, bool ctxMod )
		{
			auto le = getLayerEvaluator(e.layer);
			auto v  = le.eval(e.expr,ctx,CascadeCtx);			
			if( (v is null) && (cast(MacroLayerEval)le !is null) )
				return ast2table(e.expr);
			else
				return v;
		}
		override Value eval_( Let e, Table ctx, bool ctxMod )
		{
			Table newCtx = ctxMod ? ctx : new Table(ctx, Table.Kind.NotPropagateSet);
			Value ri = this.eval(e.init, newCtx, CascadeCtx);
			if(e.name!="_")
				newCtx.set(e.name, e.layer.empty ? currentLayer() : e.layer, ri);
			return this.eval(e.expr, newCtx, OverwriteCtx);
		}
		override Value eval_( App e, Table ctx, bool ctxMod )
		{
			Value f = this.eval( e.fun, ctx, CascadeCtx );
			return this.invokeFunction(f, e.args, ctx, e.pos, getNameIfPossible(e.fun));
		}
		override Value eval_( Fun e, Table ctx, bool ctxMod )
		{
			return createNewFunction(e, ctx);
		}
	}

	/// Evaluator for user-defined layer
	class UserDefinedLayerEval : ValueLayerEval
	{
		Layer layerID;
		mixin SimpleConstructor;

		override Layer currentLayer()
		{
			return layerID;
		}
		override Value eval_( Die e, Table ctx, bool ctxMod )
		{
			return new UndefinedValue;
		}
		override Value eval_( Str e, Table ctx, bool ctxMod )
		{
			return this.lift(new StrValue(e.data), ctx, e.pos);
		}
		override Value eval_( Int e, Table ctx, bool ctxMod )
		{
			return this.lift(new IntValue(e.data), ctx, e.pos);
		}
		override Value eval_( Var e, Table ctx, bool ctxMod )
		{
			if( ctx.has(e.name, currentLayer()) )
				return ctx.get(e.name, currentLayer());
			return this.lift(ctx.get(e.name, ValueLayer, e.pos), ctx, e.pos);
		}
	}

	// Convention!!
	//   returns null if never used macro-like feature
	class MacroLayerEval : LayerEval
	{
		override Layer currentLayer()
		{
			return MacroLayer;
		}
		override Value eval_( Die e, Table ctx, bool ctxMod )
		{
			return null;
		}
		override Value eval_( Str e, Table ctx, bool ctxMod )
		{
			return null;
		}
		override Value eval_( Int e, Table ctx, bool ctxMod )
		{
			return null;
		}
		override Value eval_( Var e, Table ctx, bool ctxMod )
		{
			if( ctx.has(e.name, currentLayer()) )
				return ctx.get(e.name, currentLayer(), e.pos);
			else
				return null;
		}
		override Value eval_( Lay e, Table ctx, bool ctxMod )
		{
			auto le = getLayerEvaluator(e.layer);
			return le.eval(e.expr,ctx,CascadeCtx);
		}
		override Value eval_( Let e, Table ctx, bool ctxMod )
		{
			Table newCtx = ctxMod ? ctx : new Table(ctx, Table.Kind.NotPropagateSet);
			Value ai = this.eval(e.init, newCtx, CascadeCtx);
			newCtx.set(e.name, NoopLayer, null);
			Value ae = this.eval(e.expr, newCtx, OverwriteCtx);
			if( ai is null && ae is null )
				return null;
			if( ai is null ) ai = ast2table(e.init);
			if( ae is null ) ae = ast2table(e.expr);
			return ast2table(e, delegate Value (AST _){
				if(_ is e.init) { return ai; }
				if(_ is e.expr) { return ae; }
				assert(false);
			});
		}
		override Value eval_( App e, Table ctx, bool ctxMod )
		{
			Value f = this.eval( e.fun, ctx, CascadeCtx );
			if(auto ff = cast(FunValue)f)
				return this.invokeFunction(ff, e.args, ctx, e.pos, getNameIfPossible(e.fun));
			else {
				bool allNull = (f is null);
				Value[] vas;
				foreach(a; e.args) {
					Value va = this.eval(a, ctx, CascadeCtx);
					if(va !is null) allNull = false;
					vas ~= va;
				}
				if( allNull )
					return null;
				return ast2table(e, delegate Value (AST _){
					if(_ is e.fun) return (f is null ? ast2table(e.fun) : f);
					foreach(i,a; e.args) if(_ is a) return (vas[i] is null ? ast2table(a) : vas[i]);
					assert(false);
				});
			}
		}
		override Value eval_( Fun e, Table ctx, bool ctxMod )
		{
			Table newCtx = new Table(ctx, Table.Kind.NotPropagateSet);
			foreach(p; e.params)
				newCtx.set(p.name, NoopLayer, null);
			Value af = this.eval(e.funbody, newCtx, CascadeCtx);
			if( af is null )
				return null;
			return ast2table(e, (AST _){if(_ is e.funbody)return af; assert(false);});
		}
	}

	class RawMacroLayerEval : MacroLayerEval
	{
		override Value eval_( Lay e, Table ctx, bool ctxMod )
		{
			Value ae = this.eval(e.expr, ctx, CascadeCtx);
			return ae is null ? null
			       : ast2table(e, delegate Value (AST _){if(_ is e.expr)return ae; assert(false);});
		}
	}

private:
	Value macroAndEval( AST e_, Layer lay, Table ctx, bool ctxMod )
	{
		assert( !isASTLayer(lay) );
		if(auto e = cast(Let)e_)
		{
			Value vai = getLayerEvaluator(RawMacroLayer).eval(e.init, ctx, CascadeCtx);
			AST ai = (vai is null ? e.init : polemy2d!(AST)(vai, e.pos));

			if( !ctxMod )
				ctx = new Table(ctx, Table.Kind.NotPropagateSet);

			Value vi = getLayerEvaluator(lay).eval(ai, ctx, CascadeCtx);
			string theLayer = e.layer.empty ? lay : e.layer;
			ctx.set(e.name, theLayer, vi);

			return macroAndEval( e.expr, lay, ctx, OverwriteCtx );
		}
		else
		{
			Value va = getLayerEvaluator(RawMacroLayer).eval(e_, ctx, ctxMod);
			AST a = (va is null ? e_ : polemy2d!(AST)(va, e_.pos));
			return getLayerEvaluator(lay).eval(a, ctx, ctxMod);
		}
	}

private:
	string getNameIfPossible(AST e)
	{
		if(auto v = cast(Var)e)
			return v.name;
		return "";
	}

	Value createNewFunction(Fun e, Table ctx)
	{
		class UserDefinedFunValue : FunValue
		{
			Fun   ast;
			Table defCtx;
			override const(Parameter[]) params() { return ast.params; }
			override Table definitionContext()   { return defCtx; }

			this(Fun ast, Table defCtx) { this.ast=ast; this.defCtx=defCtx; }
			override string toString() const
				{ return sprintf!"(function:%x:%x)"(cast(void*)ast, cast(void*)defCtx); }
			override int opCmp(Object rhs) {
				if(auto r = cast(UserDefinedFunValue)rhs) {
					if(auto c = typeid(void*).compare(cast(void*)ast, cast(void*)r.ast))
						return c;
					if(auto c = typeid(void*).compare(cast(void*)defCtx, cast(void*)r.defCtx))
						return c;
					return 0;// [TODO] avoid using pointer value...
				}
				if(auto r = cast(Value)rhs) return typeid(this).opCmp(typeid(r));
				throw genex!RuntimeException("comparison with value and something other");
			}
			override hash_t toHash() {
				return (cast(hash_t)cast(void*)ast) + (cast(hash_t)cast(void*)defCtx);
			}

			AST macroCache;
			AST[void*] mandeCache;
			static class MemokeyType
			{
				void* a; Layer b; Tuple!(string,Layer,Value)[] c;
				hash_t toHash() {
					hash_t h = structuralHash(a) + structuralHash(b);
					foreach(e; c)
						h += structuralHash(e[0])+structuralHash(e[1])+structuralHash(e[2]);
					return h;
				}
				mixin SimpleToString;
				mixin SimpleConstructor;
				mixin SimpleCompareWithoutToHash;
			}
			static Tuple!(Value,int)[MemokeyType] memo;

			override Value invoke(Layer lay, Table ctx, LexPosition pos)
			{
				if( isASTLayer(lay) ) {
					Value v = getLayerEvaluator(lay).eval(ast.funbody, ctx, CascadeCtx);
					if( v is null ) v = ast2table(ast.funbody);
					return v;
				}
				return macroAndEval(ast.funbody, lay, ctx, CascadeCtx);
/*
				auto nonMemoizedRun = (){
					if( macroCache is null )
					{
						auto va = macroAndEval(e.funbody, lay, ctx, CascadeCtx, mandeCache);
						macroCache = va[1];
						return va[0];
					}
					else
						return eval(macroCache, lay, ctx);
				};

				if( !isUserDefinedLayer(lay) )
					return nonMemoizedRun();

				MemokeyType memokey = new MemokeyType(cast(void*)ast, lay, ctx.direct_entries());

				if(auto p = memokey in memo)
				{
					(*p)[1] ++;
					return (*p)[0];
				}
				else
					memo[memokey] = tuple(lift(new UndefinedValue, lay, ctx, pos), 0);

				Value r = nonMemoizedRun();

				int touched = memo[memokey][1];
				memo[memokey] = tuple(r, 12345678);
				//if(touched) {DBG("rerun :: ",r);r = nonMemoizedRun();} // twice!!
				return r;
*/
			}
		}
		return new UserDefinedFunValue(e,ctx);
	}

public:
	/// Add primitive function to the global context
	void addPrimitive(R,T...)(string name, Layer defLay, R delegate (T) dg)
	{
		class NativeFunValue : FunValue
		{
			override const(Parameter[]) params() { return params_data; }
			override Table definitionContext()   { return theContext; }

			override string toString() { return sprintf!"(native:%x)"(dg.funcptr); }
			override int opCmp(Object rhs) {
				if(auto r = cast(NativeFunValue)rhs) return typeid(typeof(dg)).compare(&dg,&r.dg);
				if(auto r = cast(Value)rhs)          return typeid(this).opCmp(typeid(r));
				throw genex!RuntimeException("comparison with value and something other");
			}
			override hash_t toHash() const {
				return typeid(dg).getHash(&dg);
			}

			R delegate(T) dg;
			Parameter[] params_data;

			this(R delegate(T) dg)
			{
				this.dg = dg;
				foreach(i, Ti; T)
					params_data ~= new Parameter(text(i), []);
			}

			override Value invoke(Layer lay, Table ctx, LexPosition pos)
			{
				if( lay != defLay )
					throw genex!RuntimeException(pos,
						text("only ", defLay, " layer can call native function: ", name));
				T typed_args;
				foreach(i, Ti; T) {
					typed_args[i] = cast(Ti) ctx.get(text(i), ValueLayer, pos);
					if( typed_args[i] is null )
						throw genex!RuntimeException(pos,
							sprintf!"type mismatch on the argument %d of native function: %s"(i+1,name));
				}
				try {
					return dg(typed_args);
				} catch( RuntimeException e ) {
					throw e.pos is null ? new RuntimeException(pos, e.msg, e.file, e.line) : e;
				}
			}
		}
		theContext.set(name, defLay, new NativeFunValue(dg));
	}
}

version(unittest) import polemy.runtime;
unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	auto r = assert_nothrow( e.evalString(`var x = 21; x + x*x;`) );
	assert_eq( r, new IntValue(BigInt(21+21*21)) );
	assert_eq( e.globalContext.get("x",ValueLayer), new IntValue(BigInt(21)) );
	assert_nothrow( e.globalContext.get("x",ValueLayer) );
	assert_throw!RuntimeException( e.globalContext.get("y",ValueLayer) );
}
unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	auto r = assert_nothrow( e.evalString(`var x = 21; var x = x + x*x;`) );
	assert_eq( r, new IntValue(BigInt(21+21*21)) );
	assert_eq( e.globalContext.get("x",ValueLayer), new IntValue(21+21*21) );
	assert_nothrow( e.globalContext.get("x",ValueLayer) );
	assert_throw!RuntimeException( e.globalContext.get("y",ValueLayer) );
}
unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_eq( e.evalString(`let x=1; let y=(let x=2); x`), new IntValue(1) ); 
	assert_eq( e.evalString(`let x=1; let y=(let x=2;fun(){x}); y()`), new IntValue(2) ); 
}

unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_eq( e.evalString(`@a x=1; @b x=2; @a(x)`), new IntValue(BigInt(1)) );
	assert_eq( e.evalString(`@a x=1; @b x=2; @b(x)`), new IntValue(BigInt(2)) );
	assert_eq( e.evalString(`let x=1; let _ = (@a x=2;2); x`), new IntValue(BigInt(1)) );
	e = new Evaluator;
	assert_throw!Throwable( e.evalString(`let x=1; let _ = (@a x=2;2); @a(x)`) );
}

unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_eq( e.evalString(`
		@@s(x){x};
		@s "+" = fun(x, y) {@value(
			@s(x) - @s(y)
		)};
		@s(1 + 2)
	`), new IntValue(BigInt(-1)) );
}

unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_eq( e.evalString(`
@@3(x){x};
def incr(x) { x+1 };
@ 3 incr(x) {@value( if @ 3(x)+1< 3 then @3(x)+1 else 0 )};
def fb(n @value @3) { @3(n) };
fb(incr(incr(incr(0))))
	`), new IntValue(BigInt(0)) );
}

unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_nothrow( e.evalString(`
@macro twice(x) { x; x };
def main() { twice(1) };
main()
	`) );
}
unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_throw!RuntimeException( e.evalString(`case 1`) );
	assert_nothrow( e.evalString(`case 1 when 1: 2`) );

	// this is a shorthand for
	//   @macro x = fun(){} in @macro(x)
	// so it is ok to fail, but it is really incovenient on REPL
	assert_nothrow( e.evalString(`@macro x=fun(){}`) );
}

unittest
{
	auto e = new Evaluator;
	enrollRuntimeLibrary(e);
	assert_throw!RuntimeException( e.evalString(`...`) );
	assert_eq( e.evalString(`@@foo(x){x}; @foo(...)`), new UndefinedValue );
}
