package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2;

import org.springframework.statemachine.action.Action;
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.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.StateConfigurer;
import org.springframework.statemachine.guard.Guard;

abstract class AbstractThreeStateMachineConfiguration extends AbstractStateMachineConfiguration<States, Events> {

    protected abstract void addAdditionalStates(StateConfigurer<States, Events> states);

    protected abstract void addAdditionalCancellationTransitions(StateMachineTransitionConfigurer<States, Events> transitions);

    protected abstract void addAdditionalTransitions(ExternalTransitionConfigurer<States, Events> transitions) throws Exception;

    protected void addAllCancellationTransitionsForSource(StateMachineTransitionConfigurer<States, Events> transitions, States source) {
        addCancellationTransitionForCancelEvents(transitions, source, Events.EVENT_CANCEL_2);
    }

    private void addCancellationTransitionForCancelEvents(
            StateMachineTransitionConfigurer<States, Events> transitions,
            States source,
            Events... cancelEvents) {
        addTransitionForMultipleEvents(
                transitions,
                source,
                States.CANCEL,
                transitionConfigurer -> transitionConfigurer.action((context) -> System.out.println(context.getEvent().toString() + " " + context.getTransition().toString())),
                cancelEvents
        );
    }

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

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
        StateConfigurer<States, Events> end = states
                .withStates()
                .initial(States.STATE1)
                .state(States.STATE2)
                .state(States.STATE3)
                .state(States.STATE4)
                .state(States.STATE5)
                .state(States.STATE6)
                .state(States.STATE7)
                .state(States.STATE8)
                .state(States.STATE9)
                .state(States.STATE10)
                .state(States.STATE11)
                .state(States.STATE12)
                .state(States.STATE13)
                .state(States.STATE14)
                .state(States.STATE15)
                .state(States.STATE16)
                .state(States.STATE17)
                .state(States.STATE18)
                .state(States.STATE19)
                .state(States.STATE20)
                .state(States.STATEX)
                .choice(States.STATEY)
                .end(States.STATEZ);
        addAdditionalStates(end);
    }

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

        // External transitions
        ExternalTransitionConfigurer<States, Events> t1 = 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);

        transitions.withExternal().source(States.STATE1).target(States.CANCEL).event(Events.EVENT_CANCEL)
                .and()
                .withExternal().source(States.STATE2).target(States.CANCEL).event(Events.EVENT_CANCEL)
                .and()
                .withExternal().source(States.STATE3).target(States.CANCEL).event(Events.EVENT_CANCEL)
                .and()
                .withExternal().source(States.STATE4).target(States.CANCEL).event(Events.EVENT_CANCEL)
                .and()
                .withExternal().source(States.STATE5).target(States.CANCEL).event(Events.EVENT_CANCEL);

        // EXTRA CHOICE
        transitions.withExternal().source(States.STATE4).target(States.STATEY).event(Events.EVENTY);
        transitions.withChoice().source(States.STATEY).first(States.STATEX, guardVarEquals("value1")).last(States.STATEZ);

        // 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);

        addAdditionalTransitions(t1);

        addAdditionalCancellationTransitions(transitions);
    }

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

    protected 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);
    }
}
