package function.builtin;

import static java.text.MessageFormat.format;
import static table.FunctionTable.lookupFunction;

import error.LispException;
import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import function.LispSpecialFunction;
import function.UserDefinedFunction;
import sexpression.Cons;
import sexpression.SExpression;
import sexpression.Symbol;

@FunctionNames({ "SYMBOL-FUNCTION" })
public class SYMBOL_FUNCTION extends LispFunction {

    private ArgumentValidator argumentValidator;

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

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

        SExpression symbol = argumentList.getFirst();
        LispFunction function = lookupFunction(symbol.toString());

        if (function != null)
            return createRepresentation(symbol, function);

        throw new UndefinedSymbolFunctionException(symbol);
    }

    private SExpression createRepresentation(SExpression symbol, LispFunction function) {
        if (function instanceof UserDefinedFunction)
            return ((UserDefinedFunction) function).getLambdaExpression();

        String typeIndicator = function instanceof LispSpecialFunction ? "SPECIAL-FUNCTION" : "FUNCTION";

        return new Symbol(format("#<{0} {1}>", typeIndicator, symbol));
    }

    public static class UndefinedSymbolFunctionException extends LispException {

        private static final long serialVersionUID = 1L;
        private SExpression function;

        public UndefinedSymbolFunctionException(SExpression function) {
            this.function = function;
        }

        @Override
        public String getMessage() {
            return format("SYMBOL-FUNCTION: undefined function: {0}", function);
        }
    }

}
