package cd.backend.codegen;

import cd.Main;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.Stmt;
import cd.ir.Ast;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.ArraySizeAnalysis;
import cd.transform.analysis.AssignmentAnalysis;
import cd.transform.analysis.DynamicTypeAnalysis;
import cd.transform.analysis.MaybeC;
import cd.transform.analysis.NullAnalysis;

import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CfgCodeGenerator {

	public final Main main;
	private final AstCodeGeneratorRef cg;
	public final CompileTimeChecks check;

	protected Map<VariableSymbol, MaybeC<Integer>> arraySizeState;
	protected Map<VariableSymbol, MaybeC<Boolean>> nullState;
	protected Map<VariableSymbol, MaybeC<TypeSymbol>> dynamicTypeState;
	//protected Map<VariableSymbol, MaybeC<List<Ast.Assign>>> unusedAssignmentsState;

	public CfgCodeGenerator(Main main, Writer out) {
		this.main = main;
		cg = new AstCodeGeneratorNop90(main, out, this);
		check = new CompileTimeChecks(cg, this);
	}

	public void go(List<? extends ClassDecl> astRoots) {
		cg.emitPrefix(astRoots);
		for (ClassDecl cdecl : astRoots)
			new CfgStmtVisitor().visit(cdecl, null);
	}

	private class CfgStmtVisitor extends AstVisitor<Void, Void> {

		@Override
		public Void classDecl(ClassDecl ast, Void arg) {
			cg.emit.emitCommentSection("Class " + ast.name);
			cg.emit.increaseIndent("");
			super.classDecl(ast, arg);
			cg.emit.decreaseIndent();
			return null;
		}

		@Override
		public Void methodDecl(MethodDecl ast, Void arg) {
			cg.emitMethodPrefix(ast);

			ControlFlowGraph cfg = ast.cfg;
			assert cfg != null;

			Map<BasicBlock, String> labels = new HashMap<BasicBlock, String>();
			for (BasicBlock blk : cfg.allBlocks)
				labels.put(blk, cg.emit.uniqueLabel());
			String exitLabel = cg.emit.uniqueLabel();

			cg.emit.emit("jmp", labels.get(cfg.start));

			// Run analysis here to have the relevant results to this cfg object
			ArraySizeAnalysis arraySizeAnalysis = new ArraySizeAnalysis(cfg);
			NullAnalysis nullAnalysis = new NullAnalysis(cfg);
			DynamicTypeAnalysis dynamicTypeAnalysis = new DynamicTypeAnalysis(cfg);
			//AssignmentAnalysis assignmentAnalysis = new AssignmentAnalysis(cfg);

			for (BasicBlock blk : cfg.allBlocks) {
				arraySizeState = arraySizeAnalysis.inStateOf(blk);
				nullState = nullAnalysis.inStateOf(blk);
				dynamicTypeState = dynamicTypeAnalysis.inStateOf(blk);
				//unusedAssignmentsState = assignmentAnalysis.inStateOf(blk);
				assert nullState != null;
				assert arraySizeState != null;
				assert dynamicTypeState != null;
				//assert unusedAssignmentsState != null;

				cg.emit.emitCommentSection("Basic block " + blk.index);
				cg.emit.emitLabel(labels.get(blk));

				for (Stmt stmt : blk.stmts) {
					cg.sg.gen(stmt);
					//assignmentAnalysis.transferStmt(stmt, unusedAssignmentsState);
					arraySizeAnalysis.transferStmt(stmt, arraySizeState);
					nullAnalysis.transferStmt(stmt, nullState);
					dynamicTypeAnalysis.transferStmt(stmt, dynamicTypeState);
				}

				if (blk == cfg.end) {
					cg.emit.emitComment(String.format("Return"));
					assert blk.successors.size() == 0;
					cg.emit.emit("jmp", exitLabel);
				} else if (blk.condition != null) {
					assert blk.successors.size() == 2;
					cg.emit.emitComment(String.format(
							"Exit to block %d if true, block %d if false",
							blk.trueSuccessor().index, blk.falseSuccessor().index));
					cg.genJumpIfFalse(blk.condition, labels.get(blk.falseSuccessor()));
					cg.emit.emit("jmp", labels.get(blk.trueSuccessor()));
				} else {
					cg.emit.emitComment(String.format(
							"Exit to block %d", blk.successors.get(0).index));
					assert blk.successors.size() == 1;
					cg.emit.emit("jmp", labels.get(blk.successors.get(0)));
				}
			}

			cg.emit.emitLabel(exitLabel);
			if (ast.sym.returnType.equals(PrimitiveTypeSymbol.voidType))
				cg.emitMethodSuffix(true);
			else
				cg.emitMethodSuffix(true);

			return null;
		}

	}
}

