package de.dhbwstuttgart.sat.asp.writer;

import de.dhbwstuttgart.exceptions.NotImplementedException;
import de.dhbwstuttgart.parser.SyntaxTreeGenerator.FCGenerator;
import de.dhbwstuttgart.sat.asp.ASPStringConverter;
import de.dhbwstuttgart.sat.asp.model.ASPGencayRule;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.factory.NameGenerator;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.constraints.ConstraintSet;
import de.dhbwstuttgart.typeinference.constraints.Pair;
import de.dhbwstuttgart.typeinference.unify.model.PairOperator;

import java.util.*;


public class ASPGencayFactory implements TypeVisitor<String> {
    /* TODO:
     * Alle TPHs müssen als var(tph) definiert sein
     * Wenn es eine Variable ist, dann direkt in die type-Regel schreiben: type(p, type, tph)
     * Für die FCTypen eindeutige Namen für die pph-Regeln
     *  (ergibt sich von selbst, weil man einfach den Namen der TPH in der FC verwenden kann)
     * paramOrder wird benötigt!
     *  Nur bei parameterlisten > 1
     *  paramOrder(paralistPointer, parameter, num)
     *  (ähnlich meiner paramNum)
     * Trennung von FC und Eq:
     *  sub -> type -> param
     *  ...Eq -> typeEq -> paramEq
     * type..(_,_,_p): p kann sein:
     *  1. Variable
     *  2. ParameterPointer
     *  3. null
     */
    ASPWriter writer = new ASPWriter();
    boolean isFCType = false;

    private static List<ASPStatement> generateVar(ConstraintSet<Pair> constraints){
        List<ASPStatement> ret = new ArrayList<>();
        for(TypePlaceholder tph : getInvolvedTPHS(constraints)){
            ret.add(makeStatement(ASPGencayRule.ASP_TYPE_VAR.toString(),
                    ASPStringConverter.toConstant(tph.getName())));
        }
        return ret;
    }

    protected static Collection<TypePlaceholder> getInvolvedTPHS(ConstraintSet<Pair> toTest) {
        List<TypePlaceholder> ret = new ArrayList<>();
        toTest.map((Pair p)-> {
            ret.addAll(p.TA1.acceptTV(new TPHExtractor()));
            ret.addAll(p.TA2.acceptTV(new TPHExtractor()));
            return p;
        });
        return ret;
    }

    public static String generateASP(ConstraintSet<Pair> constraints, Collection<ClassOrInterface> fcClasses) throws ClassNotFoundException{
        ASPGencayFactory factory = new ASPGencayFactory();
        factory.convertFC(fcClasses);
        List<Constraint<Pair>> constraints1 = constraints.cartesianProduct().iterator().next();
        for(Constraint<Pair> constraint : constraints1){
            for(Pair p : constraint){
                factory.convertPair(p);
            }
        }
        factory.writer.addAll(generateVar(constraints));

        return factory.writer.getASPFile();
    }

    private void convertFC(Collection<ClassOrInterface> classes) throws ClassNotFoundException {
        Set<Pair> fc = FCGenerator.toFC(classes);
        isFCType = true;
        for(Pair fcp : fc){
            generateTheta((RefType) fcp.TA1);
            generateTheta((RefType) fcp.TA2);
            convertPair(fcp);
        }
        isFCType = false;
    }

    private void generateTheta(RefType t){
        String rule = "theta"+t.getParaList().size() ;
        String name = ASPStringConverter.toConstant(t.getName());
        writer.add(makeStatement(rule, name));
    }

    private void convertPair(Pair p){
        String ls = p.TA1.acceptTV(this);
        String rs = p.TA2.acceptTV(this);
        ASPStatement pairStmt = null;
        if(p.GetOperator().equals(PairOperator.SMALLERDOT)){
            pairStmt = makeStatement(ASPGencayRule.ASP_PAIR_SMALLER_DOT_NAME.toString(), ls, rs);
        }else if(p.GetOperator().equals(PairOperator.EQUALSDOT)){
            pairStmt = makeStatement(ASPGencayRule.ASP_PAIR_EQUALS_NAME.toString(), ls, rs);
        }else if(p.GetOperator().equals(PairOperator.SMALLER)){
            pairStmt = makeStatement(ASPGencayRule.ASP_PAIR_SMALLER_NAME.toString(), ls, rs);
        }else throw new NotImplementedException();
        writer.add(pairStmt);
    }

    private static ASPStatement makeStatement(String rule, String... params){
        String stmt = rule + "(";
        for(String param : params){
            stmt += param + ",";
        }
        stmt = stmt.substring(0,stmt.length()-1);
        stmt += ")";
        return new ASPStatement(stmt);
    }

    private String convertParameterlist(List<String> pointers){
        //if(pointers.size()==1)return pointers.get(0);
        String pointer = ASPStringConverter.toConstant(NameGenerator.makeNewName());
        Iterator<String> it = pointers.iterator();
        String p = pointer;
        String paramname = ASPGencayRule.ASP_PARAMLIST_NAME.toString();
        if(this.isFCType)paramname = ASPGencayRule.ASP_FC_PARAMLIST_NAME.toString();
        if(!it.hasNext()){
            return ASPGencayRule.ASP_PARAMLIST_END_POINTER.toString();
        }
        while (it.hasNext()){
            ASPStatement stmt;
            String type = it.next();
            String nextP = ASPStringConverter.toConstant(NameGenerator.makeNewName());
            if(it.hasNext()){
                stmt = makeStatement(paramname, p, type, nextP);
            }else{
                stmt = makeStatement(paramname, p, type,
                        ASPGencayRule.ASP_PARAMLIST_END_POINTER.toString());
            }
            p = nextP;
            writer.add(stmt);
        }
        return pointer;
    }

