package function.builtin.special;

import static function.builtin.EVAL.eval;
import static function.builtin.predicate.EQUAL.isEqual;
import static sexpression.Nil.NIL;
import static sexpression.Symbol.T;

import function.ArgumentValidator;
import function.FunctionNames;
import function.LispSpecialFunction;
import sexpression.Cons;
import sexpression.Nil;
import sexpression.SExpression;

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

    private ArgumentValidator argumentValidator;

    public CASE(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setMinimumNumberOfArguments(1);
        this.argumentValidator.setTrailingArgumentExpectedType(Cons.class);
        this.argumentValidator.setTrailingArgumentExcludedType(Nil.class);
    }

    @Override
    public SExpression call(Cons argumentList) {
        argumentValidator.validate(argumentList);
        SExpression key = eval(argumentList.getFirst());

        return callTailRecursive(key, (Cons) argumentList.getRest());
    }

    private SExpression callTailRecursive(SExpression key, Cons argumentList) {
        if (argumentList.isNull())
            return NIL;

        Cons clause = (Cons) argumentList.getFirst();
        Cons remainingClauses = (Cons) argumentList.getRest();
        SExpression keyList = clause.getFirst();

        if (isMatch(key, keyList))
            return evaluateConsequents(clause.getRest());

        return callTailRecursive(key, remainingClauses);
    }

    private boolean isMatch(SExpression key, SExpression keyList) {
        if (keyList.isNull())
            return false;
        else if (keyList.isCons())
            return containsMatch(key, keyList);

        return isEqual(key, keyList) || isEqual(T, keyList);
    }

    private boolean containsMatch(SExpression key, SExpression keyList) {
        for (; keyList.isCons(); keyList = advanceCons(keyList))
            if (isEqual(key, getFirst(keyList)))
                return true;

        return false;
    }

    private SExpression advanceCons(SExpression knownCons) {
        return ((Cons) knownCons).getRest();
    }

    private SExpression getFirst(SExpression knownCons) {
        return ((Cons) knownCons).getFirst();
    }

    private SExpression evaluateConsequents(SExpression consequentList) {
        SExpression lastConsequentValue = NIL;

        for (; consequentList.isCons(); consequentList = advanceCons(consequentList))
            lastConsequentValue = eval(getFirst(consequentList));

        return lastConsequentValue;
    }

}
