package function.builtin.math;

import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall;

import java.util.function.BiFunction;
import java.util.function.Function;

import recursion.TailCall;
import sexpression.Cons;
import sexpression.LispNumber;
import sexpression.SExpression;

class MathFunction {

    Function<LispNumber, LispNumber> singleValueOperation;
    BiFunction<LispNumber, LispNumber, LispNumber> multipleValueOperation;

    public MathFunction(Function<LispNumber, LispNumber> singleValueOperation,
                        BiFunction<LispNumber, LispNumber, LispNumber> multipleValueOperation) {
        this.singleValueOperation = singleValueOperation;
        this.multipleValueOperation = multipleValueOperation;
    }

    public TailCall<LispNumber> callTailRecursive(Cons argumentList) {
        Cons remainingArguments = (Cons) argumentList.getRest();
        SExpression firstArgument = argumentList.getFirst();
        LispNumber number1 = (LispNumber) firstArgument;

        if (remainingArguments.isNull())
            return done(singleValueOperation.apply(number1));

        SExpression secondArgument = remainingArguments.getFirst();
        LispNumber number2 = (LispNumber) secondArgument;
        LispNumber operationResult = multipleValueOperation.apply(number1, number2);
        SExpression remainingNumbers = remainingArguments.getRest();

        if (remainingNumbers.isNull())
            return done(operationResult);

        return tailCall(() -> callTailRecursive(new Cons(operationResult, remainingNumbers)));
    }

}
