/*
 * Copyright 2015-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.statemachine.config.builders;

import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultChoiceTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultEntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultExitTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultHistoryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultJoinTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultJunctionTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.EntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.HistoryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JunctionTransitionConfigurer;
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.HistoryData;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.TransitionData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.security.SecurityRule;
import org.springframework.statemachine.transition.TransitionKind;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * {@link AnnotationBuilder} for {@link TransitionsData}.
 *
 * @author Janne Valkealahti
 *
 * @param <S> the type of state
 * @param <E> the type of event
 */
public class StateMachineTransitionBuilder<S, E>
		extends
		AbstractConfiguredAnnotationBuilder<TransitionsData<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
		implements StateMachineTransitionConfigurer<S, E> {

	private final Collection<TransitionData<S, E>> transitionData = new ArrayList<TransitionData<S, E>>();
	private final Map<S, List<ChoiceData<S, E>>> choices = new HashMap<S, List<ChoiceData<S, E>>>();
	private final Map<S, List<JunctionData<S, E>>> junctions = new HashMap<S, List<JunctionData<S, E>>>();
	private final Map<S, List<S>> forks = new HashMap<S, List<S>>();
	private final Map<S, List<S>> joins = new HashMap<S, List<S>>();
	private final Collection<EntryData<S, E>> entryData = new ArrayList<EntryData<S, E>>();
	private final Collection<ExitData<S, E>> exitData = new ArrayList<ExitData<S, E>>();
	private final Collection<HistoryData<S, E>> historyData = new ArrayList<HistoryData<S, E>>();

	/**
	 * Instantiates a new state machine transition builder.
	 */
	public StateMachineTransitionBuilder() {
		super();
	}

	/**
	 * Instantiates a new state machine transition builder.
	 *
	 * @param objectPostProcessor the object post processor
	 * @param allowConfigurersOfSameType the allow configurers of same type
	 */
	public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor,
			boolean allowConfigurersOfSameType) {
		super(objectPostProcessor, allowConfigurersOfSameType);
	}

	/**
	 * Instantiates a new state machine transition builder.
	 *
	 * @param objectPostProcessor the object post processor
	 */
	public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
		super(objectPostProcessor);
	}

	@Override
	protected TransitionsData<S, E> performBuild() throws Exception {
		return new TransitionsData<>(transitionData, choices, junctions, forks, joins, entryData, exitData, historyData);
	}

	@Override
	public ExternalTransitionConfigurer<S, E> withExternal() throws Exception {
		return apply(new DefaultExternalTransitionConfigurer<S, E>());
	}

	@Override
	public InternalTransitionConfigurer<S, E> withInternal() throws Exception {
		return apply(new DefaultInternalTransitionConfigurer<S, E>());
	}

	@Override
	public LocalTransitionConfigurer<S, E> withLocal() throws Exception {
		return apply(new DefaultLocalTransitionConfigurer<S, E>());
	}

	@Override
	public ChoiceTransitionConfigurer<S, E> withChoice() throws Exception {
		return apply(new DefaultChoiceTransitionConfigurer<S, E>());
	}

	@Override
	public JunctionTransitionConfigurer<S, E> withJunction() throws Exception {
		return apply(new DefaultJunctionTransitionConfigurer<S, E>());
	}

	@Override
	public ForkTransitionConfigurer<S, E> withFork() throws Exception {
		return apply(new DefaultForkTransitionConfigurer<S, E>());
	}

	@Override
	public JoinTransitionConfigurer<S, E> withJoin() throws Exception {
		return apply(new DefaultJoinTransitionConfigurer<S, E>());
	}

	@Override
	public EntryTransitionConfigurer<S, E> withEntry() throws Exception {
		return apply(new DefaultEntryTransitionConfigurer<S, E>());
	}

	@Override
	public ExitTransitionConfigurer<S, E> withExit() throws Exception {
		return apply(new DefaultExitTransitionConfigurer<S, E>());
	}

	@Override
	public HistoryTransitionConfigurer<S, E> withHistory() throws Exception {
		return apply(new DefaultHistoryTransitionConfigurer<S, E>());
	}

	/**
	 * Adds the transition.
	 *
	 * @param source the source
	 * @param target the target
	 * @param state the state
	 * @param event the event
	 * @param period the period
	 * @param count the count
	 * @param actions the actions
	 * @param guard the guard
	 * @param kind the kind
	 * @param securityRule the security rule
	 */
	public void addTransition(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
			Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
		// if rule not given, get it from global
		if (securityRule == null) {
			@SuppressWarnings("unchecked")
			ConfigurationData<S, E> config = getSharedObject(ConfigurationData.class);
			securityRule = config.getTransitionSecurityRule();
		}
		transitionData.add(new TransitionData<>(source, target, state, event, period, count, actions, guard, kind, securityRule));
	}

	/**
	 * Adds the choice.
	 *
	 * @param source the source
	 * @param choices the choices
	 */
	public void addChoice(S source, List<ChoiceData<S, E>> choices) {
		this.choices.put(source, choices);
	}

	/**
	 * Adds the junction.
	 *
	 * @param source the source
	 * @param junctions the junctions
	 */
	public void addJunction(S source, List<JunctionData<S, E>> junctions) {
		this.junctions.put(source, junctions);
	}

	/**
	 * Adds the entry.
	 *
	 * @param source the source
	 * @param target the target
	 */
	public void addEntry(S source, S target) {
		this.entryData.add(new EntryData<S, E>(source, target));
	}

	/**
	 * Adds the exit.
	 *
	 * @param source the source
	 * @param target the target
	 */
	public void addExit(S source, S target) {
		this.exitData.add(new ExitData<S, E>(source, target));
	}

	/**
	 * Adds the fork.
	 *
	 * @param source the source
	 * @param targets the targets
	 */
	public void addFork(S source, List<S> targets) {
		this.forks.put(source, targets);
	}

	/**
	 * Adds the join.
	 *
	 * @param target the target
	 * @param sources the sources
	 */
	public void addJoin(S target, List<S> sources) {
		this.joins.put(target, sources);
	}

	/**
	 * Adds the default history.
	 *
	 * @param source the source
	 * @param target the target
	 */
	public void addDefaultHistory(S source, S target) {
		this.historyData.add(new HistoryData<S, E>(source, target));
	}
}
