package function.builtin.special;

import static function.builtin.cons.LIST.makeList;

import function.ArgumentValidator;
import function.FunctionNames;
import function.LispSpecialFunction;
import function.UserDefinedFunction;
import sexpression.Cons;
import sexpression.LambdaExpression;
import sexpression.SExpression;
import sexpression.Symbol;

@FunctionNames({ "LAMBDA" })
public class LAMBDA extends LispSpecialFunction {

    public static boolean isLambdaExpression(SExpression sexpr) {
        if (sexpr.isCons()) {
            SExpression first = ((Cons) sexpr).getFirst();

            return "LAMBDA".equals(first.toString());
        }

        return false;
    }

    public static UserDefinedFunction createFunction(Cons lambdaExpression) {
        SExpression rest = lambdaExpression.getRest();

        ArgumentValidator lambdaValidator = new ArgumentValidator("LAMBDA|create|");
        lambdaValidator.setEveryArgumentExpectedType(Cons.class);
        lambdaValidator.validate(makeList(rest));

        LambdaExpression lambda = new LAMBDA("LAMBDA").call((Cons) rest);

        return lambda.getFunction();
    }

    private ArgumentValidator argumentValidator;
    private ArgumentValidator lambdaListValidator;

    public LAMBDA(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setFirstArgumentExpectedType(Cons.class);
        this.argumentValidator.setMinimumNumberOfArguments(1);

        this.lambdaListValidator = new ArgumentValidator(name + "|lambda-list|");
        this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
    }

    @Override
    public LambdaExpression call(Cons argumentList) {
        argumentValidator.validate(argumentList);

        SExpression first = argumentList.getFirst();
        Cons lambdaList = (Cons) first;
        Cons body = (Cons) argumentList.getRest();

        lambdaListValidator.validate(lambdaList);

        UserDefinedFunction function = new UserDefinedFunction(":LAMBDA", lambdaList, body);

        return new LambdaExpression(makeOriginalLambdaExpression(argumentList), function);
    }

    private Cons makeOriginalLambdaExpression(Cons argumentList) {
        return new Cons(new Symbol("LAMBDA"), argumentList);
    }

}
