package magic.ast;

import magic.Type;
import magic.Types;
import magic.compiler.EvalResult;
import magic.compiler.SourceInfo;
import magic.data.APersistentList;
import magic.data.APersistentMap;
import magic.data.APersistentVector;
import magic.data.Lists;
import magic.data.Symbol;
import magic.fn.AFn;
import magic.fn.ArityException;
import magic.fn.IFn;
import magic.lang.Context;
import magic.lang.Symbols;
import magic.type.FunctionType;

/**
 * AST node representing a lambda expression a.k.a. "(fn [...] ...)"
 * 
 * @author Mike
 *
 * @param <T>
 */
public class Lambda<T> extends BaseForm<IFn<T>> {

	private final APersistentVector<Symbol> params;
	private final Node<T> body;
	private final int arity;
  
	@SuppressWarnings("unchecked")
	public Lambda(APersistentVector<Symbol> params, Node<T> body,SourceInfo source) {
		super((APersistentList<Node<?>>)(APersistentList<?>)Lists.of(Constant.create(Symbols.FN),Constant.create(params),body),body.getDependencies().excludeAll(params),source);
		this.params=params;
		this.arity=params.size();
		this.body=body;
	}
	
	
	public static <T> Lambda<T> create(APersistentVector<Symbol> params, Node<T> body) {
		return create(params,body,null);
	}
	
	public static <T> Lambda<T> create(APersistentVector<Symbol> params, Node<T> body,SourceInfo source) {
		return new Lambda<T>(params,body,source);
	}
	

	@SuppressWarnings("unchecked")
	public static <T> Lambda<T> create(Vector<Symbol> params, APersistentList<Node<?>> body, SourceInfo source) {
		APersistentVector<Symbol> alist=(APersistentVector<Symbol>) params.toForm();
		return create(alist,Do.create(body,source),source);
	}

	@Override
	public EvalResult<IFn<T>> eval(Context context,APersistentMap<Symbol, Object> bindings) {
		final APersistentMap<Symbol, Object> capturedBindings=bindings.delete(params);
		// capture variables defined in the current lexical scope
		Node<? extends T> body=this.body.specialiseValues(capturedBindings);
		
		// System.out.println(body);
		AFn<T> fn=new AFn<T>() {
			@Override
			public T applyToArray(Object... a) {
				if (a.length!=arity) throw new ArityException(arity,a.length);
				Context c=context;
				APersistentMap<Symbol, Object> bnds=capturedBindings;
				// add function arguments to the lexical bindings
				for (int i=0; i<arity; i++) {
					bnds=bnds.assoc(params.get(i), a[i]);
				}
				return body.compute(c,bnds);
			}	
			
			@Override
			public Type getReturnType() {
				return body.getType();
			}
		};
		return new EvalResult<IFn<T>>(context,fn);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Node<? extends IFn<T>> specialiseValues(APersistentMap<Symbol, Object> bindings) {
		bindings=bindings.delete(params); // hidden by argument bindings
		Node<? extends T> newBody=body.specialiseValues(bindings);
		// System.out.println("Defining lambda as "+newBody+" with bindings "+bindings);
		return (body==newBody)?this:(Lambda<T>) create(params,newBody,getSourceInfo());
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Node<? extends IFn<T>> optimise() {
		Node<? extends T> newBody=body.optimise();
		return (body==newBody)?this:(Lambda<T>) create(params,newBody,getSourceInfo());
	}

	
	/**
	 * Returns the type of this `lambda` expression, i.e. the a function type returning the type of the body
	 */
	@Override
	public Type getType() {
		int n=arity;
		Type[] argTypes=new Type[n];
		for (int i=0; i<n; i++) {
			// TODO: specialised argument types
			argTypes[i]=Types.ANYTHING;
		}
		return FunctionType.create(body.getType(),argTypes);
	}
	
	@Override
	public String toString() {
		return "(fn "+params+" "+body+")";
	}

}

@@ -334,29 +334,8 @@
 on;%0A
-import magic.fn.IFn;%0A
 impo
@@ -357,16 +357,16 @@
 ontext;%0A
+
 import m
@@ -571,17 +571,17 @@
 aseForm%3C
-I
+A
 Fn%3CT%3E%3E %7B
@@ -1637,25 +1637,25 @@
  EvalResult%3C
-I
+A
 Fn%3CT%3E%3E eval(
@@ -2474,17 +2474,17 @@
 lResult%3C
-I
+A
 Fn%3CT%3E%3E(c
@@ -2562,25 +2562,25 @@
 e%3C? extends 
-I
+A
 Fn%3CT%3E%3E speci
@@ -2998,17 +2998,17 @@
 extends 
-I
+A
 Fn%3CT%3E%3E o

Make Lambda return AFn instead of IFn