package de.dhbwstuttgart.bytecode;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.syntaxtree.statement.*;
import de.dhbwstuttgart.syntaxtree.statement.BinaryExpr.Operator;
import de.dhbwstuttgart.syntaxtree.statement.UnaryExpr.Operation;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;

import de.dhbwstuttgart.bytecode.Exception.NotInCurrentPackageException;
import de.dhbwstuttgart.bytecode.descriptor.DescriptorToString;
import de.dhbwstuttgart.bytecode.descriptor.TypeToDescriptor;
import de.dhbwstuttgart.bytecode.signature.Signature;
import de.dhbwstuttgart.bytecode.signature.TypeToSignature;
import de.dhbwstuttgart.bytecode.utilities.KindOfLambda;
import de.dhbwstuttgart.bytecode.utilities.Lambda;
import de.dhbwstuttgart.bytecode.utilities.MethodCallHelper;
import de.dhbwstuttgart.bytecode.utilities.MethodFromMethodCall;
import de.dhbwstuttgart.bytecode.utilities.Resolver;
import de.dhbwstuttgart.bytecode.utilities.SamMethod;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.AssignToLocal;
import de.dhbwstuttgart.syntaxtree.AbstractASTWalker;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.StatementVisitor;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import javassist.NotFoundException;

public class BytecodeGenMethod implements StatementVisitor {

	private Resolver resolver;
	private Method m;
	private MethodVisitor mv;
	private HashMap<String, Integer> paramsAndLocals = new HashMap<>();
	private String className;
	private int lamCounter = -1;
	private ClassWriter cw;
	private ResultSet resultSet;
	private boolean isInterface;
	private HashMap<String, String> genericsAndBoundsMethod;
	private HashMap<String, String> genericsAndBounds;
	public boolean isBinaryExp = false;
	private String superClass;
	private String path;
	private SourceFile sf;
	private IStatement statement = null;
	private boolean isReturnStmt = false;
	private boolean isParentBinary = false;
	
	private boolean needDUP = false;
	
	private Block blockFieldInit = null;
	private boolean isBlockFieldInitVisited = false;
	// for tests **
	private String fieldName;
	private String fieldDesc;
	private Expression rightSideTemp;

	private boolean isRightSideALambda = false;
	private KindOfLambda kindOfLambda;
	private HashMap<String, byte[]> classFiles;
	
	private int constructorPos = 0;

	private ArrayList<RefTypeOrTPHOrWildcardOrGeneric> varsFunInterface = new ArrayList<>();;
	// generate bytecode for constructor
	public BytecodeGenMethod(String className, String superClass,ResultSet resultSet, Method m, MethodVisitor mv,
			HashMap<String, Integer> paramsAndLocals, ClassWriter cw, HashMap<String, String> genericsAndBoundsMethod,
			HashMap<String, String> genericsAndBounds, boolean isInterface, HashMap<String, byte[]> classFiles, 
			SourceFile sf,String path, Block block, int constructorPos) {

		this.className = className;
		this.superClass = superClass;
		this.resultSet = resultSet;
		this.resolver = new Resolver(resultSet);
		this.m = m;
		this.mv = mv;
		this.paramsAndLocals = paramsAndLocals;
		this.cw = cw;
		this.genericsAndBoundsMethod = genericsAndBoundsMethod;
		this.genericsAndBounds = genericsAndBounds;
		this.isInterface = isInterface;
		this.classFiles = classFiles;
		this.sf = sf;
		this.path = path;
		this.constructorPos = constructorPos;
		if(block != null)
			this.blockFieldInit = block;
		this.m.block.accept(this);
		
		
	}
	
	public BytecodeGenMethod(String className, String superClass,ResultSet resultSet, Method m, MethodVisitor mv,
			HashMap<String, Integer> paramsAndLocals, ClassWriter cw, HashMap<String, String> genericsAndBoundsMethod,
			HashMap<String, String> genericsAndBounds, boolean isInterface, HashMap<String, byte[]> classFiles, SourceFile sf,String path) {

		this.className = className;
		this.superClass = superClass;
		this.resultSet = resultSet;
		this.resolver = new Resolver(resultSet);
		this.m = m;
		this.mv = mv;
		this.paramsAndLocals = paramsAndLocals;
		this.cw = cw;
		this.genericsAndBoundsMethod = genericsAndBoundsMethod;
		this.genericsAndBounds = genericsAndBounds;
		this.isInterface = isInterface;
		this.classFiles = classFiles;
		this.sf = sf;
		this.path = path;
		
		if (!isInterface)
			this.m.block.accept(this);

	}

	public BytecodeGenMethod(LambdaExpression lambdaExpression, ArrayList<String> usedVars, ResultSet resultSet, MethodVisitor mv,
			int indexOfFirstParamLam, boolean isInterface, HashMap<String, byte[]> classFiles, String path, int lamCounter, SourceFile sf,HashMap<String, String> genericsAndBoundsMethod,
			HashMap<String, String> genericsAndBounds) {

		this.resultSet = resultSet;
		this.resolver = new Resolver(resultSet);
		this.mv = mv;
		this.isInterface = isInterface;
		this.classFiles = classFiles;
		this.path = path;
		this.lamCounter = lamCounter;
		this.sf = sf;
		this.genericsAndBoundsMethod = genericsAndBoundsMethod;
		this.genericsAndBounds = genericsAndBounds;
		
		Iterator<FormalParameter> itr = lambdaExpression.params.iterator();
		int i = indexOfFirstParamLam;
		
		for(String var : usedVars) {
			this.paramsAndLocals.put(var, i);
			i++;
		}
		
		while (itr.hasNext()) {
			FormalParameter fp = itr.next();
			this.paramsAndLocals.put(fp.getName(), i);
			i++;
		}
		lambdaExpression.methodBody.accept(this);
	}
	
	public Resolver getResolver() {
		return resolver;
	}
	
	public void isBinary(boolean isBinary) {
		this.isBinaryExp =isBinary;
	}
	

