/**
 *
 */
package de.dhbwstuttgart.bytecode.genericsGenerator;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.*;
import org.objectweb.asm.Type;

import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.ExtendsConstraint;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.utilities.MethodAndTPH;

/**
 * @author fayez
 *
 */
public class GenericsGenerator {

    public static GenericsGeneratorResultForClass generateConstraints(final String className, final TPHExtractor tphExtractor,
                                                                      final List<String> tphsClass, final ConstraintsSimplierResult simplifiedConstraints) {

        List<GenericsGeneratorResult> classConstraints = generateConstraintsForClass(tphExtractor,
                simplifiedConstraints, tphsClass);
//		GenericGeneratorResultsForAllMethods methodsAndConstraints = generateConstraintsForAllMethods(tphExtractor,
//				simplifiedConstraints, tphsClass);
        GenericGeneratorResultsForAllMethods methodsAndConstraints = new GenericGeneratorResultsForAllMethods(new ArrayList<>());

        return new GenericsGeneratorResultForClass(className, classConstraints, methodsAndConstraints);
    }


    /**
     * @param tphExtractor
     * @param tphsClass
     * @return
     */
    public static ConstraintsSimplierResult simplifyConstraints(final TPHExtractor tphExtractor,
                                                                final List<String> tphsClass) {
        final List<GenericsGeneratorResult> genericsGeneratorResults = new ArrayList<>();

        ConstraintsSimplierResult simplifiedConstraints = ConstraintsSimplifier.simplifyConstraints(tphExtractor,
                genericsGeneratorResults, tphsClass);
        return simplifiedConstraints;
    }


    private static List<GenericsGeneratorResult> generateConstraintsForClass(final TPHExtractor tphExtractor,
                                                                             final ConstraintsSimplierResult simplifiedConstraints, final List<String> tphsClass) {
        final List<GenericsGeneratorResult> constraints = new ArrayList<>();

        if (tphsClass.isEmpty())
            return constraints;

        final List<TPHConstraint> allCons = tphExtractor.allCons;

        final Set<String> visitedTPHs = new HashSet<>();

        final List<String> methodTPHs = tphExtractor.ListOfMethodsAndTph.stream().map(m -> m.getLocalTphs())
                .flatMap(l -> l.stream()).collect(Collectors.toList());

        createConstraintsForClassTphs(simplifiedConstraints, tphsClass, constraints, allCons, visitedTPHs, methodTPHs);

        GenericsGeneratorUtility.addTPHsToClassTPHs(constraints, tphsClass);
        GenericsGeneratorUtility.removeClassTPHsFromMethodTPHs(tphsClass, tphExtractor);

        generateGGConstraintsForTPH(tphsClass, constraints, simplifiedConstraints);

        return constraints;
    }

    private static void createConstraintsForClassTphs(ConstraintsSimplierResult simplifiedConstraints, List<String> tphsClass, List<GenericsGeneratorResult> constraints, List<TPHConstraint> allCons, Set<String> visitedTPHs, List<String> methodTPHs) {
        for (String tph : tphsClass) {

            if (visitedTPHs.contains(tph))
                continue;

            final LinkedList<String> tphsInRel = GenericsGeneratorUtility
                    .createLinkedListForTPHsInRelationClass(allCons, tphsClass, methodTPHs, visitedTPHs, tph);
            if (!tphsInRel.isEmpty()) {
                GenericsGeneratorResult constraint = generateGGResultForClass(tphsInRel, simplifiedConstraints, tphsClass);
                constraints.add(constraint);
            }
        }
    }

    private static GenericsGeneratorResult generateGGResultForClass(LinkedList<String> tphsInRel,
                                                                    ConstraintsSimplierResult simplifiedConstraints, List<String> tphsClass) {
        String subType = tphsInRel.getFirst();

        String superType = GenericsGeneratorUtility.getNextClassTph(tphsClass, tphsInRel);

        TPHConstraint constraint = new ExtendsConstraint(subType, superType);

        Set<String> equalSet = GenericsGeneratorUtility
                .createEqualSet(simplifiedConstraints.getNameReplacementResults(), subType);

        return new GenericsGeneratorResult(constraint, equalSet);
    }

