package function.builtin.cons;

import static function.builtin.cons.LIST.makeList;
import static recursion.TailCalls.done;
import static recursion.TailCalls.tailCall;
import static table.FunctionTable.lookupFunction;

import java.math.BigInteger;

import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import recursion.TailCall;
import sexpression.Cons;
import sexpression.LispNumber;

@FunctionNames({ "LENGTH" })
public class LENGTH extends LispFunction {

    public static BigInteger getLength(Cons list) {
        LispNumber length = lookupLength().callWithoutArgumentValidation(makeList(list));

        return length.getValue();
    }

    private static LENGTH lookupLength() {
        return (LENGTH) lookupFunction("LENGTH");
    }

    private ArgumentValidator argumentValidator;

    public LENGTH(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setExactNumberOfArguments(1);
        this.argumentValidator.setEveryArgumentExpectedType(Cons.class);
    }

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

        return callTailRecursive(BigInteger.ZERO, argumentList).invoke();
    }

    private LispNumber callWithoutArgumentValidation(Cons argumentList) {
        return callTailRecursive(BigInteger.ZERO, argumentList).invoke();
    }

    private TailCall<LispNumber> callTailRecursive(BigInteger accumulatedLength, Cons argumentList) {
        Cons list = (Cons) argumentList.getFirst();
        Cons restOfList = makeList(list.getRest());

        if (list.isNull())
            return done(new LispNumber(accumulatedLength));

        return tailCall(() -> callTailRecursive(increment(accumulatedLength), restOfList));
    }

    private BigInteger increment(BigInteger number) {
        return number.add(BigInteger.ONE);
    }

}
