package function.builtin.predicate;

import function.*;
import function.builtin.cons.LIST;
import sexpression.*;

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

    private ArgumentValidator argumentValidator;

    public EQUAL() {
        this.argumentValidator = new ArgumentValidator("EQUAL");
        this.argumentValidator.setExactNumberOfArguments(2);
    }

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

        return callRecursive(argumentList);
    }

    private SExpression callRecursive(Cons argumentList) {
        Cons rest = (Cons) argumentList.getRest();
        SExpression firstArgument = argumentList.getFirst();
        SExpression secondArgument = rest.getFirst();

        if (!isListPair(firstArgument, secondArgument))
            return equal(firstArgument, secondArgument);

        Cons listOne = (Cons) firstArgument;
        Cons listTwo = (Cons) secondArgument;
        SExpression listOneFirst = listOne.getFirst();
        SExpression listTwoFirst = listTwo.getFirst();
        SExpression listOneRest = listOne.getRest();
        SExpression listTwoRest = listTwo.getRest();

        SExpression firstEqual = callRecursive(makeArgumentList(listOneFirst, listTwoFirst));
        SExpression restEqual = callRecursive(makeArgumentList(listOneRest, listTwoRest));

        return logicalConjunction(firstEqual, restEqual);
    }

    private boolean isListPair(SExpression firstArgument, SExpression secondArgument) {
        return firstArgument.isCons() && secondArgument.isCons();
    }

    private SExpression equal(SExpression firstArgument, SExpression secondArgument) {
        return isEqual(firstArgument, secondArgument) ? Symbol.T : Nil.getInstance();
    }

    private boolean isEqual(SExpression firstArgument, SExpression secondArgument) {
        return firstArgument.toString().equals(secondArgument.toString());
    }

    private Cons makeArgumentList(SExpression listOneFirst, SExpression listTwoFirst) {
        return new Cons(listOneFirst, LIST.makeList(listTwoFirst));
    }

    private SExpression logicalConjunction(SExpression firstEqual, SExpression restEqual) {
        return bothAreTrue(firstEqual, restEqual) ? Symbol.T : Nil.getInstance();
    }

    private boolean bothAreTrue(SExpression firstEqual, SExpression restEqual) {
        return (firstEqual == Symbol.T) && (restEqual == Symbol.T);
    }

}
