package de.dhbwstuttgart.bytecode.descriptor;

import java.util.HashMap;
import java.util.Iterator;

import org.objectweb.asm.Type;

import de.dhbwstuttgart.bytecode.signature.TypeToSignature;
import de.dhbwstuttgart.bytecode.utilities.Lambda;
import de.dhbwstuttgart.bytecode.utilities.MethodFromMethodCall;
import de.dhbwstuttgart.bytecode.utilities.NormalConstructor;
import de.dhbwstuttgart.bytecode.utilities.NormalMethod;
import de.dhbwstuttgart.bytecode.utilities.SamMethod;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.statement.Expression;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.typeinference.result.ResultSet;

public class DescriptorToString implements DescriptorVisitor{
	ResultSet resultSet;
	
	public DescriptorToString(ResultSet resultSet) {
		this.resultSet = resultSet;
	}
	
	private String addReturnType(String desc, RefTypeOrTPHOrWildcardOrGeneric returnType, ResultSet resultSet) {
		if(resultSet.resolveType(returnType).resolvedType.toString().equals("void")){
			desc = desc + ")V";
		}else {
			desc = desc + ")" + "L"+resultSet.resolveType(returnType).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
		}
		return desc;
	}
	
	@Override
	public String visit(NormalMethod method) {
		
		String desc = "(";
		Iterator<FormalParameter> itr = method.getParameterList().iterator();
		while(itr.hasNext()) {
			FormalParameter fp = itr.next();
//			System.out.println(resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToSignature()));
//			System.out.println("Parmetrisierte typ ? "+ ((RefType) fp.getType()).getParaList().size());
			if(method.hasGen()) {
				String fpDesc = fp.getType().acceptTV(new TypeToDescriptor());
				if(method.getGenericsAndBoundsMethod().containsKey(fpDesc)) {
					String bound = getBound(fpDesc, method.getGenericsAndBoundsMethod());
					desc += "L"+bound+ ";";
				}else if(method.getGenericsAndBounds().containsKey(fpDesc)){
					String bound = getBound(fpDesc, method.getGenericsAndBounds());
					desc += "L"+bound+ ";";
				}else {
//					desc += "L"+resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
					String resType = resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor());
					if(resType.contains("TPH ")/*resType.subSequence(0, 4).equals("TPH ")*/) {
						// Bound ist immer Object
						desc += "L"+Type.getInternalName(Object.class)+ ";";
					} else {
						// TODO::
						if(method.getGenericsAndBounds().containsKey(resType)) {
							String bound = getBound(resType, method.getGenericsAndBounds());
							desc += "L"+bound+ ";";
						}else if(method.getGenericsAndBoundsMethod().containsKey(resType)) {
							String bound = getBound(resType, method.getGenericsAndBoundsMethod());
							desc += "L"+bound+ ";";
						} else {
							desc += "L"+resType+ ";";
						}
					}
				}
			}
			//TODO: generate a class java%% ... %%
			else if(resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor()).contains("<")){
				desc += "L"+resultSet.resolveType(fp.getType()).resolvedType.toString().replace(".", "$$").replace("<", "$$$").replace(">", "$$$")+ ";";
			}
			else {
				desc += "L"+resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
			}
		}
		
		if(resultSet.resolveType(method.getReturnType()).resolvedType.toString().equals("void")) {
			desc += ")V";
		}else {
			if(method.hasGen()) {
				String ret = method.getReturnType().acceptTV(new TypeToDescriptor());
				if(method.getGenericsAndBoundsMethod().containsKey(ret)) {
					desc += ")L"+method.getGenericsAndBoundsMethod().get(ret)+ ";";
				}else if(method.getGenericsAndBounds().containsKey(ret)){
					desc += ")L"+method.getGenericsAndBounds().get(ret)+ ";";
				}else {
					String resType = resultSet.resolveType(method.getReturnType()).resolvedType.acceptTV(new TypeToDescriptor());
					if(resType.contains("TPH ")/*resType.subSequence(0, 4).equals("TPH ")*/) {
//						desc += ")" + "L"+method.getGenericsAndBoundsMethod().get(resType.substring(4)+"$")+ ";";
						desc += ")" + "L"+Type.getInternalName(Object.class)+ ";";
					} else {
						// TODO::
						if(method.getGenericsAndBounds().containsKey(resType)) {
							String bound = getBound(resType, method.getGenericsAndBounds());
							desc += ")L"+bound+ ";";
						}else if(method.getGenericsAndBoundsMethod().containsKey(resType)) {
							String bound = getBound(resType, method.getGenericsAndBoundsMethod());
							desc += ")L"+bound+ ";";
						} else {
							desc += ")L"+resType+ ";";
						}
//						desc += ")" + "L"+resType+ ";";
					}
				}
			}else {
				desc += ")" + "L"+resultSet.resolveType(method.getReturnType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
			}
		}
//		desc = addReturnType(desc,method.getReturnType(), resultSet);
		return desc;
	}

	private String getBound(String fpDesc, HashMap<String, String> genericsAndBounds) {
		String start = genericsAndBounds.get(fpDesc);
		while(genericsAndBounds.containsKey(start)) {
			start = genericsAndBounds.get(start);
		}
		
		return start;
	}

	@Override
	public String visit(NormalConstructor constructor) {
		String desc = "(";
		Iterator<FormalParameter> itr = constructor.getParameterList().iterator();
		while(itr.hasNext()) {
			FormalParameter fp = itr.next();
			if(constructor.hasGen()) {
//				System.out.println("Cons has Gens");
				String fpDesc = fp.getType().acceptTV(new TypeToDescriptor());
//				System.out.println(fpDesc);
				if(constructor.getGenericsAndBounds().containsKey(fpDesc)){
					desc += "L"+constructor.getGenericsAndBounds().get(fpDesc)+ ";";
				}else {
//					desc += "L"+resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
					String resType = resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor());
					if(resType.subSequence(0, 4).equals("TPH ")) {
						// Bound ist immer Object
						desc += "L"+Type.getInternalName(Object.class)+ ";";
					} else {
						desc += "L"+resType+ ";";
					}
				}
			}else {
//				System.out.println("Cons has NO Gens");
				desc += "L"+resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
			}
		}
		desc = desc + ")V";
		return desc;
	}

	@Override
	public String visit(Lambda lambdaExpression) {
		String desc = "(";
		Iterator<FormalParameter> itr = lambdaExpression.getParams().iterator();
		while(itr.hasNext()) {
			FormalParameter fp = itr.next();
			String d = resultSet.resolveType(fp.getType()).resolvedType.acceptTV(new TypeToDescriptor());
			if(d.contains("TPH ") ||d.contains("<")) {
				desc += "L"+Type.getInternalName(Object.class)+ ";";
			}else {
				desc = desc + "L"+ d + ";";
			}
		}
		
		String retType = resultSet.resolveType(lambdaExpression.getReturnType()).resolvedType.acceptTV(new TypeToDescriptor());
		
		if(retType.contains("TPH ")|| retType.contains("<")){
			desc += ")L"+Type.getInternalName(Object.class)+ ";";
		}else {
			desc = desc + ")"+"L"+retType+";";
		}
		return desc;
	}

	@Override
	public String visit(SamMethod samMethod) {
		String desc = "(";
		Iterator<RefTypeOrTPHOrWildcardOrGeneric> itr = samMethod.getArgumentList().iterator();
		while(itr.hasNext()) {
			RefTypeOrTPHOrWildcardOrGeneric rt = itr.next();
			String d = resultSet.resolveType(rt).resolvedType.acceptTV(new TypeToDescriptor());
			
			if(d.contains("TPH ") ||d.contains("<")) {
				desc += "L"+Type.getInternalName(Object.class)+ ";";
			}else {
				desc += "L"+ d + ";";
				
			}
		}
		String retType = resultSet.resolveType(samMethod.getReturnType()).resolvedType.acceptTV(new TypeToDescriptor());
		
		if(retType.contains("TPH ")|| retType.contains("<")){
			desc += ")L"+Type.getInternalName(Object.class)+ ";";
		}else {
			desc = desc + ")"+"L"+retType+";";
		}
		return desc;
	}

	@Override
	public String visit(MethodFromMethodCall methodFromMethodCall) {
		String desc = "(";
		for(Expression e : methodFromMethodCall.getArgList().getArguments()) {
			String d = resultSet.resolveType(e.getType()).resolvedType.acceptTV(new TypeToDescriptor());
			
			if(d.contains("TPH ") ||d.contains("<") || methodFromMethodCall.getReceiverName().contains("$$")) {
				desc += "L"+Type.getInternalName(Object.class)+ ";";
			}else {
				if(methodFromMethodCall.getGenericsAndBoundsMethod().containsKey(d)) {
					desc += "L"+methodFromMethodCall.getGenericsAndBoundsMethod().get(d)+ ";";
				}else if(methodFromMethodCall.getGenericsAndBounds().containsKey(d)) {
					desc += "L"+methodFromMethodCall.getGenericsAndBounds().get(d)+ ";";
				}else {
					desc += "L"+resultSet.resolveType(e.getType()).resolvedType.acceptTV(new TypeToDescriptor())+ ";";
				}
			}
			
		}
		String retType = resultSet.resolveType(methodFromMethodCall.getReturnType()).resolvedType.acceptTV(new TypeToDescriptor());
		System.out.println("DescriptorToString retType = " + retType);
		if(retType.equals("void")) {
			desc += ")V";
		}else if(retType.contains("TPH ")|| retType.contains("<") || methodFromMethodCall.getReceiverName().contains("$$")){
			desc += ")L"+Type.getInternalName(Object.class)+ ";";
		}else {
			if(methodFromMethodCall.getGenericsAndBoundsMethod().containsKey(retType)) {
				desc += ")L"+methodFromMethodCall.getGenericsAndBoundsMethod().get(retType)+ ";";
			}else if(methodFromMethodCall.getGenericsAndBounds().containsKey(retType)){
				desc += ")L"+methodFromMethodCall.getGenericsAndBounds().get(retType)+ ";";
			}else {
				desc += ")" + "L"+retType+ ";";
			}
		}
//		desc = addReturnType(desc, methodFromMethodCall.getReturnType(), resultSet);
		return desc;
	}
	
}
