package de.dhbwstuttgart.syntaxtree.factory;

import de.dhbwstuttgart.syntaxtree.SyntaxTree;
import de.dhbwstuttgart.typeinference.unify.PlaceholderRegistry;
import de.dhbwstuttgart.util.Logger;
import java.io.Writer;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import de.dhbwstuttgart.core.JavaTXCompiler;
import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.NullToken;
import de.dhbwstuttgart.parser.SourceLoc;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.FCGenerator;
import de.dhbwstuttgart.parser.scope.JavaClassName;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.GenericTypeVar;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.syntaxtree.type.Void;
import de.dhbwstuttgart.syntaxtree.type.WildcardType;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.result.PairNoResult;
import de.dhbwstuttgart.typeinference.result.PairTPHEqualTPH;
import de.dhbwstuttgart.typeinference.result.PairTPHequalRefTypeOrWildcardType;
import de.dhbwstuttgart.typeinference.result.PairTPHsmallerTPH;
import de.dhbwstuttgart.typeinference.result.ResultPair;
import de.dhbwstuttgart.typeinference.unify.model.*;
import org.antlr.v4.runtime.Token;

public class UnifyTypeFactory {
	
    public static FiniteClosure generateFC(
      List<ClassOrInterface> fromClasses,
      Logger logger,
      ClassLoader classLoader,
      JavaTXCompiler compiler,
      PlaceholderRegistry placeholderRegistry
    ) throws ClassNotFoundException {
        /*
        Die transitive Hülle muss funktionieren.
        Man darf schreiben List<A> extends AL<A>
        und Vector<B> extends List<B>
        hier muss dann aber dennoch die Vererbung V < L < AL
        hergestellt werden.
        In einem solchen Vererbungsbaum dürfen die TPH auch die gleichen Namen haben.
        Generell dürfen sie immer die gleichen Namen haben.
        TODO: die transitive Hülle bilden
         */
        return new FiniteClosure(FCGenerator.toUnifyFC(compiler, fromClasses, classLoader, placeholderRegistry), logger, compiler, placeholderRegistry);
    }

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

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

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

    /**
     * Convert from
     * ASTType -> UnifyType
     */
    public static UnifyType convert(JavaTXCompiler compiler, RefTypeOrTPHOrWildcardOrGeneric t, Boolean innerType, PlaceholderRegistry placeholderRegistry){
        if (t instanceof GenericRefType){
            return UnifyTypeFactory.convert(compiler, (GenericRefType)t, innerType, placeholderRegistry);
        } else if (t instanceof TypePlaceholder){
            return UnifyTypeFactory.convert(compiler, (TypePlaceholder)t, innerType, placeholderRegistry);
        } else if (t instanceof ExtendsWildcardType){
            return UnifyTypeFactory.convert(compiler, (ExtendsWildcardType)t, innerType, placeholderRegistry);
        } else if (t instanceof SuperWildcardType) {
            return UnifyTypeFactory.convert(compiler, (SuperWildcardType) t, innerType, placeholderRegistry);
        } else if (t instanceof RefType){
            return UnifyTypeFactory.convert(compiler, (RefType)t, innerType, placeholderRegistry);
        }
        //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(JavaTXCompiler compiler, RefType t, Boolean innerType, PlaceholderRegistry placeholderRegistry){
        //Check if it is a FunN Type:
        Pattern p = Pattern.compile("Fun(\\d+)[$][$]");
        Matcher m = p.matcher(t.getName().toString());
        boolean b = m.matches();
        if(b){
            Integer N = Integer.valueOf(m.group(1));
            if((N + 1) == t.getParaList().size()){
                return convertFunN(compiler, t.getParaList(), false, placeholderRegistry);
            }
        }
        UnifyType ret;
        List<UnifyType> params = new ArrayList<>();
        if (t.getParaList() != null) {
            for (RefTypeOrTPHOrWildcardOrGeneric pT : t.getParaList()) {
                params.add(UnifyTypeFactory.convert(compiler, pT, true, placeholderRegistry));
            }
        }

        var clazz = compiler.getClass(t.getName());
        if (clazz != null && clazz.isInterface() && clazz.isFunctionalInterface()) {
            var method = clazz.getMethods().stream().filter(x -> Modifier.isAbstract(x.modifier)).findFirst().orElseThrow();
            var methodParams = method.getParameterList().getFormalparalist().stream().map(x -> convert(compiler, x.getType(), true, placeholderRegistry)).toList();
            var generics = StreamSupport.stream(clazz.getGenerics().spliterator(), false).map(GenericTypeVar::getName).toList();
            return new FunInterfaceType(t.getName().toString(), new TypeParams(params), methodParams, convert(compiler, method.getReturnType(), true, placeholderRegistry), generics);
        }

        return new ReferenceType(t.getName().toString(),new TypeParams(params));
    }

