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 Interaction from "../domain/Interaction";
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";
import * as winston from "winston";

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 _logger : winston.Logger;
    private mountedSessionList: Session[];

    constructor(sessionRepository: SessionRepository, 
        webSiteRepository: WebSiteRepository, 
        eventStore: EventStore, 
        screenshotRepository: ScreenshotRepository, 
        videoRepository: VideoRepository,
        logger : winston.Logger) {

        this.sessionRepository = sessionRepository;
        this.webSiteRepository = webSiteRepository;
        this.eventStore = eventStore;
        this.screenshotRepository = screenshotRepository;
        this.videoRepository = videoRepository;
        this.mountedSessionList = [];

        this._logger = logger;
        this._logger.info(`SessionService creation`);
    }

    public createNewSessionForWebSiteId(webSiteId: string, baseURL: string): Promise<string | undefined> {
        this._logger.info(`SessionService:createNewSessionForWebSiteId id = ${webSiteId}, bareURL = ${baseURL}`);
        return this.webSiteRepository.findWebSiteById(webSiteId)
            .then((webSite) => {
                if (webSite === undefined) {
                    this._logger.info(`SessionService:createNewSessionForWebSiteId failed to find a website for name : ${webSiteId}`);
                } else {
                    const session: Session = new Session(webSite, baseURL);
                    this.addSessionInCache(session);
                    return this.sessionRepository.addSession(session)
                        .then((newSessionId) => {
                            this._logger.info(`SessionService:createNewSessionForWebSiteId session ${newSessionId} is created`);
                            return newSessionId;
                        });
                }
            });
    }

    public mountSession(sessionId: string): Promise<Session | undefined> {
        this._logger.info(`SessionService:mountSession ${sessionId}`);
        const sessionInCache: Session | undefined = this.mountedSessionList.find((session) => session.id === sessionId);
        if (sessionInCache) {
            this._logger.info(`SessionService:mountSession session  ${sessionId} in cache`);
            return Promise.resolve(sessionInCache);
        }

        return this.sessionRepository.findSessionById(sessionId)
            .then((foundSession) => {
                if (foundSession !== undefined) {
                    this.addSessionInCache(foundSession);
                }
                this._logger.info(`SessionService:session ${sessionId} mounted and stored into cache`);
                return foundSession;
            });
    }

    public addSessionInCache(session: Session): void {
        this._logger.info(`SessionService:addSessionInCache ${session.id}`);
        if (this.mountedSessionList.length >= CACHE_SIZE) {
            this.mountedSessionList.shift();
        }
        this.mountedSessionList[this.mountedSessionList.length] = session;
    }

    public startExploration(sessionId: string, testerName: string, startDate?: Date): Promise<number> {
        this._logger.info(`SessionService:startExploration ${sessionId}`);
        let explorationNumber: number;
        let session: Session;
        return this.mountSession(sessionId)
            .then((mountSession) => {
                if (mountSession) {
                    session = mountSession;
                    if (testerName === undefined) {
                        testerName = "anonymous";
                    }
                    if (startDate === undefined) {
                        startDate = new Date();
                    }
                    const tester: Tester = new Tester(testerName);
                    explorationNumber = session.startExploration(tester, startDate);
                    return this.sessionRepository.addExploration(sessionId, explorationNumber, tester, startDate)
                        .then(() => {
                            this._logger.info(`SessionService:startExploration exploration ${explorationNumber} is started for session ${sessionId}`);
                            return explorationNumber
                        });
                } else {
                    this._logger.error(`SessionService:startExploration cannot start exploration, wrong sessionId`);
                    throw new Error('SessionService:startExploration cannot start exploration, wrong sessionId');
                }
            })
    }

    public stopExploration(sessionId: string, explorationNumber: number, stopDate?:Date): Promise<void> {
        this._logger.info(`SessionService:stopExploration stop exploration ${explorationNumber} in session ${sessionId}`);
        return this.mountSession(sessionId)
            .then((session) => {
                if (session) {
                    session.stopExploration(explorationNumber);
                    if (stopDate === undefined) {
                        stopDate = new Date();
                    }
                    this._logger.info(`SessionService:stopExploration exploration ${explorationNumber} stopped in session ${sessionId}`);
                    return this.sessionRepository.updateExplorationIsStopped(sessionId, explorationNumber, stopDate);
                } else {
                    this._logger.error(`SessionService:stopExploration cannot stop exploration, wrong sessionId`);
                    throw new Error('SessionService:stopExploration cannot stop exploration, wrong sessionId');
                }
                
            });
    }

    public addExploration(sessionId: string,
                          testerName: string,
                          interactionListData: Array<{index: number, concreteType: string, kind: string, value: string, date?: Date}>,
                          startDate?:Date,
                          stopDate?:Date
                          ): Promise<number> {
        this._logger.info(`SessionService:addExploration in session ${sessionId}`);
        let explorationNumber : number;
        let session : Session;
        let tester : Tester;

        if (testerName) {
            tester = new Tester(testerName);
        } else {
            tester = new Tester("anonymous");
        }
        let explorationStartDate : Date;
        if (startDate === undefined) {
            explorationStartDate = new Date();
        } else {
            explorationStartDate = startDate;
        }
        let explorationStopDate : Date;
        if (stopDate === undefined) {
            explorationStopDate = new Date();
        } else {
            explorationStopDate = stopDate;
        }
        return this.mountSession(sessionId)
            .then((mountSession) => {
                if (mountSession) {
                    session = mountSession;

                    explorationNumber = session.startExploration(tester, explorationStartDate);
                    if (interactionListData) {
                        const interactionList : Interaction[] = [];
                        interactionListData
                            .sort((interA, interB) => interA.index - interB.index)
                            .forEach((interaction) => {
                                if (interaction.concreteType === "Action") {
                                    interactionList.push(new ActionInteraction(interaction.index, new Action(interaction.kind, interaction.value), interaction.date));
                                }
                                if (interaction.concreteType === "Comment") {
                                    interactionList.push(new CommentInteraction(interaction.index, new Comment(interaction.kind, interaction.value), interaction.date));
                                }
                                if (interaction.concreteType === "Answer") {
                                    interactionList.push(new AnswerInteraction(interaction.index, new Answer(interaction.kind, interaction.value), interaction.date));
                                }
                            });
                        session.addInteractionListToExploration(explorationNumber, interactionList);
                    }

                    session.stopExploration(explorationNumber, stopDate);

                    if (this.eventStore) {
                        this._logger.info(`SessionService:addExploration notifySessionExploration ${sessionId}`);
                        this.eventStore.notifySessionExploration(sessionId, session.explorationList[explorationNumber]);
                    }                    

                    return this.sessionRepository.addExploration(sessionId, explorationNumber, tester, explorationStartDate)
                        .then( () => {
                            const interactionList = session.explorationList[explorationNumber].interactionList;
                            return this.sessionRepository.updateInteractionListOfExploration(sessionId, explorationNumber, interactionList);
                        })
                        .then( () => {
                            return this.sessionRepository.updateExplorationIsStopped(sessionId, explorationNumber, explorationStopDate);
                        })
                        .then( () => {
                            this._logger.info(`SessionService:addExploration exploration ${explorationNumber} added`);
                            return explorationNumber;
                        });
                } else {
                    this._logger.error('SessionService:addExploration cannot add with wrong session id');
                    throw new Error('SessionService:addExploration cannot add with wrong session id');
                }
                
            })
            
            
    }

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

    public findScreenshotBySession(sessionId: string): Promise<Array<{explorationNumber: number, interactionIndex: number }> | undefined> {
        this._logger.info(`SessionService:findScreenshotBySession`);
        return this.screenshotRepository.findScreenshotBySession(sessionId);
    }

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

    public findVideoBySession(sessionId: string): Promise<Array<{explorationNumber: number}> | undefined> {
        this._logger.info(`SessionService:findVideoBySession`);
        return this.videoRepository.findVideoBySession(sessionId);
    }

}