    private static GenericGeneratorResultsForAllMethods generateConstraintsForAllMethods(
            final TPHExtractor tphExtractor, ConstraintsSimplierResult simplifiedConstraints, List<String> tphsClass) {

        final List<MethodAndConstraints> methodsAndConstraints = new ArrayList<>();
        final GenericGeneratorResultsForAllMethods results = new GenericGeneratorResultsForAllMethods(
                methodsAndConstraints);
        tphExtractor.ListOfMethodsAndTph.forEach(m -> {
            MethodAndConstraints methAndCons = generateConstraintsForMethod(tphExtractor.allCons, m,
                    simplifiedConstraints, tphsClass);
            methodsAndConstraints.add(methAndCons);
        });

        return results;
    }

    public static MethodAndConstraints generateConstraintsForMethod(final List<TPHConstraint> allCons,
                                                                    final MethodAndTPH m, final ConstraintsSimplierResult simplifiedConstraints, List<String> tphsClass) {
        final List<String> localTphs = m.getLocalTphs();

        List<GenericsGeneratorResult> constraints = new ArrayList<>();
        MethodAndConstraints result = new MethodAndConstraints(m.getId(), constraints);

        if (localTphs.isEmpty())
            return result;

        if (localTphs.size() == 1 && tphsClass.isEmpty()) {
            generateGGConstraintsForTPH(localTphs, constraints, simplifiedConstraints);
            return result;
        }

        final Set<String> visitedTPHs = new HashSet<>();

        for (String tph : localTphs) {

            if (visitedTPHs.contains(tph))
                continue;

            final LinkedList<String> tphsInRel = GenericsGeneratorUtility.createLinkedListForTPHsInRelation(allCons,
                    localTphs, tphsClass, visitedTPHs, tph);
            if (!tphsInRel.isEmpty()) {
                GenericsGeneratorUtility.removeNotLocalTphs(tphsInRel, localTphs, tphsClass);
                if (!GenericsGeneratorUtility.isInResult(tphsInRel.getFirst(), constraints)) {
                    GenericsGeneratorResult constraint = generateGGResult(tphsInRel, simplifiedConstraints);
                    constraints.add(constraint);
                }
            }
        }

        generateGGConstraintsForTPH(localTphs, constraints, simplifiedConstraints);
        return result;
    }

    private static void generateGGConstraintsForTPH(List<String> localTphs,
                                                    final List<GenericsGeneratorResult> constraints, final ConstraintsSimplierResult simplifiedConstraints) {

        localTphs.forEach(tph -> {
            boolean isInSimplifiedConstraints = GenericsGeneratorUtility.isTPHInGenericGeneratorResult(simplifiedConstraints.getGenericsGenResults(), tph);
            if (isInSimplifiedConstraints) {
                GenericsGeneratorResult ggResult = GenericsGeneratorUtility.getGenericGeneratorResult(simplifiedConstraints.getGenericsGenResults(), tph).get();
//				GenericsGeneratorUtility.removeGenericGeneratorResult(simplifiedConstraints.getGenericsGenResults(),ggResult);
                constraints.add(ggResult);
            } else {
                boolean isInConstraints = GenericsGeneratorUtility.isTPHInGenericGeneratorResult(constraints, tph);

                if (!isInConstraints) {
                    GenericsGeneratorResult ggResult = generateGGResult(tph, simplifiedConstraints.getNameReplacementResults());
                    constraints.add(ggResult);
                }
            }
        });

    }

    private static GenericsGeneratorResult generateGGResult(String tph,
                                                            List<NameReplacementResult> nameReplacementResults) {
        String superType = Type.getInternalName(Object.class);
        TPHConstraint constraint = new ExtendsConstraint(tph, superType);
        Set<String> equalSet = GenericsGeneratorUtility.createEqualSet(nameReplacementResults, tph);

        return new GenericsGeneratorResult(constraint, equalSet);
    }

    private static GenericsGeneratorResult generateGGResult(final LinkedList<String> tphsInRel,
                                                            final ConstraintsSimplierResult simplifiedConstraints) {

        String subType = tphsInRel.getFirst();

        if (tphsInRel.size() == 1) {
            Optional<GenericsGeneratorResult> constraintFromSimplifiedCon = simplifiedConstraints
                    .getGenericsGenResults().stream().filter(g -> g.getConstraint().getLeft().equals(subType))
                    .findFirst();
            if (constraintFromSimplifiedCon.isPresent())
                return constraintFromSimplifiedCon.get();
        }

        String superType = tphsInRel.size() > 1 ? tphsInRel.getLast() : Type.getInternalName(Object.class);

        TPHConstraint constraint = new ExtendsConstraint(subType, superType);

        Set<String> equalSet = GenericsGeneratorUtility
                .createEqualSet(simplifiedConstraints.getNameReplacementResults(), subType);

        return new GenericsGeneratorResult(constraint, equalSet);
    }

}
