package cd.transform.analysis;

import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.NewArray;
import cd.ir.Ast.NewObject;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.NullAnalysis.NullVisitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;



public class AssignmentAnalysis extends MaybeFlowAnalysis<List<Ast.Assign>> {

	public AssignmentAnalysis(ControlFlowGraph cfg) {
		super(cfg, false, true);
	}

	@Override
	protected MaybeC<List<Ast.Assign>> defaultInit(VariableSymbol varSym) {
		List<Ast.Assign> value = new ArrayList<Ast.Assign>();
		return MaybeC.value(value);
	}
	
	/*
	 * the state contains all assignments that are unused and can be removed
	 * the last assign stmt in the list represents the current assignment of a variable
	 * 
	 * if assignment: check lhs:
	 * 	if lhs is variable, put assign stmt in state of the variable
	 * 	meaning that this assign stmt was not used yet
	 * 	visit rhs to check for used variables
	 * 
	 * if not assignment, visit ast node for all var symbols and remove the last assign stmt if var was used
	 */
	@Override
	public void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<List<Ast.Assign>>> state) {
		if (stmt instanceof Assign) {
			Assign assign = (Assign) stmt;
			if (assign.right() instanceof Ast.Var) {
				Ast.Var var = (Ast.Var) assign.left();
				VariableSymbol sym = var.sym;
				if (sym == null) return;
				MaybeC<List<Assign>> maybeStateList = state.get(sym);
				List<Ast.Assign> stateList = maybeStateList.getValue();
				stateList.add(assign);
				MaybeC<List<Assign>> newMaybeStateList = MaybeC.value(stateList);
				state.put(sym, newMaybeStateList);
			}
			removeAssignStmt(assign.right(), state);
			return;
		}
		// TODO: if not assign stmt visit node and check for used variables
		if (stmt instanceof Ast.BuiltInWrite) {
			Ast.BuiltInWrite write = (Ast.BuiltInWrite) stmt;
			removeAssignStmt(write.arg(), state);
		}
	}
	
	// iterates over all var symbols and checks if one was used
	// if so, remove last assignment
	public void removeAssignStmt(Ast.Expr ast, Map<VariableSymbol, MaybeC<List<Ast.Assign>>> state) {
		for (VariableSymbol varSym: state.keySet()) {
			Boolean isused = new varVisitor().visit(ast, varSym);
			if (isused) {
				MaybeC<List<Assign>> maybeStateList = state.get(varSym);
				List<Ast.Assign> stateList = maybeStateList.getValue();
				if (!stateList.isEmpty()) {
					int end = stateList.size() - 1;
					stateList.remove(end);
				}
				MaybeC<List<Assign>> newMaybeStateList = MaybeC.value(stateList);
				state.put(varSym, newMaybeStateList);
			}
		}
		return;
	}




// returns true if a given variable is used inside an Ast node
static class varVisitor extends ExprVisitor<Boolean, VariableSymbol> {

	@Override
	public Boolean var(Ast.Var ast, VariableSymbol var) {
		return ast.sym == var;
	}

}

}
