import AifexService from "./AifexService";
import BrowserService from "./BrowserService";
import CompatibilityCheck from "./CompatibilityCheck";
import ConnectionCode from "./ConnectionCode";
import Exploration from "./Exploration";
import StateForPopup from "./StateForPopup";
import WebSite from "./Website";
import WindowManager from "./WindowManager";
import StateForTabScript from "./StateForTabScript";
import TabScriptService from "./TabScriptService";
import MediaRecorderManager from "./MediaRecorderManager";
import ExplorationEvaluation from "./ExplorationEvaluation";
import Question from "./Question";
import AifexPopup from "./AifexPopup";
import PopupService from "./PopupService";
import Evaluator from "./Evaluator";
import Screenshot from "./Screenshot";
import CommentDistribution from "./CommentDistribution";

export default class Background {

    private _aifexService : AifexService;
    private _browserService : BrowserService;
    private _tabScriptService : TabScriptService;
    private _popupService: PopupService;

    private _windowManager: WindowManager;

    private _connectionCode : ConnectionCode | undefined;
    private _webSite: WebSite | undefined;
    private _sessionBaseURL: string | undefined;

	private _testerName: string | "anonymous";
    private _exploration: Exploration | undefined;

    private _evaluator: Evaluator | undefined;
    private _explorationEvaluation: ExplorationEvaluation | undefined;
    private _probabilityMap: Map<string, number>;
    private _commentDistributions: CommentDistribution[] | undefined;
    private _commentsUp : string[];

    private _isRecording : boolean;
    private _hasEvaluator : boolean;
    private _popupCommentPosition : {x:string, y:string};
    private _isTabScriptDisplayingUserView : boolean;
    private _screenshotList : Screenshot[];

    private _mediaRecordManager : MediaRecorderManager;

    private _shouldCreateNewWindowsOnConnect: boolean;
    private _shouldCloseWindowOnDisconnect: boolean;

    private _aifexPopup: AifexPopup;
    private _usesProbabilities: boolean;

    constructor(aifexService : AifexService, popupService: PopupService, browserService : BrowserService, tabScriptService : TabScriptService) {
        this._aifexService = aifexService;
        this._browserService = browserService;
        this._tabScriptService = tabScriptService;
        this._popupService = popupService;

        this._mediaRecordManager = new MediaRecorderManager(this._browserService);
        this._windowManager = new WindowManager(browserService);
        this._windowManager.attachHandlers();
        this._windowManager.addOnWindowRemovedListener(this.onManagedWindowRemoved.bind(this));
        this._shouldCreateNewWindowsOnConnect = true;
        this._shouldCloseWindowOnDisconnect = true;
        this._aifexPopup = new AifexPopup(this._browserService);
        this._aifexPopup.attachBrowserHandlers();
        this._isTabScriptDisplayingUserView = false;

        this._usesProbabilities = true;
        this._isRecording = false;
        this._hasEvaluator = false;
        this._testerName = "anonymous";
        this._probabilityMap = new Map();
        this._commentDistributions = [];
        this._commentsUp = [];
        this._popupCommentPosition = { x:"75%", y:"75%"};
        this._screenshotList = [];
        this._exploration = new Exploration();
        this._explorationEvaluation = undefined;
        this._isRecording = false;
    }

    private initialize() : void {
        this._hasEvaluator = false;
        this._testerName = "anonymous";
        this._probabilityMap = new Map();
        this._commentDistributions = [];
        this._commentsUp = [];
        this._popupCommentPosition = { x:"75%", y:"75%"};
        this._screenshotList = [];
        this._exploration = new Exploration();
        this._explorationEvaluation = undefined;
        this._isRecording = false;
    }

    makeCompatibilityCheck() : Promise<CompatibilityCheck> {
        return this._aifexService.getPluginInfo()
            .then((pluginInfo) => {
                return new CompatibilityCheck(this._browserService.getExtensionVesion(), pluginInfo.version, pluginInfo.url);
            })
    }

