package function.builtin.special;

import java.text.MessageFormat;

import environment.RuntimeEnvironment;
import error.*;
import function.*;
import function.builtin.cons.LIST;
import sexpression.*;
import table.FunctionTable;

public abstract class Define extends LispSpecialFunction {

    private ArgumentValidator argumentValidator;
    private ArgumentValidator lambdaListIsListValidator;
    private ArgumentValidator lambdaListValidator;
    private ErrorManager errorManager;

    public Define(String functionName) {
        this.argumentValidator = new ArgumentValidator(functionName);
        this.argumentValidator.setMinimumNumberOfArguments(2);
        this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);

        this.lambdaListIsListValidator = new ArgumentValidator(functionName + "|lambda-list|");
        this.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class);

        this.lambdaListValidator = new ArgumentValidator(functionName + "|parameter|");
        this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);

        this.errorManager = RuntimeEnvironment.getInstance().getErrorManager();
    }

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

        Cons remainingArguments = (Cons) argumentList.getRest();
        SExpression functionName = argumentList.getFirst();
        SExpression secondArgument = remainingArguments.getFirst();
        lambdaListIsListValidator.validate(LIST.makeList(secondArgument));

        Cons lambdaList = (Cons) secondArgument;
        lambdaListValidator.validate(lambdaList);

        Cons functionBody = (Cons) remainingArguments.getRest();
        UserDefinedFunction function = createFunction(functionName, lambdaList, functionBody);

        if (FunctionTable.isAlreadyDefined(functionName.toString()))
            errorManager.handle(new RedefiningFunctionWarning(functionName.toString()));

        FunctionTable.defineFunction(functionName.toString(), function);

        return functionName;
    }

    protected abstract UserDefinedFunction createFunction(SExpression functionName, Cons lambdaList, Cons functionBody);

    public class RedefiningFunctionWarning extends LispWarning {

        private static final long serialVersionUID = 1L;
        private String functionName;

        public RedefiningFunctionWarning(String functionName) {
            this.functionName = functionName;
        }

        @Override
        public String getMessage() {
            return MessageFormat.format("redefining function {0}", functionName);
        }
    }

}
