import ModelService from "../application/ModelService";
import Ngram from "../domain/Ngram";
import Note from "../domain/Note";
import NoteDistribution from "../domain/NoteDistribution";
import Sequence from "../domain/Sequence";
import Stimulus from "../domain/Stimulus";
import CSPModel from "../domain/CSPModel";
import {Express} from "express";

const STATUS_INVALID_PARAMETERS = 400;
const STATUS_SERVER_ERROR = 500;
export default function attachRoutes(app: Express , modelService: ModelService): void {

    app.get("/model/ping", (req, res) => {
        res.send('alive');
    });

    app.get("/model/:modelId", (req, res) => {
        const {modelId} = req.params;
        modelService.mountModel(modelId)
        .then((model) => {
            if (model instanceof CSPModel) {
                res.json({
                    id: model.id,
                    depth: model.depth,
                    interpolationfactor: model.interpolationfactor
                });
            } else {
                res.json({
                    id: model.id,
                    depth: model.depth
                });
            }
        })
        .catch((e) => {
            res.status(STATUS_SERVER_ERROR).send({message: e.message});
        });
    });
    app.post("/model/create", (req, res) => {
        const {depth, interpolationfactor, predictionType} = req.body;
        let modelCreationPromise;
        switch (predictionType) {
            case "CSP":
                modelCreationPromise = modelService.createModelCSP_Model(depth, interpolationfactor);
                break;
            case "SP":
                modelCreationPromise = modelService.createSP_Model(depth);
                break;
            case "FIS":
                modelCreationPromise = modelService.createFIS_Model(depth);
                break;
            default:
                modelCreationPromise = modelService.createModelCSP_Model(depth, interpolationfactor);
                break;
        }
        modelCreationPromise.then((model) => {
            res.json(model);
        })
        .catch((e) => {
            console.error(e);
            res.status(STATUS_SERVER_ERROR).send({message: e.message});
        });
    });

    app.post("/model/:modelId/link/:sessionId", (req, res) => {
        const {modelId, sessionId} = req.params;
        modelService.linkModelWithSessionIdAndSynchronize(modelId, sessionId)
        .then( (model) => {
            res.json(model.id);
        })
        .catch((e) => {
            console.error(e);
            res.status(STATUS_SERVER_ERROR).send({message: e.message});
        });
    });

    app.post("/model/:modelId/getprobabilitymap", (req, res) => {
        const {interactionList} = req.body;
        const {modelId} = req.params;
        if (!interactionList || !Array.isArray(interactionList)) {
            return res.status(STATUS_INVALID_PARAMETERS).send(`invalid parameter interactionList ${interactionList}`);
        }

        const seq = new Sequence();

        interactionList.forEach((interaction) => {
            let value = interaction.kind;
            if (interaction.value !== undefined) {
                value = value + "$" + interaction.value;
            }
            if (interaction.concreteType === "Action") {
                seq.addStimulus(new Stimulus(value));
            } else if (interaction.concreteType === "Comment") {
                seq.addNote(new Note(value));
            }
        });
        modelService.getStimulusProbabilityMap(modelId, seq)
        .then((probabilityMap) => {
            return res.json(Array.from(probabilityMap));
        })
        .catch( (e) => {
            console.error(e);
            res.status(STATUS_SERVER_ERROR).send({message: e.message});
        });
    });

    app.post("/model/:modelId/getcommentlist", (req, res) => {
        const {interactionList} = req.body;
        const {modelId} = req.params;

        const sequence = new Sequence();
        if (!interactionList || !Array.isArray(interactionList)) {
            return res.status(STATUS_INVALID_PARAMETERS).send(`invalid parameter interactionList ${interactionList}`);
        }
        interactionList.forEach((interaction) => {
            let value = interaction.kind;
            if (interaction.value !== undefined) {
                value = value + "$" + interaction.value;
            }
            if (interaction.concreteType === "Action") {
                sequence.addStimulus(new Stimulus(value));
            }
        });
        modelService.getNoteDistributionListMap(modelId, sequence)
        .then((noteDistributionListMap) => {
            const data : any[] = [];
            noteDistributionListMap.forEach((distributionList, note) => {
                data.push({
                    note,
                    distributions: distributionList.map((distribution: NoteDistribution) => ({
                        contextOccurence: distribution.contextOccurence,
                        noteOccurence: distribution.noteOccurence,
                        context: distribution.context.map((stimulus: Stimulus) => stimulus.value),
                    })),
                });
            });
            return res.json(data);
        })
        .catch( (e) => {
            console.error(e);
            res.status(STATUS_SERVER_ERROR).send("error");
        });
    });

    app.get("/model/:modelId/analyze/allngram", (req, res) => {
        const {modelId} = req.params;
        modelService.getAllNgram(modelId)
        .then( (result) => {
            res.json(ngramToJSON(result));
        })
        .catch((e) => {
            res.status(STATUS_SERVER_ERROR).send(e);
        });
    });

    app.post("/model/cross_entropy_evolution", (req, res) => {
        const {sessionId_list, depth, interpolationfactor} = req.body;
        if (sessionId_list === undefined) {
            return res.status(STATUS_INVALID_PARAMETERS).send("sessionId_list parameter is required");
         } else if (!Array.isArray(sessionId_list)) {
            return res.status(STATUS_INVALID_PARAMETERS).send("sessionId_list must be an array of session id");
         } else {
             modelService.getCrossEntropyEvolutionForSessions(sessionId_list, depth, interpolationfactor)
             .then((crossEntropies) => {
                 res.send(crossEntropies);
             }).catch((e) => {
                 console.log(e);
                 res.send(e);
             });
         }
    });

    app.get("/model/profile_coverage/:modelId1/:modelId2", (req, res) => {
        const {modelId1, modelId2} = req.params;
        if (modelId1 === undefined) {
            return res.status(STATUS_INVALID_PARAMETERS).send("modelId1 parameter is required");
        }
        if (modelId2 === undefined) {
            return res.status(STATUS_INVALID_PARAMETERS).send("modelId2 parameter is required");
        }
        modelService.getProfileCoverage(modelId1, modelId2)
            .then((coverage) => res.send({coverage}))
            .catch((e) => {
                console.log(e);
                res.status(STATUS_INVALID_PARAMETERS).send(e);
            });
    });

}

function ngramToJSON(ngramSet: Ngram[]): any[] {
    const jsonData : any[]= [];
    ngramSet.forEach( (ngram) => {
        const ngramData : {
            key: string,
            n: number,
            occurence: number,
            successorStimulusSet: any[],
            successorNoteSet: any[],
        }= {
            key: ngram.key,
            n: ngram.n,
            occurence: ngram.occurence,
            successorStimulusSet: [],
            successorNoteSet: [],
        };
        for (const [key, occurence] of ngram.successorStimulus.entries()) {
            ngramData.successorStimulusSet.push({key, occurence});
        }
        for (const [key, occurence] of ngram.successorNote.entries()) {
            ngramData.successorNoteSet.push({key, occurence});
        }

        jsonData.push(ngramData);
    });
    return jsonData;
}