    connect(connectionCode: ConnectionCode) : Promise<void>{
        this.initialize();
        return this._aifexService.hasModel(connectionCode.modelId)
        .then((hasModel : boolean) => {
            if (! hasModel) {
                return Promise.reject('modeId not found');
            }
        })
        .then( () => {
            return this._aifexService.getSession(connectionCode.sessionId)
        })
        .then((session) => {
            if (session) {
                this._sessionBaseURL = session.baseURL;
                return session.webSiteId;
            } else {
                return Promise.reject('sessionId is undefined');
            }
        })
        .then((webSiteId) => {
            return this._aifexService.getWebSite(webSiteId);
        })
        .then((webSite : WebSite | undefined) => {
            if (webSite) {
                this._webSite = webSite;
                this._connectionCode = connectionCode;
                return this._aifexService.getEvaluator(webSite.id);
            } else {
                return Promise.reject(`webSite is undefined`);
            }
        })
        .then ((evaluator: Evaluator | undefined) => {
            if (evaluator === undefined) {
                this._hasEvaluator = false;
            } else {
                this._hasEvaluator = true;
                this._evaluator = evaluator;
            }
            if (this._shouldCreateNewWindowsOnConnect) {
                this._windowManager.createConnectedWindow(this._sessionBaseURL);
            } else {
                this._windowManager.connectToExistingWindow();
            }
        })
        .then(() => this.refreshPopup())
    }

    disconnect(): Promise<void> {
        this._connectionCode = undefined;
        this._sessionBaseURL = undefined;
        this._webSite = undefined;
        if (this._shouldCloseWindowOnDisconnect) {
            return this._windowManager.removeConnectedWindow();
        } else {
            return Promise.resolve();
        }
    }

    onManagedWindowRemoved(): void {
        this._connectionCode = undefined;
        this._sessionBaseURL = undefined;
        this._webSite = undefined;
    }

    isConnected() : boolean {
        return this._connectionCode !== undefined;
    }

    reloadWebsite() : Promise<void> {
        if (!this._webSite) {
            return Promise.resolve();
        }
		return this._aifexService.getWebSite(this._webSite.id)
        .then((webSite) => {
            this._webSite = webSite;
        })
        .then((_) => {
            const state = this.getStateForTabScript();
            const tabIds = this._windowManager.getConnectedTabIds();
            return Promise.all(tabIds.map(id => this._tabScriptService.reload(id, state)));
        })
        .then(() => this.refreshPopup())
    }

    drawAttention(): Promise<void> {
        const id = this._windowManager.getConnectedWindowId();
        if (this._connectionCode && id !== undefined && id !== null) {
            return this._browserService.drawAttentionToWindow(id);
        }
        else {
			return Promise.resolve();
		}
	}

    startExploration() : Promise<void> {
        if (!this._isRecording) {
            this._exploration = new Exploration();
            this._isRecording = true;
            return this.processNewAction("start", undefined)
            .then(() => {
                const state = this.getStateForTabScript();
                const tabIds = this._windowManager.getConnectedTabIds();
                return Promise.all(tabIds.map(id => this._tabScriptService.startExploration(id, state)));
            })
            .catch((e) => {
                this._exploration = undefined;
                this._isRecording = false;
                throw new Error(e);
            })
            .then(() => {
                return this._mediaRecordManager.startRecording()
                .catch((e) => {
                    this._mediaRecordManager.destroyRecording();
                    throw new Error(e);
                })
            })
            .then(() => {
                if (this._hasEvaluator) {
                    this.evaluateExploration();
                }
            })
            .then(() => this.refreshPopup())
        } else {
            return Promise.resolve();
        }
    }

    removeExploration() : Promise<void> {
        if (this._isRecording) {
            this._mediaRecordManager.stopRecording();
            this._exploration = undefined;
            this._isRecording = false;
            const state = this.getStateForTabScript();
            const tabIds = this._windowManager.getConnectedTabIds();
            return Promise.all(tabIds.map(id => this._tabScriptService.stopExploration(id, state)))
            .then((_ : void[]) => {return})
        } else {
            return Promise.resolve();
        }
    }

    evaluateExploration(): Promise<void> {
        if (this._webSite && this._exploration) {
            return this._aifexService.evaluateSequence(this._webSite, this._exploration)
            .then((evaluation) => {
                this._explorationEvaluation = evaluation;
            })
        } else {
            return Promise.resolve();
        }
    }

    getExplorationEvaluation(): ExplorationEvaluation | undefined {
        return this._explorationEvaluation;
    }

    private fetchProbabilityMap(): Promise<void> {
        if (this._exploration) {
            if (this._exploration.actions.length === 0) {
                return Promise.resolve();
            }
    
            if (!this._connectionCode) {
                return Promise.reject("Not connected to a model");
            }
            return this._aifexService.getProbabilityMap(this._connectionCode, this._exploration).then((probabilityMap) => {
                this._probabilityMap = probabilityMap
            })
        } else {
            return Promise.resolve();
        }
        
    }