    public static UnifyType convertFunN(JavaTXCompiler compiler, List<RefTypeOrTPHOrWildcardOrGeneric> paraList, Boolean innerType, PlaceholderRegistry placeholderRegistry){
        UnifyType ret;
        List<UnifyType> params = new ArrayList<>();
        if(paraList != null && paraList.size() > 0){
            for(RefTypeOrTPHOrWildcardOrGeneric pT : paraList){
                params.add(UnifyTypeFactory.convert(compiler, pT, false, placeholderRegistry));
            }
        }
        ret = FunNType.getFunNType(new TypeParams(params));
        return ret;
    }

    public static UnifyType convert(JavaTXCompiler compiler, TypePlaceholder tph, Boolean innerType, PlaceholderRegistry placeholderRegistry) {
    	if (tph.getName().equals("AFR")) {
          SyntaxTree.logger.info("XXX"+innerType);
    	}
    	PlaceholderType ntph = new PlaceholderType(tph.getName(), tph.getVariance(), placeholderRegistry);
    	ntph.setVariance(tph.getVariance());
    	ntph.setOrCons(tph.getOrCons());
    	ntph.setWildcardtable(tph.getWildcardtable());
    	int in = placeholderRegistry.UnifyTypeFactory_PLACEHOLDERS.indexOf(ntph);
		if (in == -1) {
        placeholderRegistry.UnifyTypeFactory_PLACEHOLDERS.add(ntph);
			ntph.setInnerType(innerType);
			return ntph;
		}
		else {
			 PlaceholderType oldpht = placeholderRegistry.UnifyTypeFactory_PLACEHOLDERS.get(in);
			 oldpht.setInnerType(oldpht.isInnerType() || innerType);
			 return oldpht;
		}
    }

    public static UnifyType convert(JavaTXCompiler compiler, GenericRefType t, Boolean innerType, PlaceholderRegistry placeholderRegistry){
        return new ReferenceType(t.getParsedName(), true);
    }

    public static UnifyType convert(JavaTXCompiler compiler, WildcardType t, Boolean innerType, PlaceholderRegistry placeholderRegistry){
        if(t.isExtends())
            return new ExtendsType(UnifyTypeFactory.convert(compiler, t.getInnerType(), false, placeholderRegistry));
        else if(t.isSuper())
            return new SuperType(UnifyTypeFactory.convert(compiler, t.getInnerType(), false, placeholderRegistry));
        else throw new NotImplementedException();
    }


    public static ConstraintSet<UnifyPair> convert(JavaTXCompiler compiler, ConstraintSet<Pair> constraints, PlaceholderRegistry placeholderRegistry) {
        return constraints.map(c -> UnifyTypeFactory.convert(compiler, c, placeholderRegistry));
    }

    //NEVER USED
    //public static Constraint<UnifyPair> convert(Constraint<Pair> constraint){
    //	Constraint<UnifyPair> unifyPairConstraint = constraint.stream()
    //			.map(UnifyTypeFactory::convert)
    //			.collect(Collectors.toCollection( () -> new Constraint<UnifyPair> (constraint.isInherited(), convert(constraint.getExtendConstraint()))));
    //	return unifyPairConstraint;
    //}

    public static UnifyPair convert(JavaTXCompiler compiler, Pair p, PlaceholderRegistry placeholderRegistry) {
    	UnifyPair ret = null;
        if(p.GetOperator().equals(PairOperator.SMALLERDOT)) {
             ret = generateSmallerDotPair(UnifyTypeFactory.convert(compiler, p.TA1, false, placeholderRegistry)
                    , UnifyTypeFactory.convert(compiler, p.TA2, false, placeholderRegistry), p.getLocation());
            //return ret;
        }else if(p.GetOperator().equals(PairOperator.SMALLERNEQDOT)) {
             ret = generateSmallNotEqualDotPair(UnifyTypeFactory.convert(compiler, p.TA1, false, placeholderRegistry)
                    , UnifyTypeFactory.convert(compiler, p.TA2, false, placeholderRegistry), p.getLocation());
            //return ret;
        }else if(p.GetOperator().equals(PairOperator.EQUALSDOT)) {
             ret = generateEqualDotPair(UnifyTypeFactory.convert(compiler, p.TA1, false, placeholderRegistry)
                    , UnifyTypeFactory.convert(compiler, p.TA2, false, placeholderRegistry), p.getLocation());
            //return ret;
        }else if(p.GetOperator().equals(PairOperator.SMALLER)){
            ret =  generateSmallerPair(UnifyTypeFactory.convert(compiler, p.TA1, false, placeholderRegistry),
                    UnifyTypeFactory.convert(compiler, p.TA2, false, placeholderRegistry), p.getLocation());
        }else            throw new NotImplementedException();
        UnifyType lhs, rhs;
        if (((lhs = ret.getLhsType()) instanceof PlaceholderType) 
        		&& ((PlaceholderType)lhs).isWildcardable() 
        		&&	(rhs = ret.getLhsType()) instanceof PlaceholderType) {
        	if (lhs.getName().equals("AQ")) {
        		// SyntaxTree.logger.info("");
        	}
        	((PlaceholderType)rhs).enableWildcardtable();
        }
        
        if (((rhs = ret.getLhsType()) instanceof PlaceholderType) 
        		&& ((PlaceholderType)rhs).isWildcardable()
        		&&	(lhs = ret.getLhsType()) instanceof PlaceholderType) {
        	if (rhs.getName().equals("AQ")) {
        		// SyntaxTree.logger.info("");
        	}
        	((PlaceholderType)lhs).enableWildcardtable();
        }
        return ret;
    }

