package Models.Statements;

import Exceptions.BaseException;
import Models.Expressions.IExpression;
import Models.Program.IDictionary;
import Models.Program.ProgramState;
import Models.Types.IType;
import Models.Types.ReferenceType;
import Models.Values.ReferenceValue;

public class WriteHeap implements IStatement {
    private String variableName;
    private IExpression expression;

    public WriteHeap(String variableName, IExpression expression) {
        this.variableName = variableName;
        this.expression = expression;
    }

    public ProgramState execute(ProgramState state) throws BaseException {
        var symbolTable = state.getSymbolTable();
        var heap = state.getHeap();
        if(!symbolTable.contains(this.variableName)) {
            throw new BaseException("Variable " + this.variableName + " is not defined");
        }
        if(!(symbolTable.get(this.variableName) instanceof ReferenceValue)) {
            throw new BaseException("Variable " + this.variableName + " is not a reference value");
        }
        int address = ((ReferenceValue)symbolTable.get(this.variableName)).getAddress();
        if(!heap.contains(address)) {
            throw new BaseException("Address " + address + " is not in the heap");
        }
        var heapValue = heap.get(address);
        var expressionValue = this.expression.evaluate(symbolTable, heap);
        if(!heapValue.getType().equals(expressionValue.getType())) {
            throw new BaseException("Types do not match");
        }
        heap.update(address, expressionValue);
        return null;
    }

    public IStatement copy() {
        return new WriteHeap(this.variableName, this.expression.copy());
    }

    public String toString() {
        return "writeHeap(" + this.variableName + ", " + this.expression.toString() + ")";
    }

    public IDictionary<String, IType> typeCheck(IDictionary<String, IType> typeTable) throws BaseException {
        var variableType = typeTable.get(this.variableName);
        var expressionType = this.expression.typeCheck(typeTable);
        if(!variableType.equals(new ReferenceType(expressionType))) {
            throw new BaseException("Types do not match");
        }
        return typeTable;
    }
}
