EASystemFactory.java

package net.bmahe.genetics4j.core;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;

import net.bmahe.genetics4j.core.combination.ChromosomeCombinator;
import net.bmahe.genetics4j.core.combination.ChromosomeCombinatorResolver;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluator;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorBulkAsync;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorSync;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluatorVirtualThread;
import net.bmahe.genetics4j.core.mutation.MutationPolicyHandlerResolver;
import net.bmahe.genetics4j.core.mutation.Mutator;
import net.bmahe.genetics4j.core.replacement.ReplacementStrategyHandler;
import net.bmahe.genetics4j.core.selection.SelectionPolicyHandlerResolver;
import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
import net.bmahe.genetics4j.core.spec.AbstractEAExecutionContext;
import net.bmahe.genetics4j.core.spec.EAConfiguration;
import net.bmahe.genetics4j.core.spec.EAConfigurationBulkAsync;
import net.bmahe.genetics4j.core.spec.EAExecutionContext;
import net.bmahe.genetics4j.core.spec.combination.CombinationPolicy;
import net.bmahe.genetics4j.core.spec.mutation.MutationPolicy;

/**
 * Factory class providing convenient methods for creating properly configured {@link EASystem} instances.
 * 
 * <p>EASystemFactory simplifies the complex process of assembling all the components required for evolutionary
 * algorithms by providing pre-configured factory methods that handle the initialization and wiring of selection
 * strategies, mutation operators, combination policies, and fitness evaluators.
 * 
 * <p>The factory abstracts away the complexity of manually creating and configuring:
 * <ul>
 * <li><strong>Selection policy resolvers</strong>: Components that match selection strategies to implementations</li>
 * <li><strong>Mutation policy resolvers</strong>: Components that handle different mutation strategies</li>
 * <li><strong>Chromosome combinators</strong>: Components responsible for crossover operations</li>
 * <li><strong>Replacement strategy handlers</strong>: Components managing population replacement</li>
 * <li><strong>Fitness evaluators</strong>: Components handling fitness computation strategies</li>
 * </ul>
 * 
 * <p>Factory methods are organized by evaluation strategy:
 * <ul>
 * <li><strong>Synchronous evaluation</strong>: Traditional single-threaded or parallel evaluation using
 * {@link EAConfiguration}</li>
 * <li><strong>Bulk asynchronous evaluation</strong>: Batch processing for external services or GPU acceleration using
 * {@link EAConfigurationBulkAsync}</li>
 * <li><strong>Custom evaluation</strong>: User-provided {@link FitnessEvaluator} implementations</li>
 * </ul>
 * 
 * <p>Common usage patterns:
 * 
 * <pre>{@code
 * // Simple synchronous evaluation with default thread pool
 * EAConfiguration<Double> config = EAConfigurationBuilder.<Double>builder()
 * 		.chromosomeSpecs(chromosomeSpec)
 * 		.parentSelectionPolicy(Tournament.of(3))
 * 		.combinationPolicy(SinglePointCrossover.build())
 * 		.mutationPolicies(List.of(RandomMutation.of(0.1)))
 * 		.replacementStrategy(Elitism.builder()
 * 				.offspringRatio(0.8)
 * 				.build())
 * 		.build();
 * 
 * EAExecutionContext<Double> context = EAExecutionContextBuilder.<Double>builder()
 * 		.populationSize(100)
 * 		.fitness(genotype -> computeFitness(genotype))
 * 		.termination(Generations.of(100))
 * 		.build();
 * 
 * EASystem<Double> eaSystem = EASystemFactory.from(config, context);
 * 
 * // Asynchronous evaluation for expensive fitness functions
 * EAConfigurationBulkAsync<Double> asyncConfig = EAConfigurationBulkAsyncBuilder.<Double>builder()
 * 		.chromosomeSpecs(chromosomeSpec)
 * 		.parentSelectionPolicy(Tournament.of(3))
 * 		.combinationPolicy(SinglePointCrossover.build())
 * 		.mutationPolicies(List.of(RandomMutation.of(0.1)))
 * 		.replacementStrategy(Elitism.builder()
 * 				.offspringRatio(0.8)
 * 				.build())
 * 		.fitnessBulkAsync(genotypes -> evaluateBatch(genotypes))
 * 		.build();
 * 
 * EASystem<Double> asyncSystem = EASystemFactory.from(asyncConfig, context);
 * 
 * // Custom evaluation strategy
 * FitnessEvaluator<Double> customEvaluator = new CachedFitnessEvaluator<>(baseFitness, cacheSize);
 * EASystem<Double> customSystem = EASystemFactory.from(config, context, executorService, customEvaluator);
 * }</pre>
 * 
 * <p>Factory method selection guide:
 * <ul>
 * <li><strong>Fast fitness functions</strong>: Use synchronous methods with {@link EAConfiguration}</li>
 * <li><strong>Expensive fitness functions</strong>: Use asynchronous methods with {@link EAConfigurationBulkAsync}</li>
 * <li><strong>External fitness computation</strong>: Use custom evaluator methods</li>
 * <li><strong>GPU acceleration</strong>: Use bulk async methods for batch processing</li>
 * </ul>
 * 
 * <p>Thread pool considerations:
 * <ul>
 * <li><strong>Default behavior</strong>: Uses {@link ForkJoinPool#commonPool()} for automatic parallelization</li>
 * <li><strong>Custom thread pools</strong>: Provide specific {@link ExecutorService} for resource control</li>
 * <li><strong>Single-threaded</strong>: Use {@link java.util.concurrent.Executors#newSingleThreadExecutor()}</li>
 * <li><strong>Resource management</strong>: Caller responsible for shutdown of custom thread pools</li>
 * </ul>
 * 
 * @see EASystem
 * @see EAConfiguration
 * @see EAConfigurationBulkAsync
 * @see EAExecutionContext
 * @see FitnessEvaluator
 */
