package function.builtin.special;

import static function.builtin.EVAL.eval;

import function.*;
import sexpression.*;

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

    private ArgumentValidator argumentValidator;

    public COND() {
        this.argumentValidator = new ArgumentValidator("COND");
        this.argumentValidator.setEveryArgumentExpectedType(Cons.class);
        this.argumentValidator.doNotAcceptNil();
    }

    public SExpression call(Cons argumentList) {
        argumentValidator.validate(argumentList);

        return callTailRecursive(argumentList);
    }

    private SExpression callTailRecursive(Cons argumentList) {
        if (argumentList.isNull())
            return Nil.getInstance();

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

        if (isTestSuccessful(test))
            return evaluateResult(clause, test);

        return callTailRecursive(remainingClauses);
    }

    private boolean isTestSuccessful(SExpression test) {
        return test != Nil.getInstance();
    }

    private SExpression evaluateResult(Cons clause, SExpression test) {
        SExpression lastResultValue = test;

        for (SExpression result = clause.getRest(); result.isCons(); result = advanceCons(result))
            lastResultValue = eval(getFirst(result));

        return lastResultValue;
    }

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

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

}
