package de.dhbwstuttgart.typeinference.result;

import java.util.HashSet;
import java.util.Set;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.syntaxtree.type.ExtendsWildcardType;
import de.dhbwstuttgart.syntaxtree.type.GenericRefType;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.syntaxtree.type.SuperWildcardType;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;

@SuppressWarnings("rawtypes")
public class ResultSet {

    public final Set<ResultPair> results;
    public Set<ResultPair<TypePlaceholder, TypePlaceholder>> genIns;

    public ResultSet(Set<ResultPair> set){
        this.results = set;
        this.genIns = new HashSet<>();
        results.forEach(x -> { if (x instanceof PairTPHsmallerTPH) { this.genIns.add(x);}} );
    }
    
    public boolean contains(ResultPair toCheck) {
    	return this.results.contains(toCheck);
    }
    
    public void remove(ResultPair toCheck) {
    	results.remove(toCheck);
    }

    public ResolvedType resolveType(RefTypeOrTPHOrWildcardOrGeneric type) {
        if(type instanceof TypePlaceholder)
            return new Resolver(this).resolve((TypePlaceholder)type);
        if(type instanceof GenericRefType)return new ResolvedType(type, new HashSet<>());
        if(type instanceof RefType) {
            RelatedTypeWalker related = new RelatedTypeWalker(null, this);
            type.accept(related);
            return new ResolvedType(type, related.relatedTPHs);
        } else {
            throw new NotImplementedException();
            //return new ResolvedType(type,new HashSet<>());
        }
    }

    public String toString() {
    	return results.toString();
    }

	@Override
	public boolean equals(Object o) {
		if (o instanceof ResultSet) {
			ResultSet other = (ResultSet)o;
			return this.results.equals(other.results);
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return results.hashCode();
	}
}

class Resolver implements ResultSetVisitor {
    private final ResultSet result;
    private TypePlaceholder toResolve;
    private RefTypeOrTPHOrWildcardOrGeneric resolved;
    private final Set<GenericInsertPair> additionalTPHs = new HashSet<>();
    private ResultPair<?,?> currentPair;

    public Resolver(ResultSet resultPairs){
        this.result = resultPairs;
    }

    public ResolvedType resolve(TypePlaceholder tph){
        toResolve = tph;
        resolved = null;
        System.out.println(tph.toString());
        for(ResultPair<?,?> resultPair : result.results) {
            if(resultPair instanceof PairTPHEqualTPH && ((PairTPHEqualTPH) resultPair).getLeft().equals(toResolve)){
            	currentPair = resultPair;
                return resolve(((PairTPHEqualTPH) resultPair).getRight());
            }
        }
        for(ResultPair<?,?> resultPair : result.results){
        	currentPair = resultPair;
            resultPair.accept(this);
        }
        if(resolved==null){//TPH kommt nicht im Result vor:
            resolved = tph;
        }

        ResolvedType result = new ResolvedType(resolved, additionalTPHs);//resolved;
        result.setResultPair(currentPair);
        return result;
    }

    @Override
    public void visit(PairTPHsmallerTPH p) {
    	currentPair = p;
        if(p.left.equals(toResolve)){
            additionalTPHs.add(new GenericInsertPair(p.left, p.right));
            additionalTPHs.addAll(new RelatedTypeWalker(p.right, result).relatedTPHs);
        }
        if(p.right.equals(toResolve))
            additionalTPHs.addAll(new RelatedTypeWalker(p.left, result).relatedTPHs);
    }

    @Override
    public void visit(PairTPHequalRefTypeOrWildcardType p) {
    	currentPair = p;
        if(p.left.equals(toResolve)){
            resolved = p.right;
            RelatedTypeWalker related = new RelatedTypeWalker(null, result);
            p.right.accept(related);
            additionalTPHs.addAll(related.relatedTPHs);
        }
    }

    @Override
    public void visit(PairTPHEqualTPH p) {
        //Do nothing. Dieser Fall wird in der resolve-Methode abgefangen
    }

    @Override
    public void visit(RefType refType) {

    }

