/**
 * 
 */
package de.dhbwstuttgart.bytecode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint.Relation;
import de.dhbwstuttgart.bytecode.utilities.MethodAndTPH;
import de.dhbwstuttgart.bytecode.utilities.MethodUtility;
import de.dhbwstuttgart.bytecode.utilities.Resolver;
import de.dhbwstuttgart.syntaxtree.AbstractASTWalker;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Constructor;
import de.dhbwstuttgart.syntaxtree.Field;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.ParameterList;
import de.dhbwstuttgart.syntaxtree.statement.LocalVar;
import de.dhbwstuttgart.syntaxtree.statement.LocalVarDecl;
import de.dhbwstuttgart.syntaxtree.type.GenericRefType;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typeinference.result.GenericInsertPair;
import de.dhbwstuttgart.typeinference.result.ResultPair;
import de.dhbwstuttgart.typeinference.result.ResultSet;

/**
 * @author Fayez Abu Alia
 *
 */
public class TPHExtractor extends AbstractASTWalker {
	// Alle TPHs der Felder werden iKopf der Klasse definiert
	// alle TPHs der Klasse: (TPH, is in Method?)
	public final HashMap<String, Boolean> allTPHS = new HashMap<>();
	public final List<String> tphsClass = new ArrayList<>();
	MethodAndTPH methodAndTph;

	Boolean inMethod = false;
	boolean inLocalOrParamOrReturn = false;

	public final ArrayList<MethodAndTPH> ListOfMethodsAndTph = new ArrayList<>();
	final ArrayList<ResultPair<TypePlaceholder, TypePlaceholder>> allPairs = new ArrayList<>();
	public final ArrayList<TPHConstraint> allCons = new ArrayList<>();
	private ResultSet resultSet;
	private Resolver resolver;

	public TPHExtractor() {

	}

	public void setResultSet(ResultSet resultSet) {
		this.resultSet = resultSet;
		this.resolver = new Resolver(resultSet);
	}

	public Resolver getResolver() {
		return resolver;
	}
	
//	@Override
//	public void visit(ClassOrInterface classOrInterface) {
//		inMethod = false;
//		classOrInterface.getfieldInitializations().ifPresent(c->c.block.accept(this));
//		super.visit(classOrInterface);
//		inMethod = true;
//	}
	@Override
	public void visit(TypePlaceholder tph) {
		if (resultSet.resolveType(tph).resolvedType instanceof TypePlaceholder) {
			TypePlaceholder resolvedTPH = (TypePlaceholder) resultSet.resolveType(tph).resolvedType;
			if (inMethod) {
				methodAndTph.getTphs().add(resolvedTPH.getName());
				if (inLocalOrParamOrReturn)
					methodAndTph.getLocalTphs().add(resolvedTPH.getName());
			}else {
				tphsClass.add(resolvedTPH.getName());
			}

			allTPHS.put(resolvedTPH.getName(), inMethod);
//			final List<TPHConstraint> cons = new ArrayList<>();
//			resultSet.resolveType(tph).additionalGenerics.forEach(ag -> {
//				TPHConstraint con = new ExtendsConstraint(ag.TA1.getName(), ag.TA2.getName(), Relation.EXTENDS);
//				cons.add(con);
//			});
//			
//			Map<TPHConstraint, Boolean> visitMap = new HashMap<>();
//			for(TPHConstraint cc : cons) {
//				visitMap.put(cc, false);
//			}
//			
//			String left = resolvedTPH.getName();
//			for (TPHConstraint cc : visitMap.keySet()) {
//				
//				if(visitMap.get(cc))
//					continue;
//				
//				if (cc.getLeft().equals(left)) {
//					allCons.add(cc);
//					List<TPHConstraint> toVisit = getToVisitCons(cons,cc.getRight(), visitMap);
//				}
//			}

			//resultSet.resolveType(tph).getAdditionalGenerics().forEach(ag -> {
			resultSet.genIns.forEach(ag -> {

//				if (ag.contains(resolvedTPH) /* && ag.TA1.equals(resolvedTPH) */ && !contains(allPairs, ag)) {
					if (inMethod)
						methodAndTph.getPairs().add(ag);
					allPairs.add(ag);
					TPHConstraint con = new ExtendsConstraint(ag.getLeft().getName(), ag.getRight().getName());
					if(!containsConstraint(allCons,con))
						allCons.add(con);
//				}
			});
		} else if (resultSet.resolveType(tph).resolvedType instanceof RefType) {
			RefType rt = (RefType) resultSet.resolveType(tph).resolvedType;
			rt.accept(this);
		}
	}

	private static boolean containsConstraint(ArrayList<TPHConstraint> allCons, TPHConstraint c) {
		for(TPHConstraint con:allCons) {
			if(c.getLeft().equals(con.getLeft()) && c.getRight().equals(con.getRight())) {
				return true;
			}
		}
		return false;
	}
	
	private List<TPHConstraint> getToVisitCons(List<TPHConstraint> cons, String right, Map<TPHConstraint, Boolean> visitMap) {
		List<TPHConstraint> res = new ArrayList<>();
		for(TPHConstraint cc : cons) {
			if(cc.getLeft().equals(right)) {
				res.add(cc);
				if(visitMap.get(cc))
					visitMap.replace(cc, false);
			}
		}
		return res;
	}

	@Override
	public void visit(GenericRefType genericRefType) {
		String name = genericRefType.getParsedName();
		if (inMethod) {
			methodAndTph.getTphs().add(name);
			if (inLocalOrParamOrReturn)
				methodAndTph.getLocalTphs().add(name);
		}else {
			tphsClass.add(name);
		}
		allTPHS.put(name, inMethod);
	}

	private boolean contains(ArrayList<GenericInsertPair> pairs, GenericInsertPair genPair) {
		for (int i = 0; i < pairs.size(); ++i) {
			GenericInsertPair p = pairs.get(i);
			if (p.TA1.equals(genPair.TA1) && p.TA2.equals(genPair.TA2))
				return true;
		}
		return false;
	}

	@Override
	public void visit(Method method) {
		inMethod = true;
		String id = MethodUtility.createID(resolver,method);
		methodAndTph = new MethodAndTPH(id);

		inLocalOrParamOrReturn = true;
		method.getReturnType().accept(this);
        method.getParameterList().accept(this);
        inLocalOrParamOrReturn = false;
        
        if(method.block != null)
            method.block.accept(this);
		
		inMethod = false;
		ListOfMethodsAndTph.add(methodAndTph);
	}

	@Override
	public void visit(Constructor cons) {
		inMethod = false;
		super.visit(cons);
		inMethod = true;
	}

	@Override
	public void visit(LocalVarDecl localVarDecl) {
//		inLocalOrParamOrReturn = inMethod;
		super.visit(localVarDecl);
//		inLocalOrParamOrReturn = false;
	}

	@Override
	public void visit(LocalVar localVar) {
//		inLocalOrParamOrReturn = inMethod;
		super.visit(localVar);
//		inLocalOrParamOrReturn = false;
	}

	@Override
	public void visit(ParameterList formalParameters) {
		inLocalOrParamOrReturn = inMethod;
		super.visit(formalParameters);
		inLocalOrParamOrReturn = false;
	}

}