    /**
     * Convert from
     * UnifyType -> ASTType
     */
    public static Set<ResultPair> convert(Set<UnifyPair> unifyPairSet, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        return unifyPairSet.stream().map(
                unifyPair -> convert(unifyPair, tphs, placeholderRegistry))
                        .collect(Collectors.toSet());
    }

    public static ResultPair convert(UnifyPair mp, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
    	if (mp == null) { return null;} //kann bei basePairs passieren
        RefTypeOrTPHOrWildcardOrGeneric tl = UnifyTypeFactory.convert(mp.getLhsType(), tphs, placeholderRegistry);
        RefTypeOrTPHOrWildcardOrGeneric tr = UnifyTypeFactory.convert(mp.getRhsType(), tphs, placeholderRegistry);
        if(tl instanceof TypePlaceholder){
            if(tr instanceof TypePlaceholder) {

                if(mp.getPairOp().equals(PairOperator.EQUALSDOT)) {
                    return new PairTPHEqualTPH((TypePlaceholder)tl, (TypePlaceholder)tr);
                    //Einfach ignorieren TODO: Das hier muss ausgebessert werden:
                    //return new PairTPHequalRefTypeOrWildcardType((TypePlaceholder)tl, ASTFactory.createObjectType());
                }else{
                    return new PairTPHsmallerTPH((TypePlaceholder)tl, (TypePlaceholder)tr, convert(mp.getBasePair(), tphs, placeholderRegistry));
                }
            }else if(tr instanceof RefType){
                return new PairTPHequalRefTypeOrWildcardType((TypePlaceholder)tl, (RefType) tr);
            }else if(tr instanceof WildcardType){
                return new PairTPHequalRefTypeOrWildcardType((TypePlaceholder)tl, (WildcardType) tr);
            }else if(tr instanceof GenericRefType){
                return new PairTPHequalRefTypeOrWildcardType((TypePlaceholder)tl, (GenericRefType) tr);
            }else throw new NotImplementedException();
        }else return new PairNoResult(tl, tr);//throw new NotImplementedException();
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(ReferenceType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        if(JavaClassName.Void.equals(t.getName()))return new Void(new NullToken());
        if (t.isGenTypeVar()) return new GenericRefType(t.getName(),new NullToken());
        RefType ret = new RefType(new JavaClassName(t.getName()),convert(t.getTypeParams(), tphs, placeholderRegistry),new NullToken());
        return ret;
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(FunNType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        RefType ret = new RefType(new JavaClassName(t.getName()), convert(t.getTypeParams(), tphs, placeholderRegistry), new NullToken());
        return ret;
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(SuperType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        RefTypeOrTPHOrWildcardOrGeneric innerType = convert(t.getSuperedType(), tphs, placeholderRegistry);
        return new SuperWildcardType(innerType, new NullToken());
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(ExtendsType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        RefTypeOrTPHOrWildcardOrGeneric innerType = convert(t.getExtendedType(), tphs, placeholderRegistry);
        return new ExtendsWildcardType(innerType, new NullToken());
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(PlaceholderType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        TypePlaceholder ret = tphs.get(t.getName());
        if(ret == null){ //Dieser TPH wurde vom Unifikationsalgorithmus erstellt
            ret = TypePlaceholder.fresh(new NullToken(), placeholderRegistry);
            tphs.put(t.getName(), ret);
        }
        ret.setVariance(t.getVariance());
        return ret;
    }

    public static RefTypeOrTPHOrWildcardOrGeneric convert(UnifyType t, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        if(t instanceof FunNType)return convert((FunNType) t, tphs, placeholderRegistry);
        if(t instanceof ReferenceType)return convert((ReferenceType) t, tphs, placeholderRegistry);
        if(t instanceof SuperType)return convert((SuperType) t, tphs, placeholderRegistry);
        if(t instanceof ExtendsType)return convert((ExtendsType) t, tphs, placeholderRegistry);
        if(t instanceof PlaceholderType)return convert((PlaceholderType) t, tphs, placeholderRegistry);
        throw new NotImplementedException("Der Typ "+t+" kann nicht umgewandelt werden");
    }

    private static List<RefTypeOrTPHOrWildcardOrGeneric> convert(TypeParams typeParams, Map<String,TypePlaceholder> tphs, PlaceholderRegistry placeholderRegistry) {
        List<RefTypeOrTPHOrWildcardOrGeneric> ret = new ArrayList<>();
        for(UnifyType uT : typeParams){
            RefTypeOrTPHOrWildcardOrGeneric toAdd = convert(uT, tphs, placeholderRegistry);
            ret.add(toAdd);
        }
        return ret;
    }

}
