import Action from "../domain/Action";
import ActionInteraction from "../domain/ActionInteraction";
import Answer from "../domain/Answer";
import AnswerInteraction from "../domain/AnswerInteraction";
import Comment from "../domain/Comment";
import CommentInteraction from "../domain/CommentInteraction";
import Screenshot from "../domain/Screenshot";
import ScreenshotRepository from "../domain/ScreenshotRepository";
import Session from "../domain/Session";
import SessionRepository from "../domain/SessionRepository";
import Tester from "../domain/Tester";
import Video from "../domain/Video";
import VideoRepository from "../domain/VideoRepository";
import WebSiteRepository from "../domain/WebSiteRepository";
import EventStore from "./EventStore";

const CACHE_SIZE: number = 5;

export default class SessionService {
    private readonly sessionRepository: SessionRepository;
    private readonly webSiteRepository: WebSiteRepository;
    private readonly eventStore: EventStore;
    private readonly screenshotRepository: ScreenshotRepository;
    private readonly videoRepository: VideoRepository;
    private mountedSessionList: Session[];

    constructor(sessionRepository: SessionRepository, webSiteRepository: WebSiteRepository, eventStore: EventStore, screenshotRepository: ScreenshotRepository, videoRepository: VideoRepository) {
        this.sessionRepository = sessionRepository;
        this.webSiteRepository = webSiteRepository;
        this.eventStore = eventStore;
        this.screenshotRepository = screenshotRepository;
        this.videoRepository = videoRepository;
        this.mountedSessionList = [];
    }

    public createNewSessionForWebSiteId(webSiteId: string, baseURL: string): Promise<string> {
        return this.webSiteRepository.findWebSiteById(webSiteId)
            .then((webSite) => {
                if (webSite === undefined) {
                    throw new Error(`Failed to find a website for name : ${webSiteId}`);
                }
                const session: Session = new Session(webSite, baseURL);
                this.addSessionInCache(session);
                return this.sessionRepository.addSession(session);
            }).then((newSessionId) => {
                return newSessionId;
            });
    }

    public mountSession(sessionId: string): Promise<Session> {
        if (sessionId === null || sessionId === undefined) {
            throw new Error("SessionService cannot mountSession, id is null or undefined");
        }

        const sessionInCache: Session = this.mountedSessionList.find((session) => session.id === sessionId);
        if (sessionInCache) {
            return Promise.resolve(sessionInCache);
        }

        return this.sessionRepository.findSessionById(sessionId)
            .then((foundSession) => {
                if (foundSession === undefined || foundSession === null) {
                    throw new Error("SessionService cannot mountSession, no such id");
                } else {
                    this.addSessionInCache(foundSession);
                    return foundSession;
                }
            });
    }

    public addSessionInCache(session: Session): void {
        if (this.mountedSessionList.length >= CACHE_SIZE) {
            this.mountedSessionList.shift();
        }
        this.mountedSessionList[this.mountedSessionList.length] = session;
    }

    public startExploration(sessionId: string, testerName: string): Promise<number> {
        let explorationNumber: number;
        let session: Session;
        return this.mountSession(sessionId)
            .then((mountSession) => {
                session = mountSession;
                if (testerName === undefined) {
                    testerName = "anonymous";
                }
                const tester: Tester = new Tester(testerName);
                explorationNumber = session.startExploration(tester);
                return this.sessionRepository.addExploration(sessionId, explorationNumber, tester);
            })
            .then(() => explorationNumber);
    }

    public stopExploration(sessionId: string, explorationNumber: number): Promise<void> {
        return this.mountSession(sessionId)
            .then((session) => {
                session.stopExploration(explorationNumber);
                return this.sessionRepository.updateExplorationIsStopped(sessionId, explorationNumber);
            });
    }

