/*
 * Copyright 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.persist;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import java.util.HashMap;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.TestUtils;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.configurers.StateConfigurer.History;
import org.springframework.statemachine.state.HistoryPseudoState;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
import org.springframework.statemachine.transition.Transition;

public class StateMachinePersistTests extends AbstractStateMachineTests {

	@Override
	protected AnnotationConfigApplicationContext buildContext() {
		return new AnnotationConfigApplicationContext();
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testSimplePersist1() throws Exception {
		context.register(Config1.class);
		context.refresh();
		StateMachine<String, String> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
		stateMachine.start();
		stateMachine.sendEvent("E1");
		assertThat(stateMachine.getState().getIds(), contains("S2"));

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		persister.persist(stateMachine, "xxx");
		persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), contains("S2"));

		stateMachine.sendEvent("E2");
		assertThat(stateMachine.getState().getIds(), contains("S3"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testSimplePersist2() throws Exception {
		context.register(Config2.class);
		context.refresh();
		StateMachine<TestStates, TestEvents> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
		stateMachine.start();
		stateMachine.sendEvent(TestEvents.E1);
		assertThat(stateMachine.getState().getIds(), contains(TestStates.S2));

		InMemoryStateMachinePersist2 stateMachinePersist = new InMemoryStateMachinePersist2();
		StateMachinePersister<TestStates, TestEvents, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		persister.persist(stateMachine, "xxx");
		persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), contains(TestStates.S2));

		stateMachine.sendEvent(TestEvents.E2);
		assertThat(stateMachine.getState().getIds(), contains(TestStates.S3));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testExtendedState() throws Exception {
		context.register(Config1.class);
		context.refresh();
		StateMachine<String, String> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
		stateMachine.start();

		stateMachine.getExtendedState().getVariables().put("foo", "bar");

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		persister.persist(stateMachine, "xxx");
		stateMachine.getExtendedState().getVariables().remove("foo");
		stateMachine = persister.restore(stateMachine, "xxx");

		assertThat(stateMachine.getExtendedState().get("foo", String.class), is("bar"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testSubStates() throws Exception {
		context.register(Config3.class);
		context.refresh();
		StateMachine<String, String> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
		stateMachine.start();
		stateMachine.sendEvent("E1");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S21"));
		stateMachine.sendEvent("E3");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22"));

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		persister.persist(stateMachine, "xxx");
		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22"));

		stateMachine.sendEvent("E2");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S3", "S31"));

		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testRegions() throws Exception {
		context.register(Config4.class);
		context.refresh();
		StateMachine<String, String> stateMachine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
		stateMachine.start();

		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S11", "S21", "S31"));

		stateMachine.sendEvent("E1");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S21", "S31"));
		stateMachine.sendEvent("E2");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S31"));
		stateMachine.sendEvent("E3");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		persister.persist(stateMachine, "xxx");
		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));

		stateMachine.sendEvent("E4");
		stateMachine.sendEvent("E5");
		stateMachine.sendEvent("E6");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S13", "S23", "S33"));

		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S12", "S22", "S32"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testSubsInRegions1() throws Exception {
		context.register(Config51.class, Config52.class);
		context.refresh();

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
		StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
		stateMachine1.start();

		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S11", "S111", "S21"));
		persister.persist(stateMachine1, "xxx");
		stateMachine2 = persister.restore(stateMachine2, "xxx");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S11", "S111", "S21"));

		stateMachine1.sendEvent("E1");
		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S12", "S21"));
		persister.persist(stateMachine1, "xxx");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S11", "S111", "S21"));
		stateMachine2 = persister.restore(stateMachine2, "xxx");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S12", "S21"));

		stateMachine1.sendEvent("E2");
		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S12", "S22", "S221"));
		persister.persist(stateMachine1, "xxx");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S12", "S21"));
		stateMachine2 = persister.restore(stateMachine2, "xxx");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S12", "S22", "S221"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testSubsInRegions2() throws Exception {
			context.register(Config51.class, Config52.class);
			context.refresh();

			InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
			StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

			StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
			StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
			stateMachine1.start();

			stateMachine1.sendEvent("E1");
			assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S12", "S21"));
			stateMachine1.sendEvent("E2");
			assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S12", "S22", "S221"));
			stateMachine1.sendEvent("E3");
			assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S12", "S22", "S222"));
			persister.persist(stateMachine1, "xxx");
			stateMachine2 = persister.restore(stateMachine2, "xxx");
			assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S12", "S22", "S222"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testHistoryFlatShallow() throws Exception {
		// not sure if this test makes any sense as it was done for
		// gh182, but persisting history state which is transitient
		// is not exactly possible
		context.register(Config61.class, Config62.class);
		context.refresh();

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
		StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
		// start gets in state S1
		stateMachine1.start();

		// event E1 takes into state S2
		stateMachine1.sendEvent("E1");
		assertThat(stateMachine1.getState().getIds(), contains("S2"));
		Object history = readHistoryState(stateMachine1);
		assertThat(history, is("S2"));

		// we persist with state S2 and history keeps same S2
		persister.persist(stateMachine1, "xxx");

		stateMachine2 = persister.restore(stateMachine2, "xxx");
		history = readHistoryState(stateMachine2);
		assertThat(history, is("S2"));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void testHistorySubShallow() throws Exception {
		// not sure if this test makes any sense as it was done for
		// gh182, but persisting history state which is transitient
		// is not exactly possible
		context.register(Config71.class, Config72.class);
		context.refresh();

		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

		StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
		StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
		stateMachine1.start();

		stateMachine1.sendEvent("E1");
		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S2", "S21"));

		stateMachine1.sendEvent("E3");
		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S2", "S22"));
		persister.persist(stateMachine1, "xxx");

		stateMachine1.sendEvent("E2");
		assertThat(stateMachine1.getState().getIds(), containsInAnyOrder("S1"));


		stateMachine2 = persister.restore(stateMachine2, "xxx");
		Object history = TestUtils.readField("history", stateMachine1);
		assertThat(history, notNullValue());
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S2", "S22"));

		stateMachine2.sendEvent("E2");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S1"));
		stateMachine2.sendEvent("EH3");
		assertThat(stateMachine2.getState().getIds(), containsInAnyOrder("S2", "S22"));
	}

	@Test
	public void testPersistWithEnd1() throws Exception {
		context.register(Config8.class);
		context.refresh();
		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);
		@SuppressWarnings("unchecked")
		StateMachineFactory<String, String> stateMachineFactory = context.getBean(StateMachineFactory.class);

		StateMachine<String,String> stateMachine = stateMachineFactory.getStateMachine();
		assertThat(stateMachine, notNullValue());
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));

		stateMachine.sendEvent("E1");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2"));
		assertThat(stateMachine.isComplete(), is(true));

		persister.persist(stateMachine, "xxx");

		stateMachine = stateMachineFactory.getStateMachine();
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));

		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2"));
		assertThat(stateMachine.isComplete(), is(true));
	}

	@Test
	public void testPersistWithEnd2() throws Exception {
		context.register(Config8.class);
		context.refresh();
		InMemoryStateMachinePersist1 stateMachinePersist = new InMemoryStateMachinePersist1();
		StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);
		@SuppressWarnings("unchecked")
		StateMachineFactory<String, String> stateMachineFactory = context.getBean(StateMachineFactory.class);

		StateMachine<String, String> stateMachine = stateMachineFactory.getStateMachine();

		// noinspection unchecked
		stateMachine.getStateMachineAccessor().withRegion().addStateMachineInterceptor(new StateMachineInterceptorAdapter<String, String>() {

			@Override
					public void postStateChange(State<String, String> state, Message<String> message, Transition<String, String> transition,
							StateMachine<String, String> stateMachine) {
						try {
					// noinspection unchecked
					persister.persist(stateMachine, "xxx");
				} catch (Exception e) {
					fail("Exception persisting state machine: " + e);
				}
			}
		});
		assertThat(stateMachine, notNullValue());
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));
		persister.persist(stateMachine, "xxx");
		stateMachine = persister.restore(stateMachine, "xxx");
		stateMachine.sendEvent("E1");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2"));
		assertThat(stateMachine.isComplete(), is(true));

		// rather than manually calling the persister, this test now uses the
		// interceptor
		stateMachine = stateMachineFactory.getStateMachine();
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));

		stateMachine = persister.restore(stateMachine, "xxx");
		assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2"));
		assertThat(stateMachine.isComplete(), is(true));
	}

	@Configuration
	@EnableStateMachine
	static class Config1 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S1")
					.state("S1")
					.state("S2")
					.state("S3");
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S1")
					.target("S2")
					.event("E1")
					.and()
				.withExternal()
					.source("S2")
					.target("S3")
					.event("E2");
		}
	}

	@Configuration
	@EnableStateMachine
	static class Config2 extends StateMachineConfigurerAdapter<TestStates, TestEvents> {

		@Override
		public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
			states
				.withStates()
					.initial(TestStates.S1)
					.state(TestStates.S1)
					.state(TestStates.S2)
					.state(TestStates.S3);
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
			transitions
				.withExternal()
					.source(TestStates.S1)
					.target(TestStates.S2)
					.event(TestEvents.E1)
					.and()
				.withExternal()
					.source(TestStates.S2)
					.target(TestStates.S3)
					.event(TestEvents.E2);
		}
	}

	@Configuration
	@EnableStateMachine
	static class Config3 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S1")
					.state("S1")
					.state("S2")
					.state("S3")
					.and()
					.withStates()
						.parent("S2")
						.initial("S21")
						.state("S21")
						.state("S22")
					.and()
					.withStates()
						.parent("S3")
						.initial("S31")
						.state("S31")
						.state("S32");
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S1")
					.target("S2")
					.event("E1")
					.and()
				.withExternal()
					.source("S2")
					.target("S3")
					.event("E2")
					.and()
				.withExternal()
					.source("S21")
					.target("S22")
					.event("E3");
		}
	}

	@Configuration
	@EnableStateMachine
	static class Config4 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S11")
					.state("S11")
					.state("S12")
					.state("S13")
					.and()
				.withStates()
					.initial("S21")
					.state("S21")
					.state("S22")
					.state("S23")
					.and()
				.withStates()
					.initial("S31")
					.state("S31")
					.state("S32")
					.state("S33");
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S11")
					.target("S12")
					.event("E1")
					.and()
				.withExternal()
					.source("S21")
					.target("S22")
					.event("E2")
					.and()
				.withExternal()
					.source("S31")
					.target("S32")
					.event("E3")
					.and()
				.withExternal()
					.source("S12")
					.target("S13")
					.event("E4")
					.and()
				.withExternal()
					.source("S22")
					.target("S23")
					.event("E5")
					.and()
				.withExternal()
					.source("S32")
					.target("S33")
					.event("E6");
		}
	}

	static class Config5 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S11")
					.state("S11")
					.state("S12")
					.state("S13")
					.and()
					.withStates()
						.parent("S11")
						.initial("S111")
						.state("S111")
						.state("S112")
						.and()
				.withStates()
					.initial("S21")
					.state("S21")
					.state("S22")
					.state("S23")
					.and()
					.withStates()
						.parent("S22")
						.initial("S221")
						.state("S221")
						.state("S222");
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S11")
					.target("S12")
					.event("E1")
					.and()
				.withExternal()
					.source("S21")
					.target("S22")
					.event("E2")
					.and()
				.withExternal()
					.source("S221")
					.target("S222")
					.event("E3");
		}

	}

	@Configuration
	@EnableStateMachine(name = "machine1")
	static class Config51 extends Config5 {
	}

	@Configuration
	@EnableStateMachine(name = "machine2")
	static class Config52 extends Config5 {
	}

	@Configuration
	@EnableStateMachine(name = "machine1")
	static class Config61 extends Config6 {
	}

	@Configuration
	@EnableStateMachine(name = "machine2")
	static class Config62 extends Config6 {
	}

	static class Config6 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S1")
					.state("S1")
					.state("S2")
					.state("S3")
					.state("S4")
					.history("SH", History.SHALLOW);
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S1").target("S2").event("E1")
					.and()
				.withExternal()
					.source("S2").target("S3").event("E2")
					.and()
				.withExternal()
					.source("S1").target("S4").event("E3")
					.and()
				.withExternal()
					.source("S2").target("S4").event("E3")
					.and()
				.withExternal()
					.source("S3").target("S4").event("E3")
					.and()
				.withExternal()
					.source("S1").target("SH").event("EH")
					.and()
				.withExternal()
					.source("S2").target("SH").event("EH")
					.and()
				.withExternal()
					.source("S3").target("SH").event("EH");
		}
	}

	@Configuration
	@EnableStateMachine(name = "machine1")
	static class Config71 extends Config7 {
	}

	@Configuration
	@EnableStateMachine(name = "machine2")
	static class Config72 extends Config7 {
	}

	static class Config7 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S1")
					.state("S1")
					.state("S2")
					.history("SH", History.SHALLOW)
					.and()
					.withStates()
						.parent("S2")
						.initial("S21")
						.state("S22")
						.history("S2H", History.SHALLOW);
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S1").target("S2").event("E1")
					.and()
				.withExternal()
					.source("S2").target("S1").event("E2")
					.and()
				.withExternal()
					.source("S21").target("S22").event("E3")
					.and()
				.withExternal()
					.source("S1").target("SH").event("EH1")
					.and()
				.withExternal()
					.source("S2").target("SH").event("EH2")
					.and()
				.withExternal()
					.source("S1").target("S2H").event("EH3")
					.and()
				.withExternal()
					.source("S2").target("S2H").event("EH3");
		}
	}

	@Configuration
	@EnableStateMachineFactory
	static class Config8 extends StateMachineConfigurerAdapter<String, String> {

		@Override
		public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
			config
				.withConfiguration()
					.autoStartup(true);
		}

		@Override
		public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
			states
				.withStates()
					.initial("S1")
					.end("S2");
		}

		@Override
		public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
			transitions
				.withExternal()
					.source("S1")
					.target("S2")
					.event("E1");
		}
	}

	static class InMemoryStateMachinePersist1 implements StateMachinePersist<String, String, String> {

		private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();

		@Override
		public void write(StateMachineContext<String, String> context, String contextObj) throws Exception {
			contexts.put(contextObj, context);
		}

		@Override
		public StateMachineContext<String, String> read(String contextObj) throws Exception {
			return contexts.get(contextObj);
		}
	}

	static class InMemoryStateMachinePersist2 implements StateMachinePersist<TestStates, TestEvents, String> {

		private final HashMap<String, StateMachineContext<TestStates, TestEvents>> contexts = new HashMap<>();

		@Override
		public void write(StateMachineContext<TestStates, TestEvents> context, String contextObj) throws Exception {
			contexts.put(contextObj, context);
		}

		@Override
		public StateMachineContext<TestStates, TestEvents> read(String contextObj) throws Exception {
			return contexts.get(contextObj);
		}
	}

	private static Object readHistoryState(Object stateMachine) throws Exception {
		Object pseudo = TestUtils.readField("history", stateMachine);
		if (pseudo instanceof HistoryPseudoState) {
			Object state = TestUtils.readField("state", pseudo);
			if (state != null) {
				return TestUtils.callMethod("getId", state);
			}
		}
		return null;
	}
}
