public enum ActionType {

    MESSAGE_RECEIVED, SET_NODE_ID,
    INTENT_UNDERSTANDING_SUCCEEDED, INTENT_UNDERSTANDING_PARTIAL, INTENT_UNDERSTANDING_FAILED,
    EDGE_MATCH_SUCCEEDED, EDGE_MATCH_FAILED

    } public interface IConversationEdge<I extends IConversationIntent, S extends IConversationState> {

    public Integer getEndNodeId();
    
    public String getIntentId();
    
    /**
     * Additional logic to perform before continuing the conversation along this edge. It's possible for
     * a node to have multiple edges with the same intentId. Each edge could have different preconditions
     * that must be met in order to match. The validate function allows the engine to check the preconditions
     * against the current state and return true or false. If the edge intent matches but the validate
     * fails, the engine will look at the next edge with the same intent.
     * @param intent the intent from the current user input
     * @param state the current conversation sate
     * @return true if the edge should validate, false otherwise
     */
    public boolean validate(I intent, S state);
    
    /**
     * Side effects that should occur if this edge is validated. Side effects are
     * ways to change the state before the next step in the conversation. The type
     * of Object returned depends on the {@link Redux} {@link Middleware}s that are installed.
     * <p>
     * Side effects should be used to perform actions like loading additional data
     * from a web service or database that is required to respond to the user. The state
     * that is passed in should not be modified directly. Instead the list of actions
     * returned will be passed sequentially to the Redux middleware chain and executed in order.
     * @param intent the validated intent
     * @param state immutable copy of the state
     * @return a list of actions to perform before the next conversation step
     */
    public List<Object> getSideEffects(I intent, S state);
} public interface IConversationEngine {

    /**
     * Accepts a message from the user, interprets the intent, and advances the conversation
     * to the next node along the edge determined by the conversation graph. 
     * <p>
     * If the engine
     * was able to successfully determine the intent and move along the conversation graph,
     * the {@link MessageHandlingResult} will have a value of <code>ok</code> set to <code>true</code>.
     * Otherwise an <code>errorCode</code> and <code>errorMessage</code> will be returned. An
     * <code>INTENT_UNDERSTANDING_FAILED</code> error will occur if the NLU system was
     * unable to match the input to an intent. An <code>EDGE_MATCHING_FAILED</code> will be
     * returned if no suitable outbound edge could be matched to the given input.
     * @param message user's input
     * @return the result of handling the incoming message.
     */
    public MessageHandlingResult handleIncomingMessage(String message);

    public static class MessageHandlingResult {

        public boolean ok;
        public ErrorCode errorCode;
        public String errorMessage;
    }

    public static enum ErrorCode {

        INTENT_UNDERSTANDING_FAILED, EDGE_MATCHING_FAILED
    }
} public interface IConversationIntent {
    /**
     * @return unique ID for the intent.
     */
    public String getIntentId();
    /**
     * Slots are parameters collected from the user's input while detecting intent. As an example, a
     * a slot might be a color. So in the user input, "Press the red button" the intent
     * might be <code>PRESS_BUTTON</code> with a slot of <code>color</code> set to <code>red</code>.
     * @return map of slot keys to slot values
     */
    public Map<String,Object> getSlots();
    
    public boolean getAllRequiredSlotsFilled();
} public interface IConversationNode<E extends IConversationEdge> {

    /**
     * Returns a list of outbound edges from the current node. One matching 
     * edge may be chosen to continue the conversation to the next node.
     * @return outbound edges
     */
    public Iterable<E> getEdges();

    /**
     * Adds an edge to the list of possible outbound edges.
     * @param edge edge to add
     */
    public void addEdge(E edge);

    /**
     * Returns the unique identifier for this node.
     * @return the node id
     */
    public int getId();
    
    /**
     * Node metadata is any additional information the node may
     * need to build platform-specific implementations of itself. The values stored
     * in the metadata will be highly dependent on the final use case and is
     * designed to be highly flexible. 
     * @return JSON metadata
     */
    public JsonObject getMetadata();
    
} public interface ConversationNodeRepository <N extends IConversationNode>{
    public N getNodeById(int id);
} public interface IConversationState <S extends IConversationState> extends Function<Map,S> {
    /**
     * @return the id of the node representing the last message sent to the user
     */
    public Integer getCurrentNodeId();
    /**
     * @return the number of consecutive times the engine has misunderstood the user's input.
     */
    public Integer getMisunderstoodCount();
    /**
     * @return a unique string representation of the current user
     */
    public String getUserId();
    /**
     * @return the current state as a Map
     */
    public Map getStateAsMap();
} public interface IntentDetector<I> {
    public Optional<I> detectIntent(String text, String languageCode, String sessionId);
} public interface Dispatcher <S>{

    public S dispatch(Object action);
    
} @FunctionalInterface
/**
 * Reducers calculate a new state given the previous state and an action.
 * @see <a href="https://redux.js.org/glossary#reducer">https://redux.js.org/glossary#reducer</a>
 */
