package org.codefilarete.stalactite.engine.configurer.builder;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Queue;

import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfigurationProvider;
import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.PersisterRegistry;
import org.codefilarete.tool.collection.KeepOrderSet;

/**
 * Running context of {@link org.codefilarete.stalactite.engine.configurer.builder.DefaultPersisterBuilder}. Allows to share some information during configuration of the whole entity graph.
 * The currently available instance is accessible through {@link #CURRENT} variable which is created and destroyed by
 * {@link org.codefilarete.stalactite.engine.configurer.builder.DefaultPersisterBuilder#buildOrGiveExisting(EntityMappingConfiguration)}
 * 
 * @author Guillaume Mary
 */
public class PersisterBuilderContext {
	
	/**
	 * Give access to current {@link PersisterBuilderContext}, created and destroyed by
	 * {@link org.codefilarete.stalactite.engine.configurer.builder.DefaultPersisterBuilder#build(EntityMappingConfigurationProvider)}
	 * 
	 * Made static because several {@link org.codefilarete.stalactite.engine.configurer.builder.DefaultPersisterBuilder}s are instantiated along the build process.
	 * Not the best design ever, but works !
	 */
	public static final ThreadLocal<PersisterBuilderContext> CURRENT = new ThreadLocal<>();
	
	/**
	 * List of post initializers to be invoked after persister instantiation and main configuration
	 */
	private final KeepOrderSet<BuildLifeCycleListener> buildLifeCycleListeners = new KeepOrderSet<>();
	
	/**
	 * Currently treated configurations. Made to detect cycle in graph. 
	 */
	private final Queue<Class> treatedConfigurations = Collections.asLifoQueue(new ArrayDeque<>());
	
	private final PersisterRegistry persisterRegistry;
	
	public PersisterBuilderContext(PersisterRegistry persisterRegistry) {
		this.persisterRegistry = persisterRegistry;
	}
	
	public PersisterRegistry getPersisterRegistry() {
		return persisterRegistry;
	}
	
	public KeepOrderSet<BuildLifeCycleListener> getBuildLifeCycleListeners() {
		return buildLifeCycleListeners;
	}
	
	public void addBuildLifeCycleListener(BuildLifeCycleListener listener) {
		buildLifeCycleListeners.add(listener);
	}
	
	/**
	 * Executes some code by remembering given configuration as treated so cycle (already configured entity) can be detected through {@link #isCycling(EntityMappingConfiguration)}
	 * 
	 * @param entityMappingConfiguration a configuration to remember as being currently treated
	 * @param callable some code to run
	 */
	public void runInContext(EntityMappingConfiguration<?, ?> entityMappingConfiguration, Runnable callable) {
		runInContext(entityMappingConfiguration.getEntityType(), callable);
	}
	
	/**
	 * Executes some code by remembering given configuration as treated so cycle (already configured entity) can be detected through {@link #isCycling(EntityMappingConfiguration)}
	 *
	 * @param entityMappingConfiguration a configuration to remember as being currently treated
	 * @param callable some code to run
	 */
	public void runInContext(EntityPersister<?, ?> entityMappingConfiguration, Runnable callable) {
		runInContext(entityMappingConfiguration.getClassToPersist(), callable);
	}
	
	private void runInContext(Class entityClass, Runnable callable) {
		try {
			treatedConfigurations.add(entityClass);
			callable.run();
		} finally {
			treatedConfigurations.remove();
		}
	}
	
	/**
	 * Checks if given configuration was already treated by current build process. {@link #runInContext(EntityMappingConfiguration, Runnable)} should
	 * have be invoked somehow before.
	 *
	 * @param entityMappingConfiguration configuration to be checked for cycle
	 * @return true if given configuration was already processed
	 */
	public boolean isCycling(EntityMappingConfiguration<?, ?> entityMappingConfiguration) {
		return treatedConfigurations.contains(entityMappingConfiguration.getEntityType());
	}
}