    //Wird zum erstellen der pph(..) Regeln gebraucht
    private String currentFCTypePointer = "";
    @Override
    public String visit(RefType refType) {
        String pointer = ASPStringConverter.toConstant(NameGenerator.makeNewName());
        currentFCTypePointer = pointer;
        String paramPointer = ASPGencayRule.ASP_PARAMLIST_END_POINTER.toString();
        if(refType.getParaList().size() == 1 ){
            if(refType.getParaList().get(0) instanceof TypePlaceholder){
                TypePlaceholder typePlaceholder = (TypePlaceholder) refType.getParaList().get(0);
                paramPointer = typePlaceholder.acceptTV(this);
            }
        }else{
            List<String> params = null;
            params = generateParameter(refType);
            params.remove(0);
            paramPointer = convertParameterlist(params);
            if(refType.getParaList().size()>1){
                //paramOrder generieren:
                for(String param : params) {
                    ASPStatement pOstmt = makeStatement(ASPGencayRule.ASP_PARAMLIST_ORDER.toString(),
                            paramPointer, param);
                    writer.add(pOstmt);
                }
            }
        }
        String typeName = ASPStringConverter.toConstant(refType.getName());
        String ruleName = isFCType? ASPGencayRule.ASP_FCTYPE.toString(): ASPGencayRule.ASP_TYPE.toString();
        ASPStatement stmt = makeStatement(ruleName, pointer, typeName, paramPointer);
        writer.add(stmt);
        return pointer;
        /*
        String pointer = ASPStringConverter.toConstant(NameGenerator.makeNewName());
        List<String> params = null;
        params = generateParameter(refType);
        params.remove(0); //Das erste ist der eigentliche Typ kein parameter
        String typeName = ASPStringConverter.toConstant(refType.getName());
        String ruleName = isFCType? ASPGencayRule.ASP_FCTYPE.toString(): ASPGencayRule.ASP_TYPE.toString();
        ASPStatement stmt = makeStatement(ruleName, pointer, typeName, convertParameterlist(params));
        writer.add(stmt);
        return pointer;
        */
    }

    /**
     * Erstellt die Parameterliste, wobei type auch schon als Parameter betrachtet wird.
     * Die RefTypes werden nicht zu extra type-Regeln umgewandelt. Es wird nur ihr Name als Konstante benutzt
     * Das funktioniert, weil nacher das ParamOrder zuteilt, welche Typen zusammenhängen.
     * Diese funktion nur verwenden, wenn auch ein paramOrder generiert wird
     */
    private List<String> generateParameter(RefType type){
        List<String> ret = new ArrayList<>();
        ret.add(ASPStringConverter.toConstant(type.getName()));
        for(RefTypeOrTPHOrWildcardOrGeneric param : type.getParaList()){
            if(param instanceof RefType){
                ret.addAll(generateParameter((RefType) param));
            }else if(param instanceof TypePlaceholder){
                ret.add(param.acceptTV(this));
            }else throw new NotImplementedException();
        }
        return ret;
    }

    @Override
    public String visit(SuperWildcardType superWildcardType) {
        throw new NotImplementedException();
    }

    @Override
    public String visit(TypePlaceholder typePlaceholder) {
        String name = ASPStringConverter.toConstant(typePlaceholder.getName());

        ASPStatement stmt = null;
        if(isFCType){
            stmt = makeStatement(ASPGencayRule.ASP_GENERIC_VAR.toString(),
                    currentFCTypePointer, name);
        } else {
            stmt = makeStatement(ASPGencayRule.ASP_TYPE_VAR.toString(), name);
        }

        writer.add(stmt);
        return name;
    }

    @Override
    public String visit(ExtendsWildcardType extendsWildcardType) {
        throw new NotImplementedException();
    }

    @Override
    public String visit(GenericRefType genericRefType) {
        throw new NotImplementedException();
    }

    private static class TPHExtractor implements TypeVisitor<List<TypePlaceholder>>{
        @Override
        public List<TypePlaceholder> visit(RefType refType) {
            ArrayList<TypePlaceholder> ret = new ArrayList<>();
            for(RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()){
                ret.addAll(param.acceptTV(this));
            }
            return ret;
        }

        @Override
        public List<TypePlaceholder> visit(SuperWildcardType superWildcardType) {
            return superWildcardType.getInnerType().acceptTV(this);
        }

        @Override
        public List<TypePlaceholder> visit(TypePlaceholder typePlaceholder) {
            return Arrays.asList(typePlaceholder);
        }

        @Override
        public List<TypePlaceholder> visit(ExtendsWildcardType extendsWildcardType) {
            return extendsWildcardType.getInnerType().acceptTV(this);
        }

        @Override
        public List<TypePlaceholder> visit(GenericRefType genericRefType) {
            return new ArrayList<>();
        }
    }

}