public interface Reducer {

    Map reduce(Action action, Map currentState);
} public class Redux {

    private static final Logger logger = Logger.getLogger(Store.class.getName());
    /**
     * Creates a Redux {@link Store} that holds the complete state tree of your application.
     * <p>
     * This follows the basic API specified by the original Javascript version except that it
     * also takes a constructor function as required by {@link Store}. The state builder
     * is a {@link Function} that takes a {@link Map} as a parameter and returns
     * an instance of the state as the generic type S.
     * <p>
     * There should only be a single store in your application.
     * @see <a href="https://redux.js.org/api/createstore">https://redux.js.org/api/createstore</a>
     * @param <S> generic type of the store created
     * @param reducer final Reducer function
     * @param state initial state for the application
     * @param stateBuilder function to build a typed state from a map
     * @param middlewares optional middleware
     * @return an instance of a Redux Store.
     */
    public static <S> Store<S> createStore(Reducer reducer, Map state, Function<Map,S> stateBuilder, Middleware<S>... middlewares) {
        return new Store<>(reducer, state, stateBuilder, middlewares);
    }

    /**
     * Combines multiple reducers into a single reducer.
     * <p>
     * As your application grows more complex, you'll want to split your reducing 
     * function into separate functions, each managing independent parts of the state. 
     * The combineReducers helper function turns a map of reducers into a single 
     * reducing function you can pass to createStore. The key of the reducers map
     * dictates the slice of state the reducer will manage.
     * @see <a href="https://redux.js.org/api/combinereducers">https://redux.js.org/api/combinereducers</a>
     * @param reducers map of state keys to reducers
     * @return a combined Reducer
     */
    public static Reducer combineReducers(Map<String, Reducer> reducers) {
        return (Action action, Map currentState) -> {
            boolean isDirty = false;
            Map nextState = new HashMap();
            for (Object key : currentState.keySet()) {
                Map<String,Object> nestedState = (Map<String,Object>)currentState.get(key);
                if (reducers.containsKey((String)key)) {
                    logger.log(Level.FINE, "[REDUX] delegating to reducer {0}", key);
                    Map<String,Object> nextNestedState = reducers.get(key).reduce(action, nestedState);
                    if (!nextNestedState.equals(nestedState)) {
                        isDirty = true;
                    }
                    nextState.put(key, nextNestedState);
                } else {
                    nextState.put(key, nestedState);
                }
            }
            if (isDirty) {
                return nextState;
            } else {
                return currentState;
            }
        };
    }
} public final class Store<S> implements Dispatcher {

    private static final Logger logger = Logger.getLogger(Store.class.getName());

    private Map currentState;

    private final Reducer reducer;
    private ActionDispatcher dispatcher;
    private final Function<Map,S> typedStateBuilder;
    private final Map<UUID, Consumer<Map<String, Object>>> consumers = new HashMap<>();

    @FunctionalInterface
    public interface ActionDispatcher {

        public void dispatch(Object action);

    }

    protected Store(Reducer reducer, Map initialState, Function<Map,S> stateBuilder, Middleware... middlewares) {
        this.reducer = reducer;
        this.currentState = initialState;
        this.typedStateBuilder = stateBuilder;

        List<Middleware> allMiddlewares = new ArrayList();
        
        for (Middleware mw : middlewares) {
            allMiddlewares.add(mw);
        }
        //native middleware, last middleware in chain
        allMiddlewares.add((store, action, next) -> {
            Map nextState;
            synchronized (Store.this) {
                logger.fine(String.format("[REDUX] reducing action: %s", action.toString()));
                if (!(action instanceof Action)) {
                    throw new RuntimeException("The action must be an instance of Action by the time it is received by the reducer. Action is " + action.getClass().getName());
                }
                Action a = (Action) action;
                nextState = store.reducer.reduce(a, currentState);
            }
            if (!nextState.equals(currentState)) {
                logger.fine(String.format("[REDUX] state has changed after %s", action.toString()));
                currentState = nextState;
                consumers.values().parallelStream().forEach(e -> e.accept(currentState));
            } else {
                logger.fine(String.format("[REDUX] state has not changed after %s", action.toString()));
            }
        });

        logger.info(String.format("[REDUX] initializing redux store with %d middleware(s).", (allMiddlewares.size() - 1)));

        for (int i = allMiddlewares.size() - 1; i >= 0; i--) {
            final Middleware mw = allMiddlewares.get(i);
            logger.fine(String.format("[REDUX] chaining middleware (%d)", i));
            //this will be null for the native middleware only, which is last
            final Middleware next = (i == allMiddlewares.size() - 1 ? null : allMiddlewares.get(i + 1));
            this.dispatcher = (action) -> {
                mw.dispatch(Store.this, action, next);
            };

        }
    }

    @Override
    public S dispatch(Object action) {
        logger.fine(String.format("[REDUX] dispatching action: %s", action.toString()));

        dispatcher.dispatch(action);
        logger.finer(String.format("[REDUX] reduced state: %s", getState().toString()));
        return getState();
    }

    public S getState() {
        return typedStateBuilder.apply(currentState);
    }

    public UUID subscribe(Consumer<Map<String, Object>> subscriber) {
        UUID uuid = UUID.randomUUID();
        this.consumers.put(uuid, subscriber);

        return uuid;
    }

    public void unsubscribe(UUID uuid) {

        this.consumers.remove(uuid);
    }
} public class CompletableFutureMiddleware implements Middleware {

    private static final Logger logger = Logger.getLogger(CompletableFutureMiddleware.class.getName());
    
    @Override
    public void dispatch(Store store, Object action, Middleware next) {
        if (action instanceof Future) {
            logger.log(Level.FINE, "middleware handling future action {0}", action.toString());
            Future f = (Future) action;
            try {
                Object a = f.get();
                next.dispatch(store, a, next);
            } catch (Exception ex) {
                throw new RuntimeException("Middleware received an unhandled exception. Catch exceptions in your lambda expression.", ex);
            }
        } else {
            logger.log(Level.FINE, "middleware ignoring action {0}", action.toString());
            next.dispatch(store, action, next);
        }
    }

}
import com.conversationkit.impl.action.ActionType;
import com.conversationkit.model.IConversationEdge;
import com.conversationkit.model.IConversationEngine;
import com.conversationkit.model.IConversationIntent;
import com.conversationkit.model.IConversationNode;
import com.conversationkit.model.ConversationNodeRepository;
import com.conversationkit.model.IConversationState;
import com.conversationkit.nlp.IntentDetector;
import com.conversationkit.redux.Dispatcher;
import com.conversationkit.redux.Reducer;
import com.conversationkit.redux.Redux;
import com.conversationkit.redux.Store;
import com.conversationkit.redux.impl.CompletableFutureMiddleware;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
 * The MIT License
 *
 * Copyright 2016 Synclab Consulting LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.conversationkit.impl;