public class EASystemFactory {

	/**
	 * Prevents instantiation since it's a bunch of static methods
	 */
	private EASystemFactory() {
	}

	/**
	 * Creates an {@link EASystem} with a custom fitness evaluator and explicit thread pool.
	 * 
	 * <p>This is the most flexible factory method that allows complete control over all components of the evolutionary
	 * algorithm system. It assembles and wires all necessary components including selection strategies, mutation
	 * operators, chromosome combinators, and replacement strategies.
	 * 
	 * <p>This method is primarily used internally by other factory methods, but can be used directly when you need to
	 * provide a custom {@link FitnessEvaluator} implementation such as cached, distributed, or specialized evaluation
	 * strategies.
	 * 
	 * @param <T>                the type of fitness values, must be comparable for selection operations
	 * @param eaConfiguration    the evolutionary algorithm configuration specifying genetic operators and strategies
	 * @param eaExecutionContext the execution context containing population parameters and fitness functions
	 * @param fitnessEvaluator   the fitness evaluator implementation to use for population evaluation
	 * @return a fully configured {@link EASystem} ready for evolution execution
	 * @throws IllegalArgumentException if any parameter is null
	 * @throws IllegalStateException    if no suitable replacement strategy handler can be found
	 */
	public static <T extends Comparable<T>> EASystem<T> from(final AbstractEAConfiguration<T> eaConfiguration,
			final AbstractEAExecutionContext<T> eaExecutionContext, final FitnessEvaluator<T> fitnessEvaluator) {
		Objects.requireNonNull(eaConfiguration);
		Objects.requireNonNull(eaExecutionContext);
		Objects.requireNonNull(fitnessEvaluator);

		final var selectionPolicyHandlerResolver = new SelectionPolicyHandlerResolver<T>(eaExecutionContext);

		final var parentSelectionPolicyHandler = selectionPolicyHandlerResolver
				.resolve(eaConfiguration.parentSelectionPolicy());

		final var parentSelector = parentSelectionPolicyHandler.resolve(eaExecutionContext,
				eaConfiguration,
				selectionPolicyHandlerResolver,
				eaConfiguration.parentSelectionPolicy());

		final var mutationPolicyHandlerResolver = new MutationPolicyHandlerResolver<T>(eaExecutionContext);

		final var chromosomeCombinatorResolver = new ChromosomeCombinatorResolver<T>(eaExecutionContext);

		final CombinationPolicy combinationPolicy = eaConfiguration.combinationPolicy();
		final List<ChromosomeCombinator<T>> chromosomeCombinators = eaConfiguration.chromosomeSpecs()
				.stream()
				.map((chromosome) -> {
					return chromosomeCombinatorResolver.resolve(combinationPolicy, chromosome);
				})
				.collect(Collectors.toList());

		final List<Mutator> mutators = new ArrayList<>();
		final List<MutationPolicy> mutationPolicies = eaConfiguration.mutationPolicies();
		for (int i = 0; i < mutationPolicies.size(); i++) {
			final var mutationPolicy = mutationPolicies.get(i);

			final var mutationPolicyHandler = mutationPolicyHandlerResolver.resolve(mutationPolicy);
			final var mutator = mutationPolicyHandler
					.createMutator(eaExecutionContext, eaConfiguration, mutationPolicyHandlerResolver, mutationPolicy);

			mutators.add(mutator);

		}

		final var replacementStrategyHandlers = eaExecutionContext.replacementStrategyHandlers();
		final var replacementStrategy = eaConfiguration.replacementStrategy();

		final Optional<ReplacementStrategyHandler<T>> replacementStrategyHandlerOpt = replacementStrategyHandlers.stream()
				.filter(replacementStrategyHandler -> replacementStrategyHandler.canHandle(replacementStrategy))
				.findFirst();

		final ReplacementStrategyHandler<T> replacementStrategyHandler = replacementStrategyHandlerOpt
				.orElseThrow(() -> new IllegalStateException(
						"Could not find an implementation to handle the replacement strategy " + replacementStrategy));
		final var replacementStrategyImplementor = replacementStrategyHandler
				.resolve(eaExecutionContext, eaConfiguration, selectionPolicyHandlerResolver, replacementStrategy);

		final long populationSize = eaExecutionContext.populationSize();

		return new EASystem<>(eaConfiguration,
				populationSize,
				chromosomeCombinators,
				eaConfiguration.offspringGeneratedRatio(),
				parentSelector,
				mutators,
				replacementStrategyImplementor,
				eaExecutionContext,
				fitnessEvaluator);
	}

