package function.builtin.special;

import static function.builtin.EVAL.evaluateFunctionArgumentList;

import error.LispException;
import function.ArgumentValidator;
import function.FunctionNames;
import function.LispSpecialFunction;
import sexpression.Cons;
import sexpression.SExpression;
import table.ExecutionContext;

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

    private ArgumentValidator argumentValidator;
    private ExecutionContext executionContext;

    public RECUR(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.executionContext = ExecutionContext.getInstance();
    }

    @Override
    public SExpression call(Cons argumentList) {
        verifyValidRecurCall();
        argumentValidator.validate(argumentList);
        Cons recurArguments = getRecurArguments(argumentList);
        executionContext.setRecur();

        return recurArguments;
    }

    private void verifyValidRecurCall() {
        if (!executionContext.isInFunctionCall())
            throw new RecurOutsideOfFunctionException();

        if (executionContext.isRecurInitializing())
            throw new NestedRecurException();
    }

    private Cons getRecurArguments(Cons argumentList) {
        Cons recurArguments = argumentList;

        try {
            executionContext.setRecurInitializing();

            if (isRecurArgumentListEvaluated())
                recurArguments = evaluateFunctionArgumentList(argumentList);
        } finally {
            executionContext.clearRecurInitializing();
        }

        return recurArguments;
    }

    private boolean isRecurArgumentListEvaluated() {
        return executionContext.getCurrentFunction().isArgumentListEvaluated();
    }

    public static class RecurOutsideOfFunctionException extends LispException {

        private static final long serialVersionUID = 1L;

        @Override
        public String getMessage() {
            return "recur called outide of function";
        }
    }

    public static class NestedRecurException extends LispException {

        private static final long serialVersionUID = 1L;

        @Override
        public String getMessage() {
            return "nested call to recur";
        }
    }

    public static class RecurNotInTailPositionException extends LispException {

        private static final long serialVersionUID = 1L;

        @Override
        public String getMessage() {
            return "recur not in tail position";
        }
    }

}