/**
 * An implementation of IConversationEngine that holds the current state of a
 * conversation and determines the flow by interpreting user intent and matching
 * it to the conversation graph.
 * <p>
 * The engine creates a Redux {@link Store} when initialized to hold the state
 * of the conversation for a given user. The state is only mutated by {@link DirectedConversationEngine#dispatch
 * }ing actions. This provides predictable state that is easier to test.
 * <p>
 * The {@link IntentDetector} is responsible for matching the user input with an
 * edge on the conversation graph. Most likely you will want to wrap a
 * commercial NLU service like Amazon Lex or Google DialogFlow.
 * <p>
 * The general flow of {@link #handleIncomingMessage(java.lang.String)} in this
 * implementation is as follows:
 * <ol>
 * <li>Receive a message and update the state with that message.</li>
 * <li>Delegate to the IntentDetector to try to determine the user's
 * intent.</li>
 * <li>Loop over all outbound edges for the current node and find the first edge
 * that matches the intent and has {@link IConversationEdge#validate }
 * that returns true.</li>
 * <li>Dispatch any side effects for the matched edge and process on the Redux
 * middleware chain.</li>
 * <li>Update the state with the new node id.</li>
 * </ol>
 * <p>
 * If any of the above steps fail, {@link #handleIncomingMessage} will return an
 * error.
 *
 * @author pdtyreus
 * @param <S> type of IConversationState
 * @param <I> type of IConversationIntent
 */
public class DirectedConversationEngine<S extends IConversationState, I extends IConversationIntent> implements Dispatcher, IConversationEngine {

    private static Logger logger = Logger.getLogger(DirectedConversationEngine.class.getName());
    protected final ConversationNodeRepository nodeRepository;