    private fetchComments(): Promise<void> {
        if (!this._connectionCode ) {
			return Promise.reject("Not connected to a model");
        }
        if (this._exploration) {
            if (this._exploration.actions.length === 0) {
                return Promise.resolve();
            } else {
                return this._aifexService.getCommentDistributions(this._connectionCode, this._exploration)
                .then((commentDistributionList) => {
                    this._commentDistributions = commentDistributionList;
                })
            }
        } else {
            return Promise.resolve();
        }
        
    }

    getProbabilityMap():Map<string, number> {
		if (this._isRecording && this._exploration !== undefined && this._exploration.actions.length !== 0) {
			return this._probabilityMap;
        } else {
            return new Map();
        }
    }

    getCommentDistributions(): CommentDistribution[] {
		if (this._isRecording && this._exploration !== undefined && this._exploration.actions.length !== 0 && this._commentDistributions) {
			return this._commentDistributions;
        } else {
            return [];
        }
    }

    processNewAction(prefix : string, suffix? : string): Promise<void> {
        if (this._isRecording && this._exploration) {
            this._exploration.addAction(prefix, suffix);
            this._commentsUp = [];

            let evaluatePromise;
            if (this._hasEvaluator) {
                evaluatePromise = this.evaluateExploration();
            } else {
                evaluatePromise = Promise.resolve();
            }

            const promises = [
                this.fetchComments(),
                evaluatePromise
            ]
            if (this._usesProbabilities) {
                promises.push(this.fetchProbabilityMap())
            }
            return Promise.all(promises)
            .catch((error) => console.error("Failed to process new action : ", error))
            .then(() => this.refreshPopup())
        } else {
            return Promise.resolve();
        }
    }

    addAnswerToExploration(question: Question, value: boolean) : Promise<void> {
		if (this._isRecording && this._exploration) {
            this._exploration.addAnswer(question.text, value.toString());

            return this.evaluateExploration()
            .then(() => this.refreshPopup())
        } else {
            return Promise.resolve();
        }
	}

    addCommentToExploration(kind : string , message : string): void {
        if (this._isRecording && this._exploration) {
            this._exploration.addComment(kind, message);
            this.refreshPopup()
        }
    }

    refreshPopup(): Promise<void> {
        if (this._aifexPopup.isDetached) {
            return this._popupService.refresh(this.getStateForPopup())
            .catch((error) => console.error("Failed refresh popup : ", error.message))
        } else {
            return Promise.resolve();
        }
    }

    upComment(kind : string, message : string): void {
        if (this._isRecording) {
            if (!this._commentsUp.includes(message)) {
                this.addCommentToExploration(kind, message);
                this._commentsUp.push(`${kind}$${message}`);
            }
        }
    }


    stopExploration():Promise<void> {
        if (this._isRecording && this._exploration) {
            let exploration : Exploration = this._exploration;
            return this._mediaRecordManager.stopRecording()
            .then(() => {
                exploration.addAction("end");
                exploration.stop();
                const MIN_NUMBER_OF_ACTIONS = 2;
                const HAS_MORE_THAN_START_END_ACTIONS = exploration.actions.length > MIN_NUMBER_OF_ACTIONS;
                if (this._isRecording && HAS_MORE_THAN_START_END_ACTIONS) {
                    return this.sendExploration();
                }
            })
            .then(() => {
                this._isRecording = false;
                this._exploration = new Exploration();
                this._screenshotList = [];
                this._commentsUp = [];
                const state = this.getStateForTabScript();
                const tabIds = this._windowManager.getConnectedTabIds();
                return Promise.all(tabIds.map(id => this._tabScriptService.stopExploration(id, state)))
            })
            .then((_ : void[]) => {
            })
        } else {
            return Promise.resolve();
        }
    }

    restartExploration(): Promise<void> {
        return this.stopExploration()
        .then(() => {
            return this._windowManager.reloadConnectedWindow(this._sessionBaseURL);
        })
        .then(() => {
            return this.startExploration();
        })
	}


