package click.kamil.examples.statemachine.forkjoin;

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;

@Configuration
@EnableStateMachineFactory(name = "forkJoinStateMachineFactory")
public class ForkJoinStateMachineConfig extends StateMachineConfigurerAdapter<ForkJoinStateMachineConfig.States, ForkJoinStateMachineConfig.Events> {

    public enum States {
        // Main flow
        START, FORK, JOIN, END,

        // Region 1
        REGION1_STATE1, REGION1_STATE2,

        // Region 2
        REGION2_STATE1, REGION2_STATE2
    }

    public enum Events {
        TO_FORK, R1_NEXT, R2_NEXT, TO_JOIN, TO_END
    }

    @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.START)
                .state(States.FORK)
                .fork(States.FORK)
                .join(States.JOIN)
                .end(States.END)
                .and()
                .withStates()
                .parent(States.FORK)
                .region("Region1")
                .initial(States.REGION1_STATE1)
                .state(States.REGION1_STATE2)
                .and()
                .withStates()
                .parent(States.FORK)
                .region("Region2")
                .initial(States.REGION2_STATE1)
                .state(States.REGION2_STATE2);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
        transitions
                // Initial path to fork
                .withExternal()
                .source(States.START).target(States.FORK).event(Events.TO_FORK)
                .and()
                // Fork: START → Region1 & Region2
                .withFork()
                .source(States.FORK)
                .target(States.REGION1_STATE1)
                .target(States.REGION2_STATE1)
                .and()
                // Region1 flow
                .withExternal()
                .source(States.REGION1_STATE1).target(States.REGION1_STATE2).event(Events.R1_NEXT)
                .and()
                // Region2 flow
                .withExternal()
                .source(States.REGION2_STATE1).target(States.REGION2_STATE2).event(Events.R2_NEXT)
                .and()
                // Join when both regions reach REGIONx_STATE2
                .withJoin()
                .source(States.REGION1_STATE2)
                .source(States.REGION2_STATE2)
                .target(States.JOIN)
                .and()
                // From join to end
                .withExternal()
                .source(States.JOIN).target(States.END).event(Events.TO_END);
    }

    private Action<States, Events> log(String message) {
        return context -> System.out.println("ACTION: " + message);
    }

    private Guard<States, Events> alwaysTrue() {
        return context -> true;
    }
}