	/**
	 * Creates an {@link EASystem} with synchronous fitness evaluation and explicit thread pool.
	 * 
	 * <p>This method is ideal for scenarios where fitness computation is relatively fast and can benefit from parallel
	 * evaluation across multiple threads. The fitness function defined in the configuration will be called synchronously
	 * for each individual or in parallel depending on the evaluator implementation.
	 * 
	 * <p>Use this method when:
	 * <ul>
	 * <li>Fitness computation is CPU-bound and relatively fast (&lt; 100ms per evaluation)</li>
	 * <li>You want control over the thread pool used for parallel evaluation</li>
	 * <li>You need deterministic thread pool behavior for testing or resource management</li>
	 * </ul>
	 * 
	 * @param <T>                 the type of fitness values, must be comparable for selection operations
	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
	 * @param eaExecutionContext  the execution context containing population and termination parameters
	 * @param executorService     the thread pool for parallel fitness evaluation (caller responsible for shutdown)
	 * @return a configured {@link EASystem} using synchronous fitness evaluation
	 * @throws IllegalArgumentException if any parameter is null
	 */
	public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
			final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {

		final var fitnessEvaluator = new FitnessEvaluatorSync<>(eaExecutionContext, eaConfigurationSync, executorService);
		return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
	}

	/**
	 * Creates an {@link EASystem} with synchronous fitness evaluation using the common thread pool.
	 * 
	 * <p>This is the most convenient method for creating evolutionary algorithms with simple fitness functions. It uses
	 * {@link ForkJoinPool#commonPool()} for automatic parallelization without requiring explicit thread pool management.
	 * 
	 * <p>This method is ideal for:
	 * <ul>
	 * <li>Quick prototyping and experimentation</li>
	 * <li>Applications where thread pool management is not critical</li>
	 * <li>Fast fitness functions that can benefit from automatic parallelization</li>
	 * <li>Educational purposes and simple examples</li>
	 * </ul>
	 * 
	 * @param <T>                 the type of fitness values, must be comparable for selection operations
	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
	 * @param eaExecutionContext  the execution context containing population and termination parameters
	 * @return a configured {@link EASystem} using synchronous fitness evaluation with common thread pool
	 * @throws IllegalArgumentException if any parameter is null
	 */
	public static <T extends Comparable<T>> EASystem<T> from(final EAConfiguration<T> eaConfigurationSync,
			final EAExecutionContext<T> eaExecutionContext) {
		final ExecutorService executorService = ForkJoinPool.commonPool();
		return from(eaConfigurationSync, eaExecutionContext, executorService);
	}