    sendExploration(): Promise<void> {
        if (this._exploration && this._connectionCode) {
            const EXPLORATION_CONTAINS_START_ONLY = this._exploration.actions.length === 1;
            let connectionCode : ConnectionCode = this._connectionCode;
            if (! this._isRecording ||  EXPLORATION_CONTAINS_START_ONLY) {
                return Promise.resolve();
            }

            return this._aifexService.addExploration(
                this._connectionCode,
                this._testerName,
                this._exploration
            )
            .then((explorationNumber) => {
                let screenShotPromise;
                if (this._screenshotList.length > 0) {
                    screenShotPromise = this._aifexService.addScreenshotList(
                        connectionCode,
                        explorationNumber,
                        this._screenshotList
                    );
                } else {
                    screenShotPromise = Promise.resolve();
                }

                let mediaPromise
                if (this._mediaRecordManager.isPreparedToRecordMedia) {
                    const video = this._mediaRecordManager.getRecordedChunks();
                    if (video) {
                        mediaPromise = this._aifexService.addVideo(
                            connectionCode,
                            explorationNumber,
                            video
                        )
                    } else {
                        mediaPromise = Promise.resolve();
                    }
                } else {
                    mediaPromise = Promise.resolve();
                }
                return Promise.all([screenShotPromise, mediaPromise]);
            })
            .then( () => {return;});
        } else {
            return Promise.resolve();
        }
        
    }

    changeTesterName(name:string): void {
        this._testerName = name;
    }

    setIsTabScriptDisplayingUserView(isTabScriptDisplayingUserView : boolean): void {
        this._isTabScriptDisplayingUserView = isTabScriptDisplayingUserView;
        const tabIds = this._windowManager.getConnectedTabIds();
        Promise.all(tabIds.map(id => this._tabScriptService.toggleUserView(id, this._isTabScriptDisplayingUserView)));
    }

    setPopupCommentPosition(position : {x: string, y: string}): void {
        this._popupCommentPosition = position;
    }

    getStateForPopup() : StateForPopup {
        const state = new StateForPopup();
        state.connection = this._connectionCode;
        state.isRecording = this._isRecording;
        state.testerName = this._testerName;
        state.isTabScriptDisplayingUserView = this._isTabScriptDisplayingUserView;
        state.hasBaseURL = this._sessionBaseURL !== undefined;
        state.managedWindowId = this._windowManager.getConnectedWindowId();
        if (this._exploration) {
            state.interactionList = this._exploration.actions.map(interaction => interaction.toPrintableText());
        }
        state.isPreparedToRecordMedia = this._mediaRecordManager.isPreparedToRecordMedia;
        state.shouldCreateNewWindowsOnConnect = this._shouldCreateNewWindowsOnConnect;
        state.shouldCloseWindowOnDisconnect = this._shouldCloseWindowOnDisconnect;
        state.popupIsDetached = this._aifexPopup.isDetached;
        if (this._evaluator) {
            state.evaluatorScenario = this._evaluator.scenario;
        }
        if (this._explorationEvaluation && this._webSite) {
            state.readEvaluation(this._explorationEvaluation, this._webSite);
        }
        return state;
    }


    getStateForTabScript() : StateForTabScript {
        const state = new StateForTabScript();
        state.isRecording = this._isRecording;
        state.displayUserView = this._isTabScriptDisplayingUserView;
        state.webSite = this._webSite;
        state.popupCommentPosition = this._popupCommentPosition;
        state.commentsUp = this._commentsUp;
        return state;
    }

    takeScreenShot(): Promise<void> {
        let windowId = this._windowManager.getConnectedWindowId();
        if (windowId && this._exploration) {
            let exploration = this._exploration;
            return this._browserService.takeScreenshot(windowId)
            .then(image => {
                this._screenshotList.push(new Screenshot(image,exploration.length - 1));
            })
        } else {
            return Promise.resolve();
        } 
    }

    setRecordMedia(recordMedia : boolean) : Promise<void>{
        if (recordMedia) {
            if (this._isRecording) {
                return this._mediaRecordManager.prepareRecording()
                .then(()=> {
                    this._mediaRecordManager.startRecording();
                })
            } else {
                return this._mediaRecordManager.prepareRecording();
            }
        } else {
            return this._mediaRecordManager.destroyRecording();
        }
    }

    setShouldCreateNewWindowOnConnect(shouldCreateNewWindowOnConnect: boolean): void {
        this._shouldCreateNewWindowsOnConnect = shouldCreateNewWindowOnConnect;
    }

    setShouldCloseWindowOnDisconnect(shouldCloseWindowOnDisconnect: boolean): void {
        this._shouldCloseWindowOnDisconnect = shouldCloseWindowOnDisconnect;
    }

    toggleDetachPopup(): Promise<void> {
        return this._aifexPopup.toggleDetached()
            .catch((error) => {
                console.error("Failed to toggle Popup detach", error.message);
            })
	}

}