	@Override
	public void visit(Block block) {
		for (Statement stmt : block.getStatements()) {
			stmt.accept(this);
			if(stmt instanceof MethodCall) {
				String ret = resolver.getResolvedType(((MethodCall) stmt).getType());
				if(!ret.equals("void"))
					mv.visitInsn(Opcodes.POP);
			}
		}
	}

	@Override
	public void visit(SuperCall superCall) {
		superCall.receiver.accept(this);
		superCall.arglist.accept(this);
		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, this.superClass, superCall.name, "()V",
				isInterface);
		
		if(blockFieldInit!=null && !isBlockFieldInitVisited) {
			isBlockFieldInitVisited =true;
			//blockFieldInit.accept(this);
			for(Statement stmt : blockFieldInit.statements) {
				if(stmt instanceof SuperCall)
					continue;
				
				stmt.accept(this);
			}
		}
	}

	// ??
	@Override
	public void visit(LocalVar localVar) {
		// wenn String + String zuerst wird ein StringBuilder initialisiert dann
		// wird die lokale Var geladen. Sonst wird zuerst die lokale Var geladen.
		System.out.println(localVar.name);
		mv.visitVarInsn(Opcodes.ALOAD, paramsAndLocals.get(localVar.name));
		
		if (isBinaryExp) {
			doUnboxing(resolver.getResolvedType(localVar.getType()));
		}
	}

	// ??
	@Override
	public void visit(LocalVarDecl localVarDecl) {

	}

	@Override
	public void visit(Assign assign) {
		statement = new AssignStmt(assign.rightSide);
		// if the right side is a lambda => the left side must be a functional interface
		if (assign.rightSide instanceof LambdaExpression) {
			isRightSideALambda = true;
		} else {
			isRightSideALambda = false;
		}

		isBinaryExp = statement.isExprBinary();

		if (assign.lefSide instanceof AssignToField) {
			// load_0, ldc or .. then putfield
			this.rightSideTemp = assign.rightSide;
		} else {
			assign.rightSide.accept(this);
		}
		
		statement = new AssignStmt(assign.rightSide);
		isBinaryExp = statement.isExprBinary();
		
		if (isBinaryExp) {
			BinaryExpr binary = (BinaryExpr) assign.rightSide;
			String binaryType = resolver.getResolvedType(binary.getType());
			doBoxing(binaryType);
			isBinaryExp = false;
		}

		System.out.println("ASSIGN TYPE R: " + resolver.getResolvedType(assign.rightSide.getType()));
		String typeOfRightSide = resolver.getResolvedType(assign.rightSide.getType());
		if(typeOfRightSide.contains("<")) {
			mv.visitTypeInsn(Opcodes.CHECKCAST, typeOfRightSide.substring(0, typeOfRightSide.indexOf('<')));
		}
		assign.lefSide.accept(this);

		statement = null;
	}

	@Override
	public void visit(BinaryExpr binary) {
		isParentBinary = true;
		String lexpType = resolver.getResolvedType(binary.lexpr.getType());
		String rexpType = resolver.getResolvedType(binary.rexpr.getType());

		String largerType = getLargerType(lexpType, rexpType);
		String typeOfBinary = resolver.getResolvedType(binary.getType());
		
		if (typeOfBinary.equals(Type.getInternalName(String.class))) {
			mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(StringBuilder.class));
			mv.visitInsn(Opcodes.DUP);
			mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(StringBuilder.class), "<init>", "()V",
					false);
		}

		Label endLabel = new Label();
		// this case for while loops and If statements
		if (statement instanceof LoopStmt)
			mv.visitLabel(endLabel);
		
		if(binary.lexpr instanceof UnaryExpr)
			needDUP = true;
		
		binary.lexpr.accept(this);
		
		if(lexpType.equals(Type.getInternalName(String.class))) {
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), "append",
					"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
		}

		if (!lexpType.equals(rexpType) && !lexpType.equals(largerType))
			doCast(lexpType, largerType);
		
		if(binary.rexpr instanceof UnaryExpr)
			needDUP = true;
		
		binary.rexpr.accept(this);
		if(rexpType.equals(Type.getInternalName(String.class))) {
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), "append",
					"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
		}
		isParentBinary = false;
		
		if (!lexpType.equals(rexpType) && !rexpType.equals(largerType))
			doCast(rexpType, largerType);

		Operator op = binary.operation;
		
		switch (op) {
		case ADD:
			doVisitAddOpInsn(largerType);
			break;

		case SUB:
			doVisitSubOpInsn(typeOfBinary);
			break;

		case MUL:
			doVisitMulOpInsn(typeOfBinary);
			break;

		case DIV:
			doVisitDivOpInsn(typeOfBinary);
			break;

		case MOD:
			doVisitModOpInsn(typeOfBinary);
			break;

		case LESSTHAN:
		case LESSEQUAL:
		case BIGGERTHAN:
		case BIGGEREQUAL:
		case EQUAL:
		case NOTEQUAL:
			Label branchLabel = new Label();
			doVisitRelOpInsn(op, largerType, branchLabel, endLabel);
			break;

		default:
			break;
		}
	}

	private String getLargerType(String lexpType, String rexpType) {
		if (lexpType.equals(Type.getInternalName(String.class))
				|| rexpType.equals(Type.getInternalName(String.class))) {
			return Type.getInternalName(String.class);
		} else if (lexpType.equals(Type.getInternalName(Double.class))
				|| rexpType.equals(Type.getInternalName(Double.class))) {
			return Type.getInternalName(Double.class);
		} else if (lexpType.equals(Type.getInternalName(Float.class))
				|| rexpType.equals(Type.getInternalName(Float.class))) {
			return Type.getInternalName(Float.class);
		} else if (lexpType.equals(Type.getInternalName(Long.class))
				|| rexpType.equals(Type.getInternalName(Long.class))) {
			return Type.getInternalName(Long.class);
		} else {
			return Type.getInternalName(Integer.class);
		}
	}

	private void doCast(String sourceType, String dest) {
		switch (dest) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.I2L);
			break;

		case "java/lang/Double":
			if (sourceType.equals(Type.getInternalName(Long.class))) {
				mv.visitInsn(Opcodes.L2D);
			} else if (sourceType.equals(Type.getInternalName(Float.class))) {
				mv.visitInsn(Opcodes.F2D);
			} else {
				mv.visitInsn(Opcodes.I2D);
			}
			break;

		case "java/lang/Float":
			if (sourceType.equals(Type.getInternalName(Long.class))) {
				mv.visitInsn(Opcodes.L2F);
			} else {
				mv.visitInsn(Opcodes.I2F);
			}
			break;
		// braucht man eigentlich nicht, muss getestet werden
		case "java/lang/String":
			if (sourceType.equals(Type.getInternalName(Double.class))) {
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), "valueOf",
						"(D)Ljava/lang/String;", false);
			} else if (sourceType.equals(Type.getInternalName(Long.class))) {
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), "valueOf",
						"(J)Ljava/lang/String;", false);
			} else if (sourceType.equals(Type.getInternalName(Float.class))) {
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), "valueOf",
						"(F)Ljava/lang/String;", false);
			} else {
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(String.class), "valueOf",
						"(I)Ljava/lang/String;", false);
			}
			break;
		default:
			break;
		}
	}

	private void doVisitRelOpInsn(Operator op, String typeOfBinary, Label branchLabel, Label endLabel) {
		System.out.println("TypeOfBinary: " + typeOfBinary);
		switch (typeOfBinary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LCMP);
			doVisitIfInRelOp(op, branchLabel, endLabel);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DCMPG);
			doVisitIfInRelOp(op, branchLabel, endLabel);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FCMPG);
			doVisitIfInRelOp(op, branchLabel, endLabel);
			break;
		default:
			switch (op) {
			case LESSTHAN:
				mv.visitJumpInsn(Opcodes.IF_ICMPGE, branchLabel);
				break;
			case LESSEQUAL:
				mv.visitJumpInsn(Opcodes.IF_ICMPGT, branchLabel);
				break;
			case BIGGERTHAN:
				mv.visitJumpInsn(Opcodes.IF_ICMPLE, branchLabel);
				break;
			case BIGGEREQUAL:
				mv.visitJumpInsn(Opcodes.IF_ICMPLT, branchLabel);
				break;
			case EQUAL:
				mv.visitJumpInsn(Opcodes.IF_ICMPNE, branchLabel);
				break;
			case NOTEQUAL:
				mv.visitJumpInsn(Opcodes.IFEQ, branchLabel);
				break;
			default:
				break;
			}

			statement.genBCForRelOp(mv, branchLabel, endLabel, this);

			break;
		}
	}

	private void doVisitIfInRelOp(Operator op, Label branchLabel, Label endLabel) {

		switch (op) {
		case LESSTHAN:
			mv.visitJumpInsn(Opcodes.IFGE, branchLabel);
			break;
		case LESSEQUAL:
			mv.visitJumpInsn(Opcodes.IFGT, branchLabel);
			break;
		case BIGGERTHAN:
			mv.visitJumpInsn(Opcodes.IFLE, branchLabel);
			break;
		case BIGGEREQUAL:
			mv.visitJumpInsn(Opcodes.IFLT, branchLabel);
			break;
		case EQUAL:
			mv.visitJumpInsn(Opcodes.IFNE, branchLabel);
			break;
		case NOTEQUAL:
			mv.visitJumpInsn(Opcodes.IFEQ, branchLabel);
			break;
		default:
			break;
		}

		statement.genBCForRelOp(mv, branchLabel, endLabel, this);
	}

	private void doVisitModOpInsn(String typeOfBinary) {
		switch (typeOfBinary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LREM);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DREM);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FREM);
			break;
		default:
			mv.visitInsn(Opcodes.IREM);
			break;
		}
	}

	private void doVisitDivOpInsn(String typeOfBinary) {
		switch (typeOfBinary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LDIV);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DDIV);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FDIV);
			break;
		default:
			mv.visitInsn(Opcodes.IDIV);
			break;
		}
	}

	private void doVisitMulOpInsn(String typeOfBinary) {
		switch (typeOfBinary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LMUL);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DMUL);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FMUL);
			break;
		default:
			mv.visitInsn(Opcodes.IMUL);
			break;
		}
	}

	private void doVisitSubOpInsn(String typeOfBinary) {
		switch (typeOfBinary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LSUB);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DSUB);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FSUB);
			break;
		default:
			mv.visitInsn(Opcodes.ISUB);
			break;
		}

	}

	private void doVisitAddOpInsn(String typeOfBinary) {
		switch (typeOfBinary) {
		case "java/lang/Byte":
			mv.visitInsn(Opcodes.IADD);
			break;
		case "java/lang/Short":
			mv.visitInsn(Opcodes.IADD);
			break;
		case "java/lang/Integer":
			mv.visitInsn(Opcodes.IADD);
			break;
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LADD);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DADD);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FADD);
			break;
		case "java/lang/String":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;",
					false);
			break;
		default:
			break;
		}
	}

	@Override
	public void visit(LambdaExpression lambdaExpression) {
		this.lamCounter++;
		
		String typeErasure = "(";
		Iterator<FormalParameter> itr = lambdaExpression.params.iterator();
		while (itr.hasNext()) {
			itr.next();
			typeErasure += "L" + Type.getInternalName(Object.class) + ";";
		}

		typeErasure += ")L" + Type.getInternalName(Object.class) + ";";
		
		generateBCForFunN(lambdaExpression, typeErasure);
		
		
		Lambda lam = new Lambda(lambdaExpression);
		String lamDesc = lam.accept(new DescriptorToString(resultSet));
		// Call site, which, when invoked, returns an instance of the functional
		// interface to which
		// the lambda is being converted
		MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
				MethodType.class, MethodType.class, MethodHandle.class, MethodType.class);

		Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
				mt.toMethodDescriptorString(), false);
		String methodName = "lambda$new$" + this.lamCounter;

		// Für die Parameter-Typen und Return-Typ braucht man die Bounds (für die
		// Typlöschung)

		// Type erasure
		Type arg1 = Type.getMethodType(typeErasure);
		// Type arg1 = Type.getMethodType(lamDesc);
		// real Type
		Type arg3 = Type.getMethodType(lamDesc);
		
		int staticOrSpecial = 0;
		int staticOrInstance = 0;
		int indexOfFirstParamLam = 0;
		this.kindOfLambda = new KindOfLambda(lambdaExpression);

		if (kindOfLambda.isInstanceCapturingLambda()) {
//			if(!kindOfLambda.getArgumentList().contains(BytecodeGen.THISTYPE))
//				kindOfLambda.getArgumentList().add(0, BytecodeGen.THISTYPE);
			mv.visitVarInsn(Opcodes.ALOAD, 0);
			for(String v : kindOfLambda.getUsedVars()) {
				mv.visitVarInsn(Opcodes.ALOAD, paramsAndLocals.get(v));
			}
			staticOrSpecial = Opcodes.H_INVOKESPECIAL;
			indexOfFirstParamLam = 1;
		} else {
			staticOrSpecial = Opcodes.H_INVOKESTATIC;
			staticOrInstance = Opcodes.ACC_STATIC;
		}
		String newDesc = "(";
		int pos = 0;
		if(kindOfLambda.isHasThis()) {
			pos = 1;
		}
		
		for(int i=pos;i<kindOfLambda.getArgumentList().size();i++) {
			String t = "L" + resolver.getResolvedType(kindOfLambda.getArgumentList().get(i)) + ";";
			newDesc += t;
		}
		newDesc += lamDesc.substring(1);
		// first check if capturing lambda then invokestatic or invokespecial
		Handle arg2 = new Handle(staticOrSpecial, this.className, methodName, newDesc, false);
		// Descriptor of functional interface methode
		SamMethod samMethod = new SamMethod(kindOfLambda.getArgumentList(), lambdaExpression.getType());
		// Desc: (this/nothing)TargetType
		String fiMethodDesc = samMethod.accept(new DescriptorToString(resultSet));
		mv.visitInvokeDynamicInsn("apply", fiMethodDesc, bootstrap, arg1, arg2, arg3);
		if(constructorPos<2) {
			MethodVisitor mvLambdaBody = cw.visitMethod(Opcodes.ACC_PRIVATE + staticOrInstance + Opcodes.ACC_SYNTHETIC,
					methodName, newDesc, null, null);
			
			ArrayList<String> usedVars = kindOfLambda.getUsedVars();
			
			new BytecodeGenMethod(lambdaExpression, usedVars,this.resultSet, mvLambdaBody, indexOfFirstParamLam, isInterface,
					classFiles,this.path, lamCounter, sf, genericsAndBoundsMethod,
					genericsAndBounds);
	
			mvLambdaBody.visitMaxs(0, 0);
			mvLambdaBody.visitEnd();
			cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup",
					Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL);
		}
