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

import java.util.*;

import de.dhbwstuttgart.bytecode.TPHExtractor;
import de.dhbwstuttgart.bytecode.constraint.TPHConstraint;
import de.dhbwstuttgart.bytecode.genericsGeneratorTypes.*;

/**
 * @author fayez
 *
 */
public class ConstraintsSimplifier {
	public static ConstraintsSimplierResult simplifyConstraints(final TPHExtractor tphExtractor, final List<GenericsGeneratorResult> genericsGeneratorResults, final List<String> tphsClass) {
		
		List<TPHConstraint> allCons = tphExtractor.allCons;
		List<TPHConstraint> equalCons = new ArrayList<>();

		/* Step 1 */
		List<ConstraintsWithSameLeftSide> foundCons = GenericsGeneratorUtility.findConstraintsWithLeftSameLeftSide(allCons);
		if(!foundCons.isEmpty()) {
			List<TPHConstraint> equalCons2 = new ArrayList<>();
			GenericsGeneratorUtility.simplifyConstraintsWithSameLeftSide(foundCons,tphExtractor,tphsClass,equalCons2);
			equalCons.addAll(equalCons2);
		}
		
//		if(!simpleCycles.isEmpty()) {
//			handleSimpleCycles(allCons, simpleCycles,tphExtractor);
//			equalCons.addAll(GenericsGeneratorUtility.getEqualConstraints(allCons));
//		}
//		
		List<Cycle> cycles = findCycles(allCons);
		
		if(!cycles.isEmpty()) {
			handleCycle(genericsGeneratorResults, allCons, cycles,tphExtractor);
		}
		/* The right hand side of equal constraint is the new name in name replacement result */
		List<NameReplacementResult> nameReplacementResults = GenericsGeneratorUtility.createNameReplacementResultsFromEqualConstraints(equalCons);
		getEqualsFromNameReplacementResults(genericsGeneratorResults,nameReplacementResults);
		ConstraintsSimplierResult result = new ConstraintsSimplierResult(genericsGeneratorResults, nameReplacementResults);
		return result;
	}

	private static void getEqualsFromNameReplacementResults(List<GenericsGeneratorResult> genericsGeneratorResults,
			List<NameReplacementResult> nameReplacementResults) {
		genericsGeneratorResults.forEach(g->{
			String tph = g.getConstraint().getLeft();
			if (!nameReplacementResults.isEmpty()) {
				collectAllEquals(nameReplacementResults, g, tph);
			}

			if (!nameReplacementResults.isEmpty()) {
				collectAllEquals(nameReplacementResults, g);
			}
		});
		
	}

	private static void collectAllEquals(List<NameReplacementResult> nameReplacementResults, GenericsGeneratorResult g) {
		Set<String> equalsTPHs = g.getEqualsTPHs();
		Set<String> tphsToAdd = getAllEqualTphs(equalsTPHs, nameReplacementResults);
		while (!tphsToAdd.isEmpty()){
			equalsTPHs.addAll(tphsToAdd);
			tphsToAdd = getAllEqualTphs(equalsTPHs, nameReplacementResults);
		}
	}

	private static Set<String> getAllEqualTphs(Set<String> equalsTPHs, List<NameReplacementResult> nameReplacementResults) {
		Set<String> tphsToAdd = new HashSet<>();
		equalsTPHs.forEach(e->{
			collectAllEquals(nameReplacementResults, tphsToAdd,e);
		});
		return tphsToAdd;
	}

	public static void collectAllEquals(List<NameReplacementResult> nameReplacementResults, Set<String> tphsToAdd,
										String tph) {
		List<NameReplacementResult> toRemoveList = new ArrayList<>();
		Optional<NameReplacementResult> nameReplacementResult = nameReplacementResults.stream().filter(n->n.getName().equals(tph)).findFirst();
		nameReplacementResult.ifPresent(toRemove->{
			tphsToAdd.addAll(toRemove.getOldNames());
			toRemoveList.add(toRemove);
		});
		if(!toRemoveList.isEmpty()){
			nameReplacementResults.remove(toRemoveList.get(0));
			toRemoveList.clear();
		}
	}

	/**
	 * @param nameReplacementResults
	 * @param g
	 * @param tph
	 */
	public static void collectAllEquals(List<NameReplacementResult> nameReplacementResults, GenericsGeneratorResult g,
			String tph) {
		List<NameReplacementResult> toRemoveList = new ArrayList<>();
		Optional<NameReplacementResult> nameReplacementResult = nameReplacementResults.stream().filter(n->n.getName().equals(tph)).findFirst();
		nameReplacementResult.ifPresent(toRemove->{
			g.getEqualsTPHs().addAll(toRemove.getOldNames());
			toRemoveList.add(toRemove);
		});
		if(!toRemoveList.isEmpty()){
			nameReplacementResults.remove(toRemoveList.get(0));
			toRemoveList.clear();
		}
	}

	/**
	 * @param genericsGeneratorResults
	 * @param allCons
	 * @param cycles
	 * @param tphExtractor 
	 */
	public static void handleCycle(List<GenericsGeneratorResult> genericsGeneratorResults,
								   List<TPHConstraint> allCons, List<Cycle> cycles, TPHExtractor tphExtractor) {
		GenericsGeneratorUtility.modifyRelationForConstraintsInCycle(cycles);
		List<NameReplacementResult> nameReplacementResults = GenericsGeneratorUtility.substituteTPHSFormCycle(allCons, cycles,tphExtractor);
		GenericsGeneratorUtility.createResults(genericsGeneratorResults,nameReplacementResults);
		GenericsGeneratorUtility.removeEqualConstraints(allCons);
	}

	/**
	 * @param allCons
	 * @return
	 */
	public static List<Cycle> findCycles(List<TPHConstraint> allCons) {
		/* find all cycles */
		CyclesFinder cyclesFinder = new CyclesFinder(allCons);
		List<Cycle> cycles = cyclesFinder.findCycles();
		return cycles;
	}

}
