package de.dhbwstuttgart.strucTypes5.solve;



import de.dhbwstuttgart.strucTypes5.algo.Type;
import de.dhbwstuttgart.strucTypes5.constraints.*;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVar;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarAbstract;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarInterface;
import de.dhbwstuttgart.strucTypes5.typeVars.TypeVarRefType;
import de.dhbwstuttgart.typeinference.constraints.Constraint;


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

/**
 * Created by sebastian on 01.05.17.
 */
public class Rules {


    private List<ConstraintInterface> constraintInterfaces = new ArrayList<>();


    /*
    Achtung höchstwahrscheinlich ist \theta auch ein type (eben ein Struktureller ), Dieser muss auch verglichen werden!!!

    - Rules given by Plümicke
    - reduce (1/2)   reduceEqual implemented
    - reduce 2 (not implemented)
    - adapt1   - implemented
    - adapt2   - implemented
    - erase1 - implemented
    - erase2 - implemented
    - swap   - implemented
    - subst  - implemented
    - refl   - implemented
     */


    public List<ConstraintSubstitution> substitutions = new ArrayList<>();

    public void erase1(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintSubType) {
                ConstraintSubType csst = (ConstraintSubType) cs;
                if (csst.getSubtype() instanceof  TypeVarRefType && csst.getSuperType() instanceof  TypeVarRefType) {
                    if (csst.getSubtype().equals(csst.getSuperType())) {
                        constraints.remove(cs);
                        System.out.println("Regel Erase1 "+ cs);
                        return;
                    }
                }
            }
        }
    }

    public void erase2(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintShouldEqual) {
                ConstraintShouldEqual csse = (ConstraintShouldEqual) cs;
                if (csse.getT1() instanceof  TypeVarRefType && csse.getT2() instanceof  TypeVarRefType) {
                    if (csse.getT1().equals(csse.getT2())) {
                        constraints.remove(cs);
                        System.out.println("Regel Erase 2" + cs);
                        return;
                    }
                }
            }
        }
    }



    // swap a Should Equal Constriant. if (Theta is not TypeVar) and (T is TypeVar)
    public void swap(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintShouldEqual) {
                ConstraintShouldEqual csse = (ConstraintShouldEqual) cs;
                TypeVarAbstract theta = csse.getT1();
                TypeVarAbstract typeVar = csse.getT2();
                if (theta instanceof TypeVarRefType && typeVar instanceof  TypeVar) {
                    csse.setT1(typeVar);
                    csse.setT2(theta);
                    System.out.println("Regel Swap" + csse);
                    return;
                }
            }
        }
    }


    /*
    C u { Theta =* Type }
    ----------------------
    C[T -> Typ vereinigt mit T =* Theta
        Wenn T eine Typvariable ist und T in C aber nicht in Theta vorkommt
     */
    public void subst(List<ConstraintAbstract>  constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintShouldEqual) {
                TypeVarAbstract t1 = ((ConstraintShouldEqual) cs).getT1();
                TypeVarAbstract t2 = ((ConstraintShouldEqual) cs).getT2();

                if (t1 instanceof  TypeVar) {
                    if (t2 instanceof TypeVarRefType || t2 instanceof TypeVarInterface) {

                        if (!occursCheck((TypeVar) t1)) {
                            ConstraintSubstitution constraintSubstitution = new ConstraintSubstitution(t1, t2);
                            replaceAll(constraints,t1,t2);
                            substitutions.add(constraintSubstitution);
                            System.out.println("Regel Substitution" + constraintSubstitution);
                            return;
                        }
                    }
                }
            }
        }
    }


    // für substitution
    public boolean occursCheck(TypeVar t) {
        for (ConstraintSubstitution cs : substitutions) {
            if (cs.getT1().equals(t)) {
              return true;
            }
        }
        return false;
    }



    public void replaceAll(List<ConstraintAbstract> constraints , TypeVarAbstract alt , TypeVarAbstract neu ) {

        System.out.println(String.format("Replace All: %s -> %s" , alt , neu ));
        for (ConstraintAbstract cs : constraints)  {
            // replace
            cs.replaceTypeVarAbstract(alt,neu);
        }
    }







    // ToDo Not implemented
    public void reduce(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {


        }
    }

    /*

    // Todo wer sagt dass man nicht irgendwo in der mitte anfängt
    public void reflect(List<ConstraintAbstract> constriants) {
        for (ConstraintAbstract cs : constriants) {
            if (cs instanceof ConstraintSubType) {
                TypeVarAbstract t1 = ((ConstraintSubType) cs).getSubtype();

                if (t1 instanceof TypeVarRefType || t1 instanceof TypeVarInterface ) {
                    // Hier  muss  geschaut werden ob es einen nächsten gibt
                    List<TypeVar> chain = reflectNext(t1,((ConstraintSubType) cs).getSuperType() , constriants);
                    if (chain != null) {
                        System.out.println("Regel Reflect");
                        System.out.println(chain);
                    }
                    else {
                        System.out.println("kein erfolg Reflect");
                        //return;
                    }
                }
            }
        }
    }

    private List<TypeVar> reflectNext(TypeVarAbstract startValue, TypeVarAbstract lastValue , List<ConstraintAbstract> remainingConstraints)  {

        // suche next


        for (ConstraintAbstract cs : remainingConstraints) {
            if (cs instanceof ConstraintSubType) {
                TypeVarAbstract tProbNext = ((ConstraintSubType) cs).getSubtype();
                TypeVarAbstract tNextSuper = ((ConstraintSubType) cs).getSuperType();

                if (tProbNext.equals(lastValue)) {


                    // 1. Supertyp ist der gesuchte Typ
                    if (tNextSuper.equals(startValue)) {
                        List<TypeVar> chain = new ArrayList<>();

                        // Füge Type Var in die Kette ein
                        // ToDo Substituion
                        if (tProbNext instanceof TypeVar) {
                            chain.add((TypeVar) tProbNext);
                        }
                        else {
                            System.err.println(Rules.class.getName() + "LogikFehler");
                        }
                        return chain;
                    }

                    //2. Supertyp != der gesuchte Typ aber TypVar
                    else if (tNextSuper instanceof TypeVar) {
                        List<ConstraintAbstract> remainingConstriants2 = new ArrayList<>();
                        remainingConstriants2.addAll(remainingConstraints);
                        remainingConstriants2.remove(cs);
                        List<TypeVar> chain = reflectNext(startValue,tNextSuper, remainingConstriants2);

                        if (chain != null) {
                            chain.add((TypeVar) tNextSuper);
                            return chain;
                        }
                        else {
                            return null;
                        }
                    }
                    // 3. sonst
                    else {
                        return null;
                    }
                }
            }
        }
        return null;
    }


    */


    public void reflect(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {

            if (cs instanceof  ConstraintSubType) {
                ConstraintSubType css = (ConstraintSubType) cs;
                if (css.getSubtype() instanceof  TypeVarRefType && css.getSuperType() instanceof TypeVar) {

                    List<TypeVarAbstract> reflect = reflectNext(constraints, css.getSubtype(), (TypeVar)  css.getSuperType());
                    if (reflect != null) {
                        System.out.println("Reflect für " + reflect + css.getSubtype());
                        for (TypeVarAbstract tv : reflect) {
                            substitutions.add(new ConstraintSubstitution(tv , css.getSubtype()));
                            // Todo Unsauber wg
                            constraints.remove(cs);
                            return;
                        }
                        return;
                    }
                }
            }
        }
    }

    public List<TypeVarAbstract> reflectNext(List<ConstraintAbstract> constraints , TypeVarAbstract startValue ,TypeVar nextPoint) {

        ConstraintSubType next = next(nextPoint,constraints);

        if (next != null) {

            if (next.getSuperType().equals(startValue)) {
                List<TypeVarAbstract> result = new ArrayList<>();
                result.add(next.getSubtype());
                constraints.remove(next);  // ?? Sauber?
                return result;
            }
            else if (next.getSuperType() instanceof TypeVar  ) {
                List<TypeVarAbstract> result = reflectNext(constraints,startValue,(TypeVar) next.getSuperType());
                if (result != null) {
                    result.add(next.getSubtype());
                    constraints.remove(next); // ? Sauber
                    return result;
                }
            }
        }
        return null;
    }

    public ConstraintSubType next(TypeVar point, List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof  ConstraintSubType) {
                if (((ConstraintSubType) cs).getSubtype().equals(point)) {
                    return (ConstraintSubType) cs;
                }
            }
        }
        return null;
    }





    public void reduceEqual(List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintShouldEqual) {
                ConstraintShouldEqual css = (ConstraintShouldEqual) cs;
                if (css.getT1() instanceof TypeVarInterface && css.getT2() instanceof TypeVarInterface ) {


                    TypeVarInterface st1 = (TypeVarInterface) css.getT1();
                    TypeVarInterface st2 = (TypeVarInterface) css.getT2();

                    InterfaceForConstraint if1 = st1.getInterfaceForConstraint();
                    InterfaceForConstraint if2 = st2.getInterfaceForConstraint();


                    // 1. Prüfe Gleichheit des Namens
                    // Todo .... Name ist noch Objekt TypeVar
                    if (if1.getStrucType().toString().equals(if2.getStrucType().toString())) {

                        // Prüfe ob die Listen gleich lange sind
                        if (if1.getGenerics().size() == if2.getGenerics().size()) {

                            // Erstelle neue Constraints
                            for (int i = 0 ; i < if1.getGenerics().size() ; i++) {
                                TypeVarAbstract tnX = if1.getGenerics().get(i);
                                TypeVarAbstract tnY = if2.getGenerics().get(i);

                                // Ergebnis an ConstraintList
                                constraints.add(new ConstraintShouldEqual(tnX,tnY));
                            }
                            // Lösche element
                            constraints.remove(cs);
                            System.out.println("Regel Reduce Equal" + cs);
                            return;
                        }
                    }
                }
            }
        }
    }



    public void adapt1 (List<ConstraintAbstract> constraints) {
        for (ConstraintAbstract cs : constraints) {
            if (cs instanceof ConstraintSubType) {
                ConstraintSubType csub = (ConstraintSubType) cs;
                ConstraintInterface cinterface =  sucheInterface( csub.getSubtype() , csub.getSuperType() );
                if (cinterface != null) {
                    ConstraintShouldEqual constraintShouldEqual = new ConstraintShouldEqual(cinterface.getSuperType() , csub.getSuperType());
                    constraints.remove(cs);
                    constraints.add(constraintShouldEqual);
                    // Todo Substitution für Types
                    System.out.println("Regel Adapt1 " + cs);
                    return;
                }
            }
        }
    }


    private ConstraintInterface sucheInterface( TypeVarAbstract tvSub  , TypeVarAbstract tvSuper ) {

        for (ConstraintInterface cs : constraintInterfaces) {
            if (  cs.getSubType().equalsForUnify(tvSub) && cs.getSuperType().equalsForUnify(tvSuper) ) {
              return cs;
            }
        }
        return null;
    }




    public void adapt2(List<ConstraintAbstract> constraints) {

        List<ConstraintAbstract> newConstraints = new ArrayList<>();
        List<ConstraintAbstract> constraintsToRemove = new ArrayList<>();

        for (ConstraintAbstract cs : constraints) {

            // Start
            if (cs instanceof  ConstraintSubType) {
                ConstraintSubType css = (ConstraintSubType) cs;
                if (css.getSuperType() instanceof TypeVar) {
                    if (css.getSubtype() instanceof TypeVarInterface || css.getSubtype() instanceof TypeVarRefType) {
                        List<ConstraintAbstract> ctm =  adapt2Next(((ConstraintSubType) cs).getSubtype(), ((ConstraintSubType) cs).getSuperType() , constraints, newConstraints);
                        if (ctm != null) {
                            constraintsToRemove.addAll(ctm);
                            constraintsToRemove.add(cs);
                        }
                        else {
                            constraintsToRemove = null;
                        }


                        break;
                    }
                }
            }
        }
        if (constraintsToRemove != null) {
            System.out.println("Regel Adapt2" + constraintsToRemove);
            constraints.removeAll(constraintsToRemove);


        }
        constraints.addAll(newConstraints);
    }


    public List<ConstraintAbstract> adapt2Next(TypeVarAbstract start , TypeVarAbstract last , List<ConstraintAbstract> constraints, List<ConstraintAbstract> newConstraints) {

        for (ConstraintAbstract cs : constraints) {

            if (cs instanceof  ConstraintSubType) {
                ConstraintSubType css = (ConstraintSubType) cs;
                if (css.getSubtype().equals(last)) {

                    // Next ist irgendein Interface
                    if (css.getSuperType() instanceof TypeVarInterface) {

                        // Next ist gesuchtes Inteface
                        ConstraintInterface constraintInterface  = sucheInterface(start, css.getSuperType());
                        if (constraintInterface != null) {
                            System.out.println("adapt2Next : gefunden");
                            System.out.println(constraintInterface);

                            ArrayList<ConstraintAbstract> constraintsToRemove = new ArrayList<>();


                            // Should Equal ermitteln
                            if (constraintInterface.getSuperType() instanceof TypeVarInterface) {
                                TypeVarInterface iFGiven = (TypeVarInterface) constraintInterface.getSuperType();
                                TypeVarInterface iFthis = (TypeVarInterface) css.getSuperType();

                                List<TypeVarAbstract> iFGivenGenerics = iFGiven.getInterfaceForConstraint().getGenerics();
                                List<TypeVarAbstract> iFthisGenerics = iFthis.getInterfaceForConstraint().getGenerics();

                                if (iFGivenGenerics.size() == iFthisGenerics.size()) {
                                    for (int i = 0 ; i < iFGivenGenerics.size() ; i++ ) {
                                        newConstraints.add(new ConstraintShouldEqual(iFGivenGenerics.get(i) , iFthisGenerics.get(i)));
                                        // Substitution


                                    }
                                }

                            }


                            constraintsToRemove.add(css);
                            return constraintsToRemove;
                        }
                    }
                    else if (css.getSuperType() instanceof TypeVar) {
                        List<ConstraintAbstract> remConst2 = new ArrayList<>();
                        remConst2.addAll(constraints);
                        remConst2.remove(cs);
                        adapt2Next(start,css.getSuperType() , remConst2 , newConstraints);
                    }
                }
            }
        }
        return null;
    }


    // Getter und Setter


    public List<ConstraintInterface> getConstraintInterfaces() {
        return constraintInterfaces;
    }

    public void setConstraintInterfaces(List<ConstraintInterface> constraintInterfaces) {
        this.constraintInterfaces = constraintInterfaces;
    }

    public List<ConstraintSubstitution> getSubstitutions() {
        return substitutions;
    }

    public void setSubstitutions(List<ConstraintSubstitution> substitutions) {
        this.substitutions = substitutions;
    }
}
