package org.requirementsascode;

import static org.requirementsascode.ModelElementContainer.findModelElement;
import static org.requirementsascode.ModelElementContainer.getModelElements;
import static org.requirementsascode.ModelElementContainer.hasModelElement;
import static org.requirementsascode.ModelElementContainer.saveModelElement;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.requirementsascode.builder.ModelBuilder;
import org.requirementsascode.exception.NoSuchElementInModel;

/**
 * A model is a container for events/commands and the system reactions that
 * handle them.
 * 
 * Either implicitly or explicitly, the model contains use cases and their
 * contained steps. Each step contains the event/command and the system
 * reaction, the condition under which it is executed and the actors it is
 * associated with.
 * 
 * A model is used to configure a {@link ModelRunner}.
 *
 * @author b_muth
 */
public class Model implements Serializable {
	private static final long serialVersionUID = -410733530299609758L;

	private Map<String, Actor> nameToActorMap;
	private Map<String, UseCase> nameToUseCaseMap;
	private Actor userActor;
	private Actor systemActor;

	private Model() {
		this.nameToActorMap = new LinkedHashMap<>();
		this.nameToUseCaseMap = new LinkedHashMap<>();
		this.userActor = newActor("User");
		this.systemActor = newActor("System");
	}

	/**
	 * The only way to build a model and its parts is to use the builder returned by
	 * this method.
	 * 
	 * @return the builder.
	 */
	public static ModelBuilder builder() {
		return new ModelBuilder(new Model());
	}

	/**
	 * Checks whether this model contains the specified actor.
	 *
	 * @param actorName the name of the actor whose existence to check
	 * @return true if this model contains the specified actor, false otherwise
	 */
	public boolean hasActor(String actorName) {
		boolean hasActor = hasModelElement(actorName, nameToActorMap);
		return hasActor;
	}

	/**
	 * Checks whether this model contains the specified use case.
	 *
	 * @param useCaseName the name of the use case whose existence to check
	 * @return true if this model contains the specified use case, false otherwise
	 */
	public boolean hasUseCase(String useCaseName) {
		boolean hasUseCase = hasModelElement(useCaseName, nameToUseCaseMap);
		return hasUseCase;
	}

	/**
	 * Creates a new actor with the specified name. Don't use User or System as actor names,
	 * these are reserved for internal use.
	 * 
	 * @param actorName the actor name
	 * @return the newly created actor
	 */
	public Actor newActor(String actorName) {
		Objects.requireNonNull(actorName);
		Actor actor = new Actor(actorName, this);
		saveModelElement(actor, nameToActorMap);
		return actor;
	}

	public UseCase newUseCase(String useCaseName) {
		Objects.requireNonNull(useCaseName);
		UseCase useCase = new UseCase(useCaseName, this);
		saveModelElement(useCase, nameToUseCaseMap);
		return useCase;
	}

	/**
	 * Finds the actor with the specified name, contained in this model.
	 *
	 * @param actorName the name of the actor to look for
	 * @return the actor if found, or else an empty optional
	 * @throws NoSuchElementInModel if no actor with the specified actorName is
	 *                              found in the model
	 */
	public Actor findActor(String actorName) {
		Actor actor = findModelElement(actorName, nameToActorMap);
		return actor;
	}

	/**
	 * Finds the use case with the specified name, contained in this model.
	 *
	 * @param useCaseName the name of the use case to look for
	 * @return the use case if found, or else an empty optional
	 * @throws NoSuchElementInModel if no use case with the specified useCaseName is
	 *                              found in the model
	 */
	public UseCase findUseCase(String useCaseName) {
		UseCase useCase = findModelElement(useCaseName, nameToUseCaseMap);
		return useCase;
	}

	/**
	 * Returns the actors contained in this model. Do not modify that collection
	 * directly, use {@link #newActor(String)}.
	 *
	 * @return the actors
	 */
	public Collection<Actor> getActors() {
		Collection<Actor> modifiableActors = getModelElements(nameToActorMap);
		return Collections.unmodifiableCollection(modifiableActors);
	}

	/**
	 * Returns the use cases contained in this model.
	 *
	 * @return the use cases
	 */
	public Collection<UseCase> getUseCases() {
		Collection<UseCase> modifiableUseCases = getModifiableUseCases();
		return Collections.unmodifiableCollection(modifiableUseCases);
	}

	Collection<UseCase> getModifiableUseCases() {
		return getModelElements(nameToUseCaseMap);
	}

	/**
	 * Returns the steps of use cases contained in this model.
	 *
	 * @return the use steps
	 */
	public Collection<Step> getSteps() {
		Collection<Step> modifiableSteps = getModifiableSteps();
		return Collections.unmodifiableCollection(modifiableSteps);
	}

	Collection<Step> getModifiableSteps() {
		Collection<UseCase> modifiableUseCases = getModifiableUseCases();
		List<Step> modifiableSteps = modifiableUseCases.stream().map(useCase -> useCase.getModifiableSteps())
				.flatMap(steps -> steps.stream()).collect(Collectors.toList());
		return modifiableSteps;
	}

	/**
	 * Returns the actor representing the default user.
	 *
	 * @return the user actor
	 */
	public Actor getUserActor() {
		return userActor;
	}

	/**
	 * Returns the actor representing the system.
	 *
	 * @return the user actor
	 */
	public Actor getSystemActor() {
		return systemActor;
	}
}

@@ -363,16 +363,44 @@
 izable;%0A
+import java.util.ArrayList;%0A
 import j
@@ -486,31 +486,8 @@
 ap;%0A
-import java.util.List;%0A
 impo
@@ -533,44 +533,8 @@
 cts;
-%0Aimport java.util.stream.Collectors;
 %0A%0Aim
@@ -5110,20 +5110,26 @@
 es();%0A%09%09
-List
+Collection
 %3CStep%3E m
@@ -5149,50 +5149,111 @@
 s = 
-modifiableUseCases.stream().map(useCase -%3E
+new ArrayList%3C%3E();%0A%09%09%0A%09%09for (UseCase useCase : modifiableUseCases) %7B%0A%09%09%09Collection%3CStep%3E useCaseSteps =
  use
@@ -5281,77 +5281,54 @@
 ps()
-)
+;
 %0A%09%09%09
-%09.flatMap(steps -%3E steps.stream()).collect(Collectors.toList());
+modifiableSteps.addAll(useCaseSteps);%0A%09%09%7D
 %0A%09%09r

Remove stream from getModifiableSteps()