package de.dhbwstuttgart.typeinference.unify;

import de.dhbwstuttgart.util.Logger;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;

import de.dhbwstuttgart.typeinference.constraints.Constraint;
import de.dhbwstuttgart.typeinference.unify.interfaces.IFiniteClosure;
import de.dhbwstuttgart.typeinference.unify.model.UnifyPair;

public class TypeUnify {

	private TypeUnify() {}

	private static <T> T joinFuture(CompletableFuture<T> future) {
		try {
			return future.get();
		}
		catch (InterruptedException | ExecutionException exception) {
			throw new RuntimeException(exception);
		}
	}

	/**
	 * unify parallel ohne result modell
	 */
	public static Set<Set<UnifyPair>> unify(Set<UnifyPair> undConstrains, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, UnifyContext unifyContext) {
		ForkJoinPool pool = TypeUnify.createThreadPool(unifyContext.logger());
		UnifyContext context = unifyContext.newWithParallel(true).newWithExecutor(pool);
		TypeUnifyTask unifyTask = new TypeUnifyTask(undConstrains, oderConstraints, fc, context, 0);
		Set<Set<UnifyPair>> res = joinFuture(unifyTask.compute());

		unifyContext.logger().debug("\nnoShortendElements: " + TypeUnifyTask.noShortendElements + "\n");
		return res;
	}
	
	/**
	 * unify asynchron mit Rückgabe UnifyResultModel ohne dass alle results gesammelt sind
	 */
	public static UnifyResultModel unifyAsync(Set<UnifyPair> undConstrains, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, UnifyContext unifyContext) {
		ForkJoinPool pool = TypeUnify.createThreadPool(unifyContext.logger());
		UnifyContext context = unifyContext.newWithExecutor(pool);
		TypeUnifyTask unifyTask = new TypeUnifyTask(undConstrains, oderConstraints, fc, context, 0);
		unifyTask.compute();
		return unifyContext.resultModel();
	}

	/**
	 * unify parallel mit Rückgabe UnifyResultModel nachdem alle results gesammelt sind
	 */
	public static Set<Set<UnifyPair>> unifyParallel(Set<UnifyPair> undConstrains, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, UnifyContext unifyContext) {
		ForkJoinPool pool = TypeUnify.createThreadPool(unifyContext.logger());
		UnifyContext context = unifyContext.newWithParallel(true).newWithExecutor(pool);
		TypeUnifyTask unifyTask = new TypeUnifyTask(undConstrains, oderConstraints, fc, context, 0);
		var result = joinFuture(unifyTask.compute());

		unifyContext.logger().debug("\nnoShortendElements: " + TypeUnifyTask.noShortendElements + "\n");
		return result;
	}
	
	/*
	public Set<Set<UnifyPair>> unifySequential(Set<UnifyPair> eq, IFiniteClosure fc, FileWriter logFile, Boolean log) {
		TypeUnifyTask unifyTask = new TypeUnifyTask(eq, fc, false, logFile, log);
		Set<Set<UnifyPair>> res = unifyTask.compute();
		return res;
	}
	*/
	
	/**
	 * unify sequentiell mit oderconstraints
	 */
	public static Set<Set<UnifyPair>> unifyOderConstraints(Set<UnifyPair> undConstrains, List<Set<Constraint<UnifyPair>>> oderConstraints, IFiniteClosure fc, UnifyContext unifyContext) {
		TypeUnifyTask unifyTask = new TypeUnifyTask(undConstrains, oderConstraints, fc, unifyContext.newWithParallel(false), 0);
		Set<Set<UnifyPair>> res = joinFuture(unifyTask.compute());
		unifyContext.logger().debug("\nnoShortendElements: " + TypeUnifyTask.noShortendElements +"\n");
		return res;
	}

	private static ForkJoinPool createThreadPool(Logger logger) {
		logger.info("Available processors: " + Runtime.getRuntime().availableProcessors());
		return new ForkJoinPool(
			Runtime.getRuntime().availableProcessors(),
			ForkJoinPool.defaultForkJoinWorkerThreadFactory,
			null,
			false
		);
	}

}
