package name.panitz.fun4u.visitor;

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

import name.panitz.fun4u.machine.Add;
import name.panitz.fun4u.machine.Case;
import name.panitz.fun4u.machine.CaseJump;
import name.panitz.fun4u.machine.End;
import name.panitz.fun4u.machine.Eq;
import name.panitz.fun4u.machine.FunJump;
import name.panitz.fun4u.machine.Goto;
import name.panitz.fun4u.machine.Instruction;
import name.panitz.fun4u.machine.Jump;
import name.panitz.fun4u.machine.Kellermaschine;
import name.panitz.fun4u.machine.Lt;
import name.panitz.fun4u.machine.Gt;
import name.panitz.fun4u.machine.Mult;
import name.panitz.fun4u.machine.Push;
import name.panitz.fun4u.machine.PushC;
import name.panitz.fun4u.machine.PushInt;
import name.panitz.fun4u.machine.Return;
import name.panitz.fun4u.machine.Slide;
import name.panitz.fun4u.machine.Sub;
import name.panitz.fun4u.tree.App;
import name.panitz.fun4u.tree.CaseExpr;
import name.panitz.fun4u.tree.ConstructorDef;
import name.panitz.fun4u.tree.Definition;
import name.panitz.fun4u.tree.Exp;
import name.panitz.fun4u.tree.FunctionDef;
import name.panitz.fun4u.tree.IfExp;
import name.panitz.fun4u.tree.IntLiteral;
import name.panitz.fun4u.tree.OpExp;
import name.panitz.fun4u.tree.Pack;
import name.panitz.fun4u.tree.Pair;
import name.panitz.fun4u.tree.Param;
import name.panitz.fun4u.tree.Program;
import name.panitz.fun4u.tree.Type;
import name.panitz.fun4u.tree.Var;

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> constructorTable=null; 
	
	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) {
		for (Exp arg:app.args){
			arg.welcome(this);
		}
		Map<String, Integer> oldEnv= localEnv;
		int oldOffset= offset;
		
		prog.add(new PushC(prog.size()+2));
		prog.add(new FunJump(app.functionName));
		localEnv=oldEnv;
		offset=oldOffset-app.args.size()+1;	
	}

	@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 Gt());
		}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 Return());
	}

	@Override
	public void visit(Program program) {
		CollectConstructors collector = new CollectConstructors();
		program.welcome(collector);
		constructorTable=collector.constructorTable;
		MakeConstructorcalls mkConstr = new MakeConstructorcalls(constructorTable);
		program.welcome(mkConstr);
		
		program.e.welcome(this);
		prog.add(new End());
		for (Definition fun:program.definitions){
			fun.welcome(this);
		}
		instr=new Instruction[prog.size()];
		int i=0;
		for (Instruction stat:prog){
			if (stat instanceof Case){
				Case ca = (Case)stat;
				ca.adr=funTable.get(ca.funName);
				instr[i]=stat;
			}else 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;
		}	
		System.out.println(funTable);
		new Kellermaschine(instr).run();
	}

	@Override
	public void visit(CaseExpr caseExpr) {
		caseExpr.e.welcome(this);
		PushC zurück = new PushC(0);
		prog.add(zurück);
		for (Pair<String,String>p:caseExpr.cases){
			prog.add(new Case(p.fst,constructorTable.get(p.fst),p.snd));			
		}
		zurück.n = prog.size();
	}

	@Override
	public void visit(ConstructorDef constructorDef) {
	}
	
	@Override
	public void visit(Pack pack) {
		for (Exp e:pack.args){
			e.welcome(this);
		}
		prog.add(new name.panitz.fun4u.machine.Pack(pack.name,pack.args.size()));
	}
}
