package function.builtin;

import static table.FunctionTable.lookupFunction;

import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import sexpression.Cons;
import sexpression.SExpression;
import sexpression.Symbol;
import table.ExecutionContext;
import table.SymbolTable;

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

    public static SExpression set(Cons argumentList) {
        return lookupFunction("SET").call(argumentList);
    }

    private ArgumentValidator argumentValidator;
    private ExecutionContext executionContext;

    public SET(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setExactNumberOfArguments(2);
        this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
        this.executionContext = ExecutionContext.getInstance();
    }

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

        Cons rest = (Cons) argumentList.getRest();
        SExpression symbol = argumentList.getFirst();
        SExpression value = rest.getFirst();

        SymbolTable table = findScopeOfSymbol(symbol);
        table.put(symbol.toString(), value);

        return value;
    }

    private SymbolTable findScopeOfSymbol(SExpression symbol) {
        SymbolTable table = executionContext.getScope();

        while (!isSymbolInTable(symbol, table) && !isGlobalTable(table))
            table = table.getParent();

        return table;
    }

    private boolean isSymbolInTable(SExpression symbol, SymbolTable table) {
        return table.contains(symbol.toString());
    }

    private boolean isGlobalTable(SymbolTable table) {
        return table.getParent() == null;
    }

}
