package cd.backend.codegen;

import cd.Config;
import cd.backend.ExitCode;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.CompileTimeEvaluator;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.MaybeC;

import java.util.Optional;

import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.RegisterManager.STACK_REG;

/**
 * Class with simple static checks mimicking the runtime system with the functions to check
 * possible errors at runtime. The purpose is to skip unnecessary calls to those functions
 */
public class CompileTimeChecks {
	protected final CfgCodeGenerator cfgCg;
	protected final AstCodeGeneratorRef cgRef;
	protected CompileTimeEvaluator cte;
	
	
	public CompileTimeChecks(AstCodeGeneratorRef cg, CfgCodeGenerator cfgCodeGenerator) {
		this.cfgCg = cfgCodeGenerator;
		this.cgRef = cg;
		cte = new CompileTimeEvaluator();
	}

	/**
	 * Convenience method that extracts the VariableSymbol from a Var or Field node.
	 * <br/> If the node is not one of those, the node must be a Index
	 */
	private VariableSymbol symbolOf(Expr expr) {
		if (expr instanceof Var)
			return ((Var) expr).sym;
		if (expr instanceof Field)
			return ((Field) expr).sym;
		// assert expr instanceof Index // only for local checks
		return null;
	}
	
	
	/** Returns whether a NewArray expression needs to check dynamically for the
	 * validity of the index (>=0). In case that it is known but invalid, a call
	 * to exit is done with the appropriate error code.
	 */
	public boolean checkArraySize(Ast.NewArray expr) {
		Optional<Integer> arraySize = cte.calc(expr.arg());
		if (!arraySize.isPresent()) {
			return true;
		}
		int arraySizeValue = arraySize.get();
		if (arraySizeValue < 0) {
			cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
			cgRef.emit.emit("call", Config.EXIT);
			return false;
		} 
		return false;
	}


	/** Returns whether a run-time check for the bounds of an array index is necessary.
	 * <br/> A check is only necessary if the index expression cannot be evaluated in
	 * compile time or the size of the array is unknown.
	 * <br/> If the value is known and it is invalid, an unconditional call to exit is
	 * performed with the appropriate exit code
	 */
	public boolean checkArrayBound(Index expr) {
		Optional<Integer> index = cte.calc(expr.right());
		if (!index.isPresent()) {
			return true;
		}
		VariableSymbol symbol = symbolOf(expr.left());
		if (!cfgCg.arraySizeState.containsKey(symbol)) {
			return true;
		}
		int indexValue = index.get();
		MaybeC<Integer> bound = cfgCg.arraySizeState.get(symbol);
		if (bound.isNotConstant()) {
			return true;
		}
		int boundValue = bound.getValue();
		if (indexValue < 0 || indexValue > (boundValue - 1)) {
			cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
			cgRef.emit.emit("call", Config.EXIT);
		}
		return false;
	}

	/**
	 * Returns true only when it is impossible for the expression (Field or Var) to be null.
	 * <br/> Currently doesn't check indexes.
	 */
	public boolean isNotNull(Expr expr) {
		if (expr instanceof ThisRef) {
			return false;
		} else {
			VariableSymbol symbol = symbolOf(expr);
			MaybeC<Boolean> isNull = cfgCg.nullState.get(symbol);
			return isNull != null && isNull.isConstant() && !isNull.getValue();
		}
	}
}