	/**
	 * Creates an {@link EASystem} with bulk asynchronous fitness evaluation and explicit thread pool.
	 * 
	 * <p>This method is designed for expensive fitness functions that can benefit from batch processing or asynchronous
	 * evaluation. The bulk async evaluator can process entire populations at once, enabling optimization strategies like
	 * GPU acceleration, external service calls, or database batch operations.
	 * 
	 * <p>Use this method when:
	 * <ul>
	 * <li>Fitness computation involves external resources (databases, web services, files)</li>
	 * <li>Evaluation can be accelerated through batch processing (GPU, vectorized operations)</li>
	 * <li>Fitness computation is expensive (&gt; 100ms per evaluation)</li>
	 * <li>You need to optimize I/O operations through batching</li>
	 * </ul>
	 * 
	 * @param <T>                      the type of fitness values, must be comparable for selection operations
	 * @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
	 * @param eaExecutionContext       the execution context containing population and termination parameters
	 * @param executorService          the thread pool for managing asynchronous operations (caller responsible for
	 *                                 shutdown)
	 * @return a configured {@link EASystem} using bulk asynchronous fitness evaluation
	 * @throws IllegalArgumentException if any parameter is null
	 */
	public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
			final EAExecutionContext<T> eaExecutionContext, final ExecutorService executorService) {

		final var fitnessEvaluator = new FitnessEvaluatorBulkAsync<>(eaConfigurationBulkAsync, executorService);
		return from(eaConfigurationBulkAsync, eaExecutionContext, fitnessEvaluator);
	}

	/**
	 * Creates an {@link EASystem} with bulk asynchronous fitness evaluation using the common thread pool.
	 * 
	 * <p>This convenience method provides the benefits of bulk asynchronous evaluation without requiring explicit thread
	 * pool management. It automatically uses {@link ForkJoinPool#commonPool()} for managing asynchronous operations.
	 * 
	 * <p>This method combines the convenience of automatic thread pool management with the power of bulk asynchronous
	 * evaluation, making it ideal for:
	 * <ul>
	 * <li>Expensive fitness functions in research and experimentation</li>
	 * <li>Applications requiring external resource access without complex thread management</li>
	 * <li>GPU-accelerated fitness evaluation with simple setup</li>
	 * <li>Prototype development with advanced evaluation strategies</li>
	 * </ul>
	 * 
	 * @param <T>                      the type of fitness values, must be comparable for selection operations
	 * @param eaConfigurationBulkAsync the bulk async EA configuration with batch fitness evaluation
	 * @param eaExecutionContext       the execution context containing population and termination parameters
	 * @return a configured {@link EASystem} using bulk asynchronous fitness evaluation with common thread pool
	 * @throws IllegalArgumentException if any parameter is null
	 */
	public static <T extends Comparable<T>> EASystem<T> from(final EAConfigurationBulkAsync<T> eaConfigurationBulkAsync,
			final EAExecutionContext<T> eaExecutionContext) {
		final ExecutorService executorService = ForkJoinPool.commonPool();
		return from(eaConfigurationBulkAsync, eaExecutionContext, executorService);
	}

	/**
	 * Creates an {@link EASystem} with virtual thread-based fitness evaluation.
	 *
	 * <p>This method leverages Java 21+ virtual threads to provide massive parallelism for fitness evaluation without
	 * the overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach
	 * ideal for I/O-bound fitness functions and large populations.
	 *
	 * <p>Virtual threads excel in scenarios involving:
	 * <ul>
	 * <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li>
	 * <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li>
	 * <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li>
	 * <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li>
	 * </ul>
	 *
	 * <p>Performance characteristics:
	 * <ul>
	 * <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li>
	 * <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li>
	 * <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li>
	 * <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li>
	 * </ul>
	 *
	 * <p><strong>Requirements:</strong> Java 21+ with virtual threads enabled
	 *
	 * @param <T>                 the type of fitness values, must be comparable for selection operations
	 * @param eaConfigurationSync the synchronous EA configuration with a simple fitness function
	 * @param eaExecutionContext  the execution context containing population and termination parameters
	 * @return a configured {@link EASystem} using virtual thread-based fitness evaluation
	 * @throws IllegalArgumentException      if any parameter is null
	 * @throws UnsupportedOperationException if virtual threads are not available in current JVM
	 */
	public static <T extends Comparable<T>> EASystem<T> fromWithVirtualThreads(
			final EAConfiguration<T> eaConfigurationSync, final EAExecutionContext<T> eaExecutionContext) {

		final var fitnessEvaluator = new FitnessEvaluatorVirtualThread<>(eaExecutionContext, eaConfigurationSync);
		return from(eaConfigurationSync, eaExecutionContext, fitnessEvaluator);
	}
}