package org.codefilarete.stalactite.engine.runtime.cycle;

import java.util.Map;
import java.util.Set;

import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.configurer.onetomany.FirstPhaseCycleLoadListener;
import org.codefilarete.stalactite.engine.runtime.SecondPhaseRelationLoader;
import org.codefilarete.stalactite.sql.result.BeanRelationFixer;

/**
 * Loader in case of same entity type present in entity graph as child of itself : A.b -> B.c -> C.a
 * Sibling is not the purpose of this class.
 *
 * Implemented as such :
 * - very first query reads cycling entity type identifiers
 * - a secondary query is executed to load subgraph with above identifiers
 *
 * @param <SRC> type of root entity graph
 * @param <TRGT> cycling entity type (the one to be loaded)
 * @param <TRGTID> cycling entity identifier type
 *
 */
public class ManyToManyCycleLoader<SRC, TRGT, TRGTID> extends AbstractCycleLoader<SRC, TRGT, TRGTID> {
	
	public ManyToManyCycleLoader(EntityPersister<TRGT, TRGTID> targetPersister) {
		super(targetPersister);
	}
	
	/**
	 * Implemented to read very first identifiers of source type
	 */
	public FirstPhaseCycleLoadListener<SRC, TRGTID> buildRowReader(String relationName) {
		return (src, targetId) -> {
			if (!SecondPhaseRelationLoader.isDefaultValue(targetId)) {
				ManyToManyCycleLoader.this.currentRuntimeContext.get().addRelationToInitialize(relationName, src, targetId);
			}
		};
	}
	
	@Override
	protected void applyRelationToSource(EntityRelationStorage<SRC, TRGTID> relationStorage,
										 BeanRelationFixer<SRC, TRGT> beanRelationFixer,
										 Map<TRGTID, TRGT> targetPerId) {
		relationStorage.getEntitiesToFulFill().forEach(src -> {
			Set<TRGTID> trgtids = relationStorage.getRelationToInitialize(src);
			if (trgtids != null) {
				trgtids.forEach(targetId -> {
					// because we loop over all source instances of a recursion process (invoking SQL until no more entities need to be loaded),
					// and whereas this method is called only for one iteration of the whole process, we may encountered source that are not concerned
					// by given targetPerId, so we need to check for not null matching, else we would get a null element in target collection
					if (targetPerId.get(targetId) != null) {
						beanRelationFixer.apply(src, targetPerId.get(targetId));
					}
				});
			}
		});
	}
}
