import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class GenCode implements Visitor {
	List<Instruction> prog= new LinkedList<Instruction>();
	Instruction[] instr=null;
	
	Map<String,Integer> funTable = new HashMap<String, Integer>(); 
	Map<String,Integer> localEnv = new HashMap<String, Integer>();
	int offset;
	
	@Override
	public void visit(Type type) {
	}

	@Override
	public void visit(Param param) {
	}

	@Override
	public void visit(IfExp ifExp) {
		ifExp.cond.welcome(this);
		CaseJump casejump=new CaseJump(0);
		offset=offset-1;
		prog.add(casejump);
		int thenTeil=prog.size();
		int oldOffset=offset;
		ifExp.alt1.welcome(this);
		Jump jump=new Jump(0);
		prog.add(jump);
		int elseTeil=prog.size();
		offset=oldOffset;
		ifExp.alt2.welcome(this);
		int endeIf=prog.size();
		casejump.jump=elseTeil-thenTeil+1;
		jump.jump=endeIf-elseTeil+1;
	}

	@Override
	public void visit(App app) {
		PushInt zurück = new PushInt(-1);
		prog.add(zurück);
		offset=offset+1;

		for (Exp arg:app.args){
			arg.welcome(this);
		}
		Map<String, Integer> oldEnv= localEnv;
		int oldOffset= offset;
		prog.add(new FunJump(app.functionName));
		zurück.n=prog.size();
		localEnv=oldEnv;
		offset=oldOffset-app.args.size();	
	}

	@Override
	public void visit(OpExp opExp) {
		opExp.left.welcome(this);
		opExp.right.welcome(this);
		if (opExp.name.equals("+")){
			prog.add(new Add());
		}else if (opExp.name.equals("-")){
			prog.add(new Sub());
		}else if (opExp.name.equals("*")){
			prog.add(new Mult());
		}else if (opExp.name.equals("<")){
			prog.add(new Lt());
		}else if (opExp.name.equals("==")){
			prog.add(new Eq());
		}else{
			throw new RuntimeException("unknown operator "+opExp.name);
		}
		offset=offset-1;
	}

	@Override
	public void visit(Var var) {
		prog.add(new Push(localEnv.get(var.name)+offset));
		offset=offset+1;
	}

	@Override
	public void visit(IntLiteral intLiteral) {
		prog.add(new PushInt(intLiteral.n));
		offset=offset+1;
	}

	@Override
	public void visit(FunctionDef functionDef) {
		funTable.put(functionDef.name,prog.size());
		localEnv= new HashMap<String, Integer>();
		int i=functionDef.params.size()-1;
		for(Param param:functionDef.params){
			localEnv.put(param.name, i);
			i=i-1;
		}
		offset=0;
		functionDef.body.welcome(this);
		prog.add(new Slide(functionDef.params.size()));
		prog.add(new Swap());
		prog.add(new Return());
	}

	@Override
	public void visit(Program program) {
		program.e.welcome(this);
		prog.add(new End());
		for (FunctionDef fun:program.functions){
			fun.welcome(this);
		}
		instr=new Instruction[prog.size()];
		int i=0;
		for (Instruction stat:prog){
			if (stat instanceof FunJump){
				FunJump fj = (FunJump)stat;
				instr[i]=new Goto(funTable.get(fj.functionName));
			}else{
				instr[i]=stat;
			}
			System.out.println(i+": "+instr[i]);
			i=i+1;
		}	
		new Kellermaschine(instr).run();
	}
}