    @Override
    public void visit(GenericRefType genericRefType) {

    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {

    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {

    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {

    }
    
    

}

/**
 * Sucht aus dem Result Set den Sub/supertyp für einen TPH
 */
@SuppressWarnings("rawtypes")
class TPHResolver implements ResultSetVisitor {

    private final TypePlaceholder tph;
    Set<GenericInsertPair> resolved = new HashSet<>();
    private final ResultSet resultSet;

    TPHResolver(TypePlaceholder tph, ResultSet resultSet){
        this.resultSet = resultSet;
        this.tph = tph;
        for(ResultPair p : resultSet.results){
            p.accept(this);
        }
        if(resolved.size() == 0){
            resolved.add(new GenericInsertPair(tph, null));
        }
    }

    @Override
    public void visit(PairTPHsmallerTPH p) {
        if(p.left.equals(tph) || p.right.equals(tph)){
            resolved.add(new GenericInsertPair(p.left, p.right));
        }
    }

    @Override
    public void visit(PairTPHequalRefTypeOrWildcardType p) {
        TypePlaceholder otherSide = null;
        if(p.right.equals(tph)){
            otherSide = p.left;
        }
        if(otherSide != null){
            Set<ResultPair> newResultSet = new HashSet<>(this.resultSet.results);
            newResultSet.remove(p);
            resolved.addAll(new TPHResolver(otherSide, new ResultSet(newResultSet)).resolved);
        }
    }

    @Override
    public void visit(PairTPHEqualTPH p) {
        //ignorieren. Wird vom Resolver behandelt
    }

    @Override
    public void visit(RefType refType) {

    }

    @Override
    public void visit(GenericRefType genericRefType) {

    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {

    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {

    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {

    }
}

@SuppressWarnings("rawtypes")
class RelatedTypeWalker implements ResultSetVisitor {

    final Set<GenericInsertPair> relatedTPHs = new HashSet<>();
    private final TypePlaceholder toResolve;
    private final ResultSet resultSet;

    /**
     * Läuft über das resultSet und speichert alle TPHs, welche mit start in Verbindung stehen
     * @param start - kann null sein, wenn der Walker für einen RefType benutzt wird
     * @param resultSet
     */
    RelatedTypeWalker(TypePlaceholder start, ResultSet resultSet){
        this.toResolve = start;
        this.resultSet = resultSet;
        int resolved = 0;
        do{
            resolved = relatedTPHs.size();
            for(ResultPair p : resultSet.results){
                p.accept(this);
                p.accept(this);
            }
        }while(resolved - relatedTPHs.size() > 0);
    }

    @Override
    public void visit(PairTPHsmallerTPH p) {
        if(p.getRight().equals(toResolve)){
            relatedTPHs.addAll(new TPHResolver(p.right, resultSet).resolved);
            //relatedTPHs.addAll(new RelatedTypeWalker(p.right, resultSet).relatedTPHs);
        }
        if(p.getLeft().equals(toResolve)){
            relatedTPHs.addAll(new TPHResolver(p.left, resultSet).resolved);
            //relatedTPHs.addAll(new RelatedTypeWalker(p.left, resultSet).relatedTPHs);
        }
    }

    @Override
    public void visit(PairTPHequalRefTypeOrWildcardType p) {
        if(p.getLeft().equals(toResolve)){
            p.getRight().accept(this);
        }
    }

    @Override
    public void visit(PairTPHEqualTPH p) {
        //Kann ignoriert werden. Diese Fälle werden vom Resolver behandelt
    }

    /*
    Die folgenden Funktionen fügen alle TPHs an die relatedTPHs an, denen sie begegnen:
    Das wird verwendet, wenn alle relatedTPHs aus den Parametern eines RefTypes angefügt werden sollen
     */

    @Override
    public void visit(RefType refType) {
        for(RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()){
            param.accept(this);
        }
    }

    @Override
    public void visit(SuperWildcardType superWildcardType) {
        superWildcardType.getInnerType().accept(this);
    }

    @Override
    public void visit(TypePlaceholder typePlaceholder) {
        relatedTPHs.addAll(new TPHResolver(typePlaceholder, resultSet).resolved);
    }

    @Override
    public void visit(ExtendsWildcardType extendsWildcardType) {
        extendsWildcardType.getInnerType().accept(this);
    }

    @Override
    public void visit(GenericRefType genericRefType) {
    }
}