package de.dhbwstuttgart.syntaxtree.factory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.unify.model.ExtendsType;
import de.dhbwstuttgart.typeinference.unify.model.FiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.FunNType;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;
import de.dhbwstuttgart.typeinference.unify.model.PlaceholderType;
import de.dhbwstuttgart.typeinference.unify.model.ReferenceType;
import de.dhbwstuttgart.typeinference.unify.model.SuperType;
import de.dhbwstuttgart.typeinference.unify.model.TypeParams;
import de.dhbwstuttgart.typeinference.unify.model.UnifyType;


public class UnifyTypeFactory {

    public static FiniteClosure generateFC(List<ClassOrInterface> fromAvailableClasses){
        HashSet<UnifyPair> pairs = new HashSet<>();
        for(ClassOrInterface cl : fromAvailableClasses){
            UnifyType t1 = UnifyTypeFactory.convert(cl.getType());
            UnifyType t2 = UnifyTypeFactory.convert(cl.getSuperClass());
            pairs.add(generateSmallerPair(t1, t2));
        }
        return new FiniteClosure(pairs);
    }

    public static UnifyPair generateSmallerPair(UnifyType tl, UnifyType tr){
        return new UnifyPair(tl, tr, PairOperator.SMALLER);
    }

    public static UnifyPair generateSmallerDotPair(UnifyType tl, UnifyType tr){
        return new UnifyPair(tl, tr, PairOperator.SMALLERDOT);
    }

    public static UnifyPair generateEqualDotPair(UnifyType tl, UnifyType tr){
        return new UnifyPair(tl, tr, PairOperator.EQUALSDOT);
    }

    /**
     * Convert from
     * ASTType -> UnifyType
     */
    public static UnifyType convert(RefTypeOrTPHOrWildcardOrGeneric t){
        if(t instanceof GenericRefType){
            return UnifyTypeFactory.convert((GenericRefType)t);
        }else
        if(t instanceof FunN){
            return UnifyTypeFactory.convert((FunN)t);
        }else if(t instanceof TypePlaceholder){
            return UnifyTypeFactory.convert((TypePlaceholder)t);
        }else if(t instanceof ExtendsWildcardType){
            return UnifyTypeFactory.convert((ExtendsWildcardType)t);
        }else if(t instanceof SuperWildcardType){
            return UnifyTypeFactory.convert((SuperWildcardType)t);
        }else if(t instanceof RefType){
            return UnifyTypeFactory.convert((RefType)t);
        }
        //Es wurde versucht ein Typ umzuwandeln, welcher noch nicht von der Factory abgedeckt ist
        throw new NotImplementedException("Der Typ "+t+" kann nicht umgewandelt werden");
    }

    public static UnifyType convert(RefType t){
        UnifyType ret;
        if(t.getParaList() != null && t.getParaList().size() > 0){
            List<UnifyType> params = new ArrayList<>();
            for(RefTypeOrTPHOrWildcardOrGeneric pT : t.getParaList()){
                params.add(UnifyTypeFactory.convert(pT));
            }
            ret = new ReferenceType(t.getName().toString(),new TypeParams(params));
        }else{
            ret = new ReferenceType(t.getName().toString());
        }
        return ret;
    }

    public static UnifyType convert(FunN t){
        UnifyType ret;
        List<UnifyType> params = new ArrayList<>();
        if(t.getParaList() != null && t.getParaList().size() > 0){
            for(RefTypeOrTPHOrWildcardOrGeneric pT : t.getParaList()){
                params.add(UnifyTypeFactory.convert(pT));
            }
        }
        ret = FunNType.getFunNType(new TypeParams(params));
        return ret;
    }

    public static UnifyType convert(TypePlaceholder tph){
        return new PlaceholderType(tph.getName());
    }

    public static UnifyType convert(GenericRefType t){
        return new PlaceholderType(TypePlaceholder.fresh(t.getOffset()).getName());
    }

    public static UnifyType convert(WildcardType t){
        if(t.isExtends())
            return new ExtendsType(UnifyTypeFactory.convert(t.getInnerType()));
        else if(t.isSuper())
            return new SuperType(UnifyTypeFactory.convert(t.getInnerType()));
        else throw new NotImplementedException();
    }


    public static ConstraintSet<UnifyPair> convert(ConstraintSet<Pair> constraints) {
        return constraints.map(UnifyTypeFactory::convert);
    }

    public static UnifyPair convert(Pair p) {
        if(p.OperatorSmaller()) {
            UnifyPair ret = generateSmallerDotPair(UnifyTypeFactory.convert(p.TA1)
                    , UnifyTypeFactory.convert(p.TA2));
            return ret;
        }else if(p.OperatorEqual()){
            UnifyPair ret = generateEqualDotPair(UnifyTypeFactory.convert(p.TA1)
                    , UnifyTypeFactory.convert(p.TA2));
            return ret;
        }else throw new NotImplementedException();

    }

    /**
     * Convert from
     * UnifyType -> ASTType
     */
/*
    public static Pair convert(UnifyPair mp) {
        Type tl = UnifyTypeFactory.convert(mp.getLhsType());
        Type tr = UnifyTypeFactory.convert(mp.getRhsType());
        return new Pair(tl, tr, mp.getPairOp());
    }

    public static Type convert(ReferenceType t) {
        //TODO: Hier kann man die GTVs extrahieren
        if(t.getName().toString().equals(Void.VOID_NAME))return new Void( 0);
        RefType ret = new RefType(t.getName(),0);
        ret.set_ParaList(convert(t.getTypeParams()));
        return ret;
    }

    public static Type convert(FunNType t) {
        RefType ret = new RefType(t.getName(),0);
        ret.set_ParaList(convert(t.getTypeParams()));
        return ret;
    }

    public static Type convert(SuperType t) {
        RefType innerType = new RefType(t.getSuperedType().getName(),0);
        return new SuperWildcardType(innerType);
    }

    public static Type convert(ExtendsType t) {
        RefType innerType = new RefType(t.getExtendedType().getName(),0);
        return new ExtendsWildcardType(innerType);
    }

    public static Type convert(PlaceholderType t) {
        TypePlaceholder ret = TypePlaceholder.getInstance(t.getName());
        if(ret == null){ //Dieser TPH wurde vom Unifikationsalgorithmus erstellt
            ret = TypePlaceholder.fresh(t.getName(), NULL_NODE);
        }
        return ret;
    }

    public static Type convert(UnifyType t) {
        if(t instanceof FunNType)return convert((FunNType) t);
        if(t instanceof ReferenceType)return convert((ReferenceType) t);
        if(t instanceof SuperType)return convert((SuperType) t);
        if(t instanceof ExtendsType)return convert((ExtendsType) t);
        if(t instanceof PlaceholderType)return convert((PlaceholderType) t);
        throw new NotImplementedException("Der Typ "+t+" kann nicht umgewandelt werden");
    }

    private static List<Type> convert(TypeParams typeParams) {
        List<Type> ret = new ArrayList<>();
        for(UnifyType uT : typeParams){
            Type toAdd = convert(uT);
            ret.add(toAdd);
        }
        return ret;
    }
*/
}
