package com.jscape.util.fsm;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Alex Usun
 */
public class CommonStateTransitions
        implements StateTransitions {

    private final Map<Integer, State> states;

    public CommonStateTransitions(Collection<Entry> entries) {
        this.states = new HashMap<Integer, State>();
        initStates(entries);
    }

    public CommonStateTransitions(Entry... entries) {
        this(Arrays.asList(entries));
    }

    public State stateFor(State currentState, Event event)
            throws IllegalTransitionException {
        Integer key = keyFor(currentState, event);
        State targetState = this.states.get(key);
        assertStateValid(currentState, event, targetState);

        return targetState;
    }

    @Override
    public String toString() {
        return "CommonStateTransitions {"
                + "states=" + this.states
                + '}';
    }

    private void initStates(Collection<Entry> entries) {
        for (Entry entry : entries) {
            Integer key = keyFor(entry.initialState, entry.eventClass);
            this.states.put(key, entry.targetState);
        }
    }

    private Integer keyFor(State state, Event event) {
        return keyFor(state, event.getClass());
    }

    private Integer keyFor(State state, Class eventClass) {
        return 31 * state.hashCode() + eventClass.hashCode();
    }

    private void assertStateValid(State currentState, Event event, State state)
            throws IllegalTransitionException {
        if (state == null) {
            throw new IllegalTransitionException(currentState, event);
        }
    }

    public static class Entry {

        public final State initialState;
        public final Class eventClass;
        public final State targetState;

        public Entry(State initialState, Class eventClass, State targetState) {
            this.initialState = initialState;
            this.eventClass = eventClass;
            this.targetState = targetState;
        }

        @Override
        public String toString() {
            return "Entry {"
                    + "initialState=" + this.initialState
                    + ", eventClass=" + this.eventClass
                    + ", targetState=" + this.targetState
                    + '}';
        }
    }
}