    public addActionToExploration(sessionId: string, explorationNumber: number, actionKind: string, actionValue: string): Promise<number> {
        return this.mountSession(sessionId)
            .then((session) => {
                const action = new Action(actionKind, actionValue);
                session.addActionToExploration(explorationNumber, action);
                const explorationList = session.getInteractionListOfExploration(explorationNumber);
                return this.sessionRepository.updateInteractionListOfExploration(sessionId, explorationNumber, explorationList);
            })
            .then(() => explorationNumber);
    }

    public addCommentToExploration(sessionId: string, explorationNumber: number, commentKind: string, commentValue: string): Promise<void> {
        return this.mountSession(sessionId)
            .then((session) => {
                const comment = new Comment(commentKind, commentValue);
                session.addCommentToExploration(explorationNumber, comment);
                const explorationList = session.getInteractionListOfExploration(explorationNumber);
                return this.sessionRepository.updateInteractionListOfExploration(sessionId, explorationNumber, explorationList);
            });
    }

    public addExploration(sessionId: string,
                          testerName: string,
                          interactionListData: Array<{index: number, concreteType: string, kind: string, value: string}>,
                          ): Promise<number> {
        let explorationNumber;
        let session;
        let tester;
        return this.mountSession(sessionId)
            .then((mountSession) => {
                session = mountSession;
                if (testerName) {
                    tester = new Tester(testerName);
                } else {
                    tester = new Tester("anonymous");
                }

                explorationNumber = session.startExploration(tester);
                if (interactionListData) {
                    const interactionList = interactionListData
                        .sort((interA, interB) => interA.index - interB.index)
                        .map((interaction) => {
                            if (interaction.concreteType === "Action") {
                                return new ActionInteraction(interaction.index, new Action(interaction.kind, interaction.value));
                            }
                            if (interaction.concreteType === "Comment") {
                                return new CommentInteraction(interaction.index, new Comment(interaction.kind, interaction.value));
                            }
                            if (interaction.concreteType === "Answer") {
                                return new AnswerInteraction(interaction.index, new Answer(interaction.kind, interaction.value));
                            }
                        });
                    session.addInteractionListToExploration(explorationNumber, interactionList);
                }
                session.stopExploration(explorationNumber);
            })
            .then( () => {
                if (this.eventStore) {
                    this.eventStore.notifySessionExploration(sessionId, session.explorationList[explorationNumber]);
                }
                return;
            })
            .then( () => this.sessionRepository.addExploration(sessionId, explorationNumber, tester))
            .then( () => {
                const interactionList = session.explorationList[explorationNumber].interactionList;

                return this.sessionRepository.updateInteractionListOfExploration(sessionId, explorationNumber, interactionList);
            })
            .then( () => {
                return explorationNumber;
            });
    }

    public addScreenshot(sessionId: string, explorationNumber: number, interactionIndex: number, image: string): Promise<string> {
        return this.screenshotRepository.findScreenshot(sessionId, explorationNumber, interactionIndex)
            .then((screenshotId) => {
                if (screenshotId === undefined) {
                    const screenshot = new Screenshot(sessionId, explorationNumber, interactionIndex, image);
                    return this.screenshotRepository.addScreenshot(screenshot);
                } else {
                    return Promise.resolve(screenshotId);
                }
            });
    }

    public findScreenshotBySession(sessionId: string): Promise<Array<{explorationNumber: number, interactionIndex: number }>> {
        return this.screenshotRepository.findScreenshotBySession(sessionId);
    }

    public addVideo(sessionId: string, explorationNumber: number, videoBuffer: Buffer): Promise<void> {
        return this.videoRepository.findVideo(sessionId, explorationNumber)
            .then((video: Video) => {
                if (video === undefined) {
                    const videoObj: Video = new Video(sessionId, explorationNumber, videoBuffer);
                    return this.videoRepository.addVideo(videoObj);
                } else {
                    return Promise.resolve();
                }
            });
    }

    public findVideoBySession(sessionId: string): Promise<Array<{explorationNumber: number}>> {
        return this.videoRepository.findVideoBySession(sessionId);
    }

}
