import { generate } from "shortid";
import Action from "./Action";
import Answer from "./Answer";
import Interaction from "./Interaction";
import InteractionFactory from "./InteractionFactory";
import Question from "./Question";
import SequenceEvaluation from "./SequenceEvaluation";
import Step from "./Step";

export default class SequenceEvaluator {

    public readonly id: string;
    public readonly sessionId: string;

    public stepList: Step[];
    public name: string;

    constructor(name: string, id?: string) {
        this.name = name;
        this.stepList = [];
        if (id !== undefined && id !== null) {
            this.id = id;
        } else {
            this.id = generate();
        }
    }

    public addStep(step: Step): void {
        this.stepList.push(step);
    }

    public evaluate(sequence: Interaction[]): Promise<SequenceEvaluation> {
        return new Promise((resolve) => {
            const sequenceEvaluation = new SequenceEvaluation();

            let evaluatedSequence = sequence;
            const stepsToEvaluate = this.stepList.slice();

            while (stepsToEvaluate.length > 0) {
                const step = stepsToEvaluate.shift();
                const {isAccepted,
                    continuingInteractions,
                    finishingInteractionList,
                    enteringInteractionList,
                    interactionsNotConsumed} = this.evaluateStep(sequence, step);

                if (isAccepted) {
                    sequenceEvaluation.validatedStepList.push(step);
                } else {
                    sequenceEvaluation.notValidatedStepList = [step, ...stepsToEvaluate];
                    sequenceEvaluation.enteringInteractionList = enteringInteractionList;
                    sequenceEvaluation.continuingActionList = continuingInteractions.filter((interaction) => interaction instanceof Action) as Action[];
                    sequenceEvaluation.finishingInteractionList = finishingInteractionList;
                    sequenceEvaluation.questionList = continuingInteractions.filter((interaction) => interaction instanceof Answer)
                                                                            .map((answer: Answer) => answer.question) as Question[];
                    break;
                }
                evaluatedSequence = interactionsNotConsumed;
            }
            resolve(sequenceEvaluation);
        });
    }

    private evaluateStep(sequence: Interaction[], step: Step): {
        isAccepted: boolean,
        enteringInteractionList: Interaction[],
        continuingInteractions: Interaction[],
        finishingInteractionList: Interaction[],
        interactionsNotConsumed: Interaction[],
    } {
        const { state, interactionsNotConsumed } = step.followSequence(sequence);
        if (!state) {
            return {
                isAccepted: false,
                enteringInteractionList: step.enteringInteractionList,
                continuingInteractions: [],
                finishingInteractionList: [],
                interactionsNotConsumed,
            };
        }

        const isAccepted = step.stepDFA.isFinalState(state);
        const availableInteractionLabelList = Array.from(step.stepDFA.transitions.get(state).keys());
        const availableInteractionList = availableInteractionLabelList.map((label) => InteractionFactory.parseInteraction(label));

        const finishingInteractionList = [];
        availableInteractionList.forEach((interaction) => {
            const destinations = step.stepDFA.transitions.get(state).get(interaction.getLabel());
            if (destinations.some((dest) => step.stepDFA.isFinalState(dest))) {
                finishingInteractionList.push(interaction);
            }
        });

        const interactionList = availableInteractionList.filter((interaction) => !finishingInteractionList.some((finishingInteraction) => finishingInteraction.toString() === interaction.toString()));
        let enteringInteractionList = [];
        let continuingInteractions = [];
        if (state === step.stepDFA.startState) {
            enteringInteractionList = interactionList;
        } else {
            continuingInteractions = interactionList;
        }
        return {
            isAccepted,
            enteringInteractionList,
            continuingInteractions,
            finishingInteractionList,
            interactionsNotConsumed,
        };
    }

}
