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";
import { transitionType } from "./StepState";

export default class SequenceEvaluator {

    public readonly id: string;
    public readonly webSiteId: string;
    public description: string;

    public stepList: Step[];

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

    public addStep(step: Step): void {
        if (step.expression && step.expression.length > 0) {
            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,
                    forbiddenInteractionMadeList,
                    continuingInteractions,
                    finishingInteractionList,
                    enteringInteractionList,
                    interactionsNotConsumed
                } = this.evaluateStep(sequence, step);

                if (forbiddenInteractionMadeList.length > 0) {
                    sequenceEvaluation.containsForbiddenInteraction = true;
                    sequenceEvaluation.forbiddenInteractionList = sequenceEvaluation.forbiddenInteractionList.concat(forbiddenInteractionMadeList)
                }

                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)
                                                                            .reduce((questionList: Question[], question) => {
                                                                                if (!questionList.some((question2: Question) => question2.text === question.text)) {
                                                                                    questionList.push(question);
                                                                                }
                                                                                return questionList;
                                                                            }, []);
                    break;
                }
                evaluatedSequence = interactionsNotConsumed;
            }
            resolve(sequenceEvaluation);
        });
    }

    private evaluateStep(sequence: Interaction[], step: Step): {
        isAccepted: boolean,
        forbiddenInteractionMadeList: Interaction[],
        enteringInteractionList: Interaction[],
        continuingInteractions: Interaction[],
        finishingInteractionList: Interaction[],
        interactionsNotConsumed: Interaction[],
    } {

        const { state, interactionsNotConsumed } = step.followSequence(sequence);
        const forbiddenInteractionMadeList = this.getSequenceInteractionForbiddenByStep(sequence, step);
        if (!state) {
            return {
                isAccepted: false,
                forbiddenInteractionMadeList,
                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 transitions = step.stepDFA.transitions.get(state);
            if (transitions.has(interaction.getLabel())) {
                const destinations = transitions.get(interaction.getLabel());
                if (destinations.some((dest) => step.stepDFA.isFinalState(dest))) {
                    finishingInteractionList.push(interaction);
                }
            } else {
                 if (transitions.has(transitionType.star)) {
                    const destinations = transitions.get(transitionType.star);
                    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,
            forbiddenInteractionMadeList,
            enteringInteractionList,
            continuingInteractions,
            finishingInteractionList,
            interactionsNotConsumed,
        };
    }

    private getSequenceInteractionForbiddenByStep(sequence: Interaction[], step: Step): Interaction[] {
        return sequence.filter((interaction) =>
            step.forbiddenIteractionList.some((forbiddenInteraction) =>
                interaction.toString() === forbiddenInteraction.toString())
        )
    }

}
