package click.kamil.examples.statemachine.complex;

import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
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.guard.Guard;

import java.util.EnumSet;
@Configuration
@EnableStateMachineFactory(name = "complexStateMachineFactory")
public class ComplexStateMachineConfig extends StateMachineConfigurerAdapter<ComplexStateMachineConfig.States, ComplexStateMachineConfig.Events> {

    public enum States {
        STATE1, STATE2, STATE3, STATE4, STATE5,
        STATE6, STATE7, STATE8, STATE9, STATE10,
        STATE11, STATE12, STATE13, STATE14, STATE15,
        STATE16, STATE17, STATE18, STATE19, STATE20
    }

    public enum Events {
        EVENT1, EVENT2, EVENT3, EVENT4, EVENT5,
        EVENT6, EVENT7, EVENT8, EVENT9, EVENT10,
        EVENT11, EVENT12, EVENT13, EVENT14, EVENT15
    }

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

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
        states
                .withStates()
                .initial(States.STATE1)
                .states(EnumSet.allOf(States.class))
                .stateEntry(States.STATE5, logEntryAction("Entered STATE5"))
                .stateExit(States.STATE10, logExitAction("Exited STATE10"));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {

        // External transitions
        transitions
                .withExternal().source(States.STATE1).target(States.STATE2).event(Events.EVENT1)
                .and()
                .withExternal().source(States.STATE2).target(States.STATE3).event(Events.EVENT2)
                .and()
                .withExternal().source(States.STATE3).target(States.STATE4).event(Events.EVENT3)
                .and()
                .withExternal().source(States.STATE4).target(States.STATE5).event(Events.EVENT4)
                .and()
                .withExternal().source(States.STATE5).target(States.STATE6).event(Events.EVENT5)
                .and()
                .withExternal().source(States.STATE6).target(States.STATE7).event(Events.EVENT6)
                .and()
                .withExternal().source(States.STATE7).target(States.STATE8).event(Events.EVENT7)
                .and()
                .withExternal().source(States.STATE8).target(States.STATE9).event(Events.EVENT8)
                .and()
                .withExternal().source(States.STATE9).target(States.STATE10).event(Events.EVENT9)
                .and()
                .withExternal().source(States.STATE10).target(States.STATE11).event(Events.EVENT10)
                .and()
                .withExternal().source(States.STATE11).target(States.STATE12).event(Events.EVENT11)
                .and()
                .withExternal().source(States.STATE12).target(States.STATE13).event(Events.EVENT12)
                .and()
                .withExternal().source(States.STATE13).target(States.STATE14).event(Events.EVENT13)
                .and()
                .withExternal().source(States.STATE14).target(States.STATE15).event(Events.EVENT14)
                .and()
                .withExternal().source(States.STATE15).target(States.STATE16).event(Events.EVENT15);

        // Choice transitions (at least 10)
        transitions
                .withChoice()
                .source(States.STATE16)
                .first(States.STATE17, guardVarEquals("value1"))
                .then(States.STATE18, guardVarEquals("value2"))
                .last(States.STATE19)
                .and()
                .withChoice()
                .source(States.STATE17)
                .first(States.STATE20, guardEventHeaderEquals("header1", "foo"))
                .last(States.STATE16)
                .and()
                .withChoice()
                .source(States.STATE18)
                .first(States.STATE19, guardVarEquals("value3"))
                .last(States.STATE20)
                .and()
                .withChoice()
                .source(States.STATE19)
                .first(States.STATE1, guardVarEquals("reset"))
                .last(States.STATE20)
                .and()
                .withChoice()
                .source(States.STATE20)
                .first(States.STATE5, guardEventHeaderEquals("header2", "bar"))
                .last(States.STATE1)
                .and()
                .withChoice()
                .source(States.STATE11)
                .first(States.STATE13, guardVarEquals("goTo13"))
                .then(States.STATE14, guardVarEquals("goTo14"))
                .last(States.STATE15)
                .and()
                .withChoice()
                .source(States.STATE12)
                .first(States.STATE10, guardVarEquals("goBack10"))
                .last(States.STATE11)
                .and()
                .withChoice()
                .source(States.STATE14)
                .first(States.STATE12, guardVarEquals("loop12"))
                .last(States.STATE16)
                .and()
                .withChoice()
                .source(States.STATE9)
                .first(States.STATE8, guardVarEquals("stepBack"))
                .then(States.STATE7, guardVarEquals("stepBackMore"))
                .last(States.STATE6)
                .and()
                .withChoice()
                .source(States.STATE8)
                .first(States.STATE9, guardVarEquals("forward9"))
                .last(States.STATE10);
    }

    private Guard<States, Events> guardVarEquals(String expected) {
        return context -> {
            Object var = context.getExtendedState().getVariables().get("var");
            return expected.equals(var);
        };
    }

    private Guard<States, Events> guardEventHeaderEquals(String headerName, String expected) {
        return context -> {
            Object header = context.getMessageHeader(headerName);
            return expected.equals(header);
        };
    }

    private Action<States, Events> logEntryAction(String msg) {
        return context -> System.out.println(msg);
    }

    private Action<States, Events> logExitAction(String msg) {
        return context -> System.out.println(msg);
    }
}