//		generateBCForFunN(lambdaExpression, typeErasure);
	}

	private void generateBCForFunN(LambdaExpression lambdaExpression, String methDesc) {
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

		SignatureWriter methSig = new SignatureWriter();

		int numberOfParams = 0;
		SignatureVisitor paramVisitor = methSig.visitParameterType();
		Iterator<FormalParameter> itr = lambdaExpression.params.iterator();
		while (itr.hasNext()) {
			numberOfParams++;
			// getBounds
			paramVisitor.visitTypeVariable("T" + numberOfParams);
			itr.next();
		}
		methSig.visitReturnType().visitTypeVariable("R");
		// ")"+lam.getReturn.getBounds
		Signature sig = new Signature(numberOfParams);
		String name = "Fun" + numberOfParams + "$$";
		classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC+Opcodes.ACC_INTERFACE + Opcodes.ACC_ABSTRACT, name, sig.toString(),
				Type.getInternalName(Object.class), null);
		MethodVisitor mvApply = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "apply", methDesc,
				methSig.toString(), null);
		mvApply.visitEnd();
		writeClassFile(classWriter.toByteArray(), name);
	}

	public void writeClassFile(byte[] bytecode, String name) {
		FileOutputStream output;
		try {
			System.out.println("generating " + name + ".class file...");
			output = new FileOutputStream(
					new File(path + name + ".class"));
			output.write(bytecode);
			output.close();
			System.out.println(name + ".class file generated");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Override
	public void visit(CastExpr castExpr) {

	}

	@Override
	public void visit(EmptyStmt emptyStmt) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(FieldVar fieldVar) {
		fieldName = fieldVar.fieldVarName;
		fieldDesc  = "L";
		if(resultSet.resolveType(fieldVar.getType()).resolvedType instanceof TypePlaceholder) {
			fieldDesc += Type.getInternalName(Object.class);
		} else {
			fieldDesc += resultSet.resolveType(fieldVar.getType()).resolvedType.acceptTV(new TypeToDescriptor());
		}
		fieldDesc +=";";

		fieldVar.receiver.accept(this);
		// test (if)
		if (!fieldVar.receiver.getClass().equals(StaticClassName.class)) {
			mv.visitFieldInsn(Opcodes.GETFIELD, resolver.getResolvedType(fieldVar.receiver.getType()), fieldName, fieldDesc);
		}
		
		if (isBinaryExp) {
			doUnboxing(resolver.getResolvedType(fieldVar.getType()));
		}
		// mv.visitFieldInsn(Opcodes.GETSTATIC,
		// fieldVar.receiver.getType().toString().replace(".", "/"),
		// fieldVar.fieldVarName, fieldVar.getType().toString());
	}

	@Override
	public void visit(ForStmt forStmt) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(IfStmt ifStmt) {
		statement = new IfStatement(ifStmt.expr, ifStmt.then_block, ifStmt.else_block);
		isBinaryExp = statement.isExprBinary();
		ifStmt.expr.accept(this);
		if(!(ifStmt.expr instanceof BinaryExpr)) {
			doUnboxing(resolver.getResolvedType(ifStmt.expr.getType()));
			Label branchLabel = new Label();
			Label endLabel = new Label();
			mv.visitJumpInsn(Opcodes.IFEQ, branchLabel);
			statement.genBCForRelOp(mv, branchLabel, endLabel, this);
		}
		statement = null;
	}

	@Override
	public void visit(InstanceOf instanceOf) {

	}
	
	@Override
	public void visit(MethodCall methodCall) {
		boolean parentBinary = isParentBinary;
		System.out.println("In MethodCall = " + methodCall.name);
		String receiverName = resolver.getResolvedType(methodCall.receiver.getType());
		System.out.println("Methods of " + receiverName + " ");
		java.lang.reflect.Method methodRefl = null;
		String clazz = receiverName.replace("/", ".");
		
		String mDesc = "";
		
		MethodCallHelper helper = new MethodCallHelper(methodCall, sf, resultSet, path);
		
		boolean toCreate = false;

		ClassLoader cLoader = ClassLoader.getSystemClassLoader();
		// This will be used if the class is not standard class (not in API)
		ClassLoader cLoader2;

		String methCallType = resultSet.resolveType(methodCall.getType()).resolvedType.acceptTV(new TypeToDescriptor());
		String[] typesOfParams = getTypes(methodCall.arglist.getArguments());
		try {
			if (receiverName.contains("<")) {
				clazz = clazz.substring(0, receiverName.indexOf("<"));
			}

			java.lang.reflect.Method[] methods = cLoader.loadClass(clazz).getMethods();
			System.out.println("Methods of " + receiverName + " ");
			methodRefl = getMethod(methodCall.name, methodCall.arglist.getArguments().size(), methods);

			} catch (Exception e) {
					String superClass = "";
					while(true) {
						try {
							superClass = helper.getSuperClass(receiverName);
							
							try {
								String superClazz = superClass.replace("/", ".");
								if(superClass.contains("<")) {
									superClazz = superClazz.substring(0, superClass.indexOf("<"));
								}
								java.lang.reflect.Method[] methods = cLoader.loadClass(superClazz).getMethods();
								System.out.println("Methods of " + superClass + " ");
								
								for(java.lang.reflect.Method m : methods) {
									if(methodCall.name.equals(m.getName())) {
										methodRefl = m;
										break;
									}
								}
								
								break;
							} catch (Exception e3) {
								receiverName = superClass;
								continue;
							}
						} catch (NotInCurrentPackageException e2) {
							break;
						}
						
					}
				
			}
			
			if(methodRefl == null) {
				toCreate = !receiverName.equals(className) && helper.isInCurrPkg(clazz);
				if(toCreate) {
					try {
						mDesc = helper.getDesc(clazz);
					} catch (NotInCurrentPackageException | NotFoundException e) {
						e.printStackTrace();
					}
				} else if(!helper.isInCurrPkg(clazz)){
					if(clazz.contains("$$")) {
						mDesc = helper.generateBCForFunN();
//						mDesc = helper.generateBCForFunN(methCallType,typesOfParams);
					}else {
						try {
							cLoader2 = new URLClassLoader(new URL[] {new URL("file://"+path)});
							java.lang.reflect.Method[] methods = cLoader2.loadClass(clazz).getMethods();
							System.out.println("Methods of " + receiverName + " ");
							for(int i = 0; i<methods.length; i++) {
								System.out.println(methods[i]);
							}
							methodRefl = getMethod(methodCall.name,methodCall.arglist.getArguments().size(),methCallType, typesOfParams,methods);
						}
						catch (Exception e2) {
							e2.printStackTrace();
						}
					}
				}
				
			}
		
		methodCall.receiver.accept(this);
		
		System.out.println("Methodcall type : " + resultSet.resolveType(methodCall.getType()).resolvedType.acceptTV(new TypeToDescriptor()));
		List<Boolean> argListMethCall = new LinkedList<>();
		String receiverRefl="";
		if(methodRefl == null && receiverName.equals(className)) {
			MethodFromMethodCall method = new MethodFromMethodCall(methodCall.arglist, methodCall.getType(),
					receiverName, genericsAndBoundsMethod, genericsAndBounds);
			mDesc = method.accept(new DescriptorToString(resultSet));
			methodCall.arglist.accept(this);
		} else if(methodRefl != null) {
			System.out.println(methodCall.name + " -> Refl != null");
			receiverRefl = methodRefl.getAnnotatedReceiverType().getType().toString();
			for(Parameter p:methodRefl.getParameters()) {
				System.out.println(p.getName() + " und is Primitive = " + p.getType().isPrimitive());
				argListMethCall.add(p.getType().isPrimitive());
			}
			System.out.println("Receiver = " + methodRefl.getAnnotatedReceiverType().getType().toString());
			mDesc = getMethodDesc(methodRefl);
			for (Expression al : methodCall.arglist.getArguments()) {
				statement = new ArgumentExpr(al);
				ArgumentVisitor argV = new ArgumentVisitor(argListMethCall,this); 
				al.accept(argV);
				statement = null;
			}
		} else {
			methodCall.arglist.accept(this);
		}
		
		System.out.println("Methodcall ("+ methodCall.name +") Desc : " + mDesc);
		
		
//		methodCall.arglist.accept(this);
		
		// is methodCall.receiver functional Interface)?
		if (varsFunInterface.contains(methodCall.receiver.getType()) || (methodRefl!= null && receiverRefl.contains("interface")) ||
				receiverName.contains("$$")) {
			mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, clazz.replace(".", "/"), methodCall.name,
					mDesc, true);
		} else {
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, clazz.replace(".", "/"), methodCall.name,
					mDesc, isInterface);
		}
		
		
		if(methodRefl != null && !methodRefl.getReturnType().isPrimitive()) {
			if(methodRefl.getReturnType().equals(Object.class)) {
				helper.createCheckCast(methodCall,mv);
			}
			if(isBinaryExp) {
				doUnboxing(resolver.getResolvedType(methodCall.getType()));
				
			}
		} else if(receiverName.contains("$$") && !methCallType.equals(Type.getInternalName(Object.class))) {
			helper.createCheckCast(methodCall,mv);
		}
		System.out.println("ISParent Binary = "+isParentBinary +" -> " + parentBinary);
		if(methodRefl == null && (parentBinary || !isReturnStmt)) {
			if(isBinaryExp)
				doUnboxing(resolver.getResolvedType(methodCall.getType()));
		}
		
	}

	
	private String getDescForMethInCurrPkg(String name) {
		// TODO Auto-generated method stub
		return null;
	}

	private boolean isInCurrPkg(String superClass) {
		for(ClassOrInterface cl : sf.KlassenVektor) {
			if(superClass.equals(cl.getClassName().toString()))
				return true;
		}
		return false;
	}

	private String[] getTypes(List<Expression> arguments) {
		String[] types = new String[arguments.size()];
		for(int i = 0; i<arguments.size(); ++i) {
			String t = resolver.getResolvedType(arguments.get(i).getType());
			types[i] = t;
		}
		return types;
	}

	/**
	 * For API methods
	 * @param name name of a method
	 * @param i number of parameters 
	 * @param methods all methods of a class
	 * @return the method in the class file which its name equals the given methode name
	 */
	private java.lang.reflect.Method getMethod(String name, int i, java.lang.reflect.Method[] methods) {
		for(java.lang.reflect.Method m : methods) {
			if(name.equals(m.getName()) && i == m.getParameterCount()) {
				return m;
			}
		}
		return null;
	}
	
	/**
	 * @param name name of a method
	 * @param i number of parameters
	 * @param methCallType return type of method
	 * @param typesOfParams 
	 * @param methods all methods of a class
	 * @return the method in the class file which its name equals the given methode name
	 */
	private java.lang.reflect.Method getMethod(String name, int i, String methCallType, String[] typesOfParams, java.lang.reflect.Method[] methods) {
//		if(methCallType.equals("void")) {
//			methCallType = "V";
//		} else {
//			methCallType = "L"+methCallType+";";
//		}
		for(java.lang.reflect.Method m : methods) {
			if(name.equals(m.getName()) && i == m.getParameterCount() && 
					(methCallType.equals(m.getReturnType().getName().replace(".", "/")) || 
							m.getReturnType().getName().replace(".", "/").equals(Type.getInternalName(Object.class)))) {
				boolean typesEqual = true;
				Class<?>[] pTypes = m.getParameterTypes();
				for(int j = 0; j<typesOfParams.length; ++j) {
					if(!typesOfParams[j].equals(pTypes[j].getName().replaceAll(".", "/")) && !pTypes[j].getName().replace(".", "/").equals(Type.getInternalName(Object.class))) {
						typesEqual = false;
						break;
					}
				}
				
				if(typesEqual)
					return m;
			}
		}
		return null;
	}

	private String getMethodDesc(java.lang.reflect.Method methodRefl) {
		StringBuilder sb = new StringBuilder("(");
	    
		for(final Class c:(methodRefl.getParameterTypes()))
	        sb= sb.append(getDescriptorForClass(c));
	    
	    sb.append(')');
	    sb.append(getDescriptorForClass(methodRefl.getReturnType()));
	    return sb.toString();
	}

	private String getDescriptorForClass(final Class c) {
		if(c.isPrimitive())
	    {
	        if(c==byte.class)
	            return "B";
	        if(c==char.class)
	            return "C";
	        if(c==double.class)
	            return "D";
	        if(c==float.class)
	            return "F";
	        if(c==int.class)
	            return "I";
	        if(c==long.class)
	            return "J";
	        if(c==short.class)
	            return "S";
	        if(c==boolean.class)
	            return "Z";
	        if(c==void.class)
	            return "V";
	        
	    }
	    if(c.isArray()) 
	    		return c.getName().replace('.', '/');
	    
	    return ('L'+c.getName()+';').replace('.', '/');
	}

	@Override
	public void visit(NewClass methodCall) {

		mv.visitTypeInsn(Opcodes.NEW, methodCall.name.replace(".", "/"));
		mv.visitInsn(Opcodes.DUP);
		// creates Descriptor
		methodCall.arglist.accept(this);
		String d = "(";
		for (Expression e : methodCall.arglist.getArguments()) {
			d = d + "L" + resolver.getResolvedType(e.getType()) + ";";
		}
		d += ")V";

		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, methodCall.name.replace(".", "/"), "<init>", d, isInterface);
	}

	@Override
	public void visit(NewArray newArray) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(ExpressionReceiver receiver) {
		receiver.expr.accept(this);
	}

	@Override
	public void visit(UnaryExpr unaryExpr) {
		unaryExpr.expr.accept(this);
		Operation op = unaryExpr.operation;

		String typeOfUnary = resolver.getResolvedType(unaryExpr.getType());

		boolean isIncOrDec = false;

		switch (op) {
		case POSTDECREMENT:
		case POSTINCREMENT:
			if (statement instanceof AssignStmt)
				mv.visitInsn(Opcodes.DUP);
			genBCForIncAndDec(op, typeOfUnary);
			doBoxing(typeOfUnary);
			isIncOrDec = true;
			break;
		case PREDECREMENT:
		case PREINCREMENT:
			genBCForIncAndDec(op, typeOfUnary);
			doBoxing(typeOfUnary);
			if (statement instanceof AssignStmt)
				mv.visitInsn(Opcodes.DUP);
			isIncOrDec = true;
			break;

		case MINUS:
			doVisitNegIns(typeOfUnary);
			break;
		default:
			break;
		}

		// Für Byte und Short muss noch einen Cast geben i2b, i2s
		// das wird später gemacht, da bytecode für cast noch nicht erzeugt wird

		if (isIncOrDec && (unaryExpr.expr instanceof LocalVar)) {
			if(needDUP) {
				mv.visitInsn(Opcodes.DUP);
				needDUP = false;
			}
			LocalVar local = (LocalVar) unaryExpr.expr;
			mv.visitVarInsn(Opcodes.ASTORE, paramsAndLocals.get(local.name));
		}
	}

	private void doVisitNegIns(String typeOfUnary) {
		switch (typeOfUnary) {
		case "java/lang/Long":
			mv.visitInsn(Opcodes.LNEG);
			break;
		case "java/lang/Double":
			mv.visitInsn(Opcodes.DNEG);
			break;
		case "java/lang/Float":
			mv.visitInsn(Opcodes.FNEG);
			break;
		default:
			mv.visitInsn(Opcodes.INEG);
			break;
		}

	}

	private void genBCForIncAndDec(Operation op, String typeOfUnary) {

		doUnboxing(typeOfUnary);
		loadValue(typeOfUnary, 1.0, true);

		switch (op) {
		case POSTDECREMENT:
		case PREDECREMENT:
			doVisitSubOpInsn(typeOfUnary);
			break;

		case POSTINCREMENT:
		case PREINCREMENT:
			doVisitAddOpInsn(typeOfUnary);
			break;

		default:
			break;
		}
	}

	@Override
	public void visit(Return aReturn) {
		statement = new ReturnStmt(aReturn.retexpr);
		isReturnStmt = true;
		isBinaryExp = statement.isExprBinary();
		boolean isBinary = isBinaryExp;
		if(aReturn.retexpr instanceof UnaryExpr)
			needDUP = true;
		
		aReturn.retexpr.accept(this);

		if (isBinary) {
			BinaryExpr binary = (BinaryExpr) aReturn.retexpr;
			doBoxing(resolver.getResolvedType(binary.getType()));
//			isBinaryExp = false;
		}
		
		mv.visitInsn(Opcodes.ARETURN);
		isReturnStmt = false;
		statement = null;
	}

	@Override
	public void visit(ReturnVoid aReturn) {
		mv.visitInsn(Opcodes.RETURN);
	}

	@Override
	public void visit(StaticClassName staticClassName) {
		// mv.visitMethodInsn(Opcodes.INVOKESTATIC,
		// staticClassName.getType().toString().replace(".", "/"),
		// staticClassName.toString(), staticClassName.getType().toString(), false);
		// mv.visitFieldInsn(Opcodes.GETSTATIC,
		// getResolvedType(staticClassName.getType()), fieldName, fieldDesc);
		throw new NotImplementedException("Static noch nicht implementiert!");
	}

	@Override
	public void visit(Super aSuper) {

	}

	@Override
	public void visit(This aThis) {
		
		if(BytecodeGen.THISTYPE == null)
			BytecodeGen.THISTYPE = aThis.getType();
		
		mv.visitVarInsn(Opcodes.ALOAD, 0);
	}

	@Override
	public void visit(WhileStmt whileStmt) {
		statement = new LoopStmt(whileStmt.expr, whileStmt.loopBlock);
		isBinaryExp = statement.isExprBinary();
		whileStmt.expr.accept(this);
//		isBinaryExp = false;
		statement = null;
	}

	@Override
	public void visit(DoStmt whileStmt) {
		whileStmt.expr.accept(this);
		// TODO:
	}

	@Override
	public void visit(Literal literal) {
		Object value = literal.value;
		String typeOfLiteral = resolver.getResolvedType(literal.getType());
		System.out.println("typeOfLiteral :=> "+ typeOfLiteral);
		// Der Wert des Literals wird auf den Stack geladen und
		// geboxt, wenn es nötig ist.
		loadValue(typeOfLiteral, value, false);
	}

	// Unboxing: RefType -> prim
	public void doUnboxing(String type) {
		switch (type) {
//		case "java/lang/String":
//			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StringBuilder.class), "append",
//					"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
//
//			break;
		case "java/lang/Boolean":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
			break;
		case "java/lang/Byte":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
			break;
		case "java/lang/Short":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "java/lang/Integer":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
			break;
		case "java/lang/Long":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			break;
		case "java/lang/Float":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "java/lang/Double":
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			break;
		case "java/lang/Character":
			break;
		default:
			// mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue",
			// "()I", false);
			break;
		}
	}

	private void loadValue(String type, Object value, boolean isOperator) {
		switch (type) {
		case "java/lang/String":
			mv.visitLdcInsn(String.valueOf(value));
			break;
		case "java/lang/Boolean":
			visitBooleanLiteral((Boolean) value);
			break;
		case "java/lang/Byte":
			if(value instanceof Double)
				visitByteLiteral(((Double) value).byteValue(), false);
			if(value instanceof Integer)
				visitByteLiteral(((Integer) value).byteValue(), false);
			break;
		case "java/lang/Short":
			if(value instanceof Double)
				visitShortLiteral(((Double) value).shortValue(), false);
			if(value instanceof Integer)
				visitShortLiteral(((Integer) value).shortValue(), false);
			break;
		case "java/lang/Integer":
			// zweite Argument isLong
//			visitIntegerLiteral(((Double) value).intValue(), false);
			if(value instanceof Double)
				visitIntegerLiteral(((Double) value).intValue(), false);
			if(value instanceof Integer)
				visitIntegerLiteral(((Integer) value).intValue(), false);
			break;
		case "java/lang/Long":
			if(value instanceof Double)
				visitLongLiteral(((Double) value).longValue(), true);
			if(value instanceof Integer)
				visitLongLiteral(((Integer) value).longValue(), true);
			break;
		case "java/lang/Float":
			if(value instanceof Double)
				visitFloatLiteral(((Double) value).floatValue());
			if(value instanceof Integer)
				visitFloatLiteral(((Integer) value).floatValue());
			break;
		case "java/lang/Double":
			if(value instanceof Double)
				visitDoubleLiteral((Double) value);
			if(value instanceof Integer)
				visitDoubleLiteral(((Integer) value).doubleValue());
			break;
		case "java/lang/Character":
			visitCharLiteral((Character) value);
			break;
		default:
			// wenn die Typ des Literals = Number ist, wird integer-value
			// verwendet
//			visitIntegerLiteral(((Double) value).intValue(), false);
			break;
		}
		// Boxing
		if (!type.equals("java/lang/String") && !type.equals("java/lang/Boolean")) {
			if (!this.isBinaryExp && !isOperator)
				doBoxing(type);
		}

	}

	// Boxing: prim -> RefType
	public void doBoxing(String type) {
		switch (type) {
//		case "java/lang/String":
//			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;",
//					false);
//			break;
		case "java/lang/Boolean":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			break;
		case "java/lang/Byte":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			break;
		case "java/lang/Short":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			break;
		case "java/lang/Integer":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			break;
		case "java/lang/Long":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			break;
		case "java/lang/Float":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			break;
		case "java/lang/Double":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			break;
		case "java/lang/Character":
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;",
					false);
			break;
		default:
			break;
		}
	}

	private void visitCharLiteral(Character value) {
		mv.visitIntInsn(Opcodes.BIPUSH, (int) value);

	}

	private void visitDoubleLiteral(Double value) {
		if (value == 0) {
			mv.visitInsn(Opcodes.DCONST_0);
		} else if (value == 1) {
			mv.visitInsn(Opcodes.DCONST_1);
		} else {
			mv.visitLdcInsn(value);
		}
	}

	private void visitFloatLiteral(Float value) {
		if (value.intValue() > -1 && value.intValue() < 3) {
			// Opcodes.FCONST_0 = 11, Opcodes.FCONST_1 = 12, usw
			mv.visitInsn(value.intValue() + 11);
		} else {
			mv.visitLdcInsn(value);
		}
	}

	private void visitLongLiteral(Long value, boolean isLong) {
		if (value == 0) {
			mv.visitInsn(Opcodes.LCONST_0);
		} else if (value == 1) {
			mv.visitInsn(Opcodes.LCONST_1);
		} else {
			mv.visitLdcInsn(value);
		}
	}

	private void visitShortLiteral(Short value, boolean isLong) {
		if (value < 128 || (value > -129 && value < -1)) {
			visitByteLiteral(value.byteValue(), isLong);
		} else if (value < Math.pow(2, 15) || (value >= -Math.pow(2, 15)) && value < -128) {
			mv.visitIntInsn(Opcodes.SIPUSH, value);
		}
	}

	private void visitByteLiteral(Byte value, boolean isLong) {

		if (!isLong && value < 6 && value > -1) {
			// Opcodes.ICONST_0 = 3, Opcodes.ICONST_1 = 4, usw
			mv.visitInsn(value + 3);
		} else if (isLong && value > -1 && value < 2) {
			// Opcodes.LCONST_0 = 9, Opcodes.LCONST_1 = 10
			mv.visitInsn(value + 9);
		} else {
			mv.visitIntInsn(Opcodes.BIPUSH, value);
		}

	}

	private void visitIntegerLiteral(Integer value, boolean isLong) {

		if (value < Math.pow(2, 15) || (value >= -Math.pow(2, 15)) && value < -128) {
			visitShortLiteral(value.shortValue(), isLong);
		} else {
			mv.visitLdcInsn(value);
		}
	}

	private void visitBooleanLiteral(Boolean b) {
		if (b) {
			mv.visitInsn(Opcodes.ICONST_1);
		} else {
			mv.visitInsn(Opcodes.ICONST_0);
		}
		// muss noch getestet werden.
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
	}

	@Override
	public void visit(ArgumentList argumentList) {
		for (Expression al : argumentList.getArguments()) {
			statement = new ArgumentExpr(al);
			isBinaryExp = statement.isExprBinary();
			if(al instanceof UnaryExpr)
				needDUP = true;
			al.accept(this);
			//TODO: teste, ob man das für unary braucht
			if (isBinaryExp) {
				BinaryExpr binary = (BinaryExpr) al;
				String binaryType = resolver.getResolvedType(binary.getType());
				doBoxing(binaryType);
			}
			statement = null;
		}
	}

	@Override
	public void visit(AssignToField assignLeftSide) {
		// temporäre Lösung für testen, bis ich weiss wie man funktionale
		// interfaces erkennt
		if (isRightSideALambda)
			varsFunInterface.add(assignLeftSide.field.getType());
		// Loads the an object reference from the local variable
		// array slot onto the top of the operand stack.
		assignLeftSide.field.receiver.accept(this);
		this.rightSideTemp.accept(this);
		String fDesc  = "L";
		if(resultSet.resolveType(assignLeftSide.field.getType()).resolvedType instanceof TypePlaceholder) {
			fDesc += Type.getInternalName(Object.class);
		} else {
			fDesc += resultSet.resolveType(assignLeftSide.field.getType()).resolvedType.acceptTV(new TypeToDescriptor());
		}
		fDesc +=";";

		
		System.out.println("Receiver = " + resolver.getResolvedType(assignLeftSide.field.receiver.getType()));
		mv.visitFieldInsn(Opcodes.PUTFIELD, resolver.getResolvedType(assignLeftSide.field.receiver.getType()),
				assignLeftSide.field.fieldVarName, fDesc);
	}

	@Override
	public void visit(AssignToLocal assignLeftSide) {
		if (isRightSideALambda)
			varsFunInterface.add(assignLeftSide.localVar.getType());
		int index = paramsAndLocals.size();
		String var = assignLeftSide.localVar.name;
		if (!paramsAndLocals.containsKey(var)) {
			paramsAndLocals.put(var, index + 1);
		} 
		
		mv.visitVarInsn(Opcodes.ASTORE, paramsAndLocals.get(var));
	}

}
