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";

export default class Background {

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

    private _windowManager: WindowManager;

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

	private _testerName: string;
    private _exploration: Exploration;
    private _commentsUp : Array<string>;

    private _explorationEvaluation: ExplorationEvaluation;
    private _probabilityMap: Map<string, number>;
    private _commentList: any[];

    private _isRecording : boolean;
    private _hasEvaluator : boolean;
    private _popupCommentPosition : {x:string, y:string};
    private _isTabScriptDisplayingUserView : boolean;

    private _screenshotList;

    private _mediaRecordManager : MediaRecorderManager;

    private _shouldCreateNewWindowsOnConnect: boolean;
    private _shouldCloseWindowOnDisconnect: boolean;

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

        this._mediaRecordManager = new MediaRecorderManager(this._browserService);
        this._windowManager = new WindowManager(browserService);
        this._windowManager.addOnWindoRemovedListener(this.onManagedWindowRemoved.bind(this));
        this.initialize();
        this._shouldCreateNewWindowsOnConnect = true;
        this._shouldCloseWindowOnDisconnect = true;
    }

    private initialize() : void {
        this._screenshotList = [];
        this._exploration = new Exploration();
        this._probabilityMap = new Map();
        this._commentList = [];
        this._testerName = "anonymous";

        this._isRecording = false;
        this._popupCommentPosition = { x:"75%", y:"75%"};
        this._isTabScriptDisplayingUserView = false;
        this._commentsUp = [];
    }

    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.getSession(connectionCode.sessionId)
            .then((session) => {
                this._sessionBaseURL = session.baseURL;
                return session.webSiteId
            })
            .then((webSiteId) => {
                return this._aifexService.getWebSite(webSiteId);
            })
            .then((webSite : WebSite) => {
                this._webSite = webSite;
                this._connectionCode = connectionCode;
                return this._aifexService.hasEvaluator(webSite.id);
            })
            .then ((hasEvaluator) => {
                this._hasEvaluator = hasEvaluator;
                if (this._shouldCreateNewWindowsOnConnect) {
                    this._windowManager.createConnectedWindow(this._sessionBaseURL);
                } else {
                    this._windowManager.connectToExistingWindow();
                }
            })
    }

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

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

    reloadWebsite() : Promise<void> {
		return this._aifexService.getWebSite(this._webSite.id)
			.then((webSite) => {
                this._webSite = webSite;
            })
            .then((_) => {
                const state = this.getStateForTabScript();
                const tabIds = this._windowManager.getConnectedTabIds();
                //console.log('background: reload will send:',tabIds);
                return Promise.all(tabIds.map(id => this._tabScriptService.reload(id, state)));
            })
            .then((_) => {});
    }
    
    drawAttention(): Promise<void> {
        let 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.reset();
            this._isRecording = true;
            return this.addActionToExploration("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.reset();
                    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();
                    }
                })
        } else {
            return Promise.resolve();
        }
    }

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

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

    getExplorationEvaluation(): Promise<ExplorationEvaluation> {
        return Promise.resolve(this._explorationEvaluation);
    }

    private fetchProbabilityMap(): Promise<void> {
        if (this._exploration.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
        })
    }

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

    getProbabilities(): Promise<{
		state: StateForTabScript;
		probabilityMap: string;
	} | void> {
		if (!this._isRecording || this._exploration.length === 0) {
			return Promise.resolve();
        }
        return Promise.resolve({
            state: this.getStateForTabScript(),
            probabilityMap: JSON.stringify(Array.from(this._probabilityMap)),
        });
    }
    
    getComments(): Promise<{
		state: StateForTabScript;
		commentDistributionList: string;
	} | void> {
		if (!this._isRecording || this._exploration.length === 0 ) {
			return Promise.resolve();
        }
        return Promise.resolve({
            state: this.getStateForTabScript(),
            commentDistributionList: JSON.stringify(this._commentList)
        });
    }
    
    addActionToExploration(prefix : string, suffix : string): Promise<void> {
        if (this._isRecording) {
            this._exploration.addAction(prefix, suffix);
            this._commentsUp = [];
        }
        let evaluatePromise;
        if (this._hasEvaluator) {
            evaluatePromise = this.evaluateExploration();
        } else {
            evaluatePromise = Promise.resolve();
        }
        return Promise.all([
            this.fetchProbabilityMap(), 
            this.fetchComments(), 
            evaluatePromise
        ])
        .then(() => {return})
    }

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

            return this.evaluateExploration()
        }
	}

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

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


    stopExploration():Promise<void> {
        //console.log('will stop');
        if (!this._isRecording) {
            return Promise.resolve();
        }
        //console.log('records');

        
        //console.log('will send');
        return this._mediaRecordManager.stopRecording()
                .then(() => {
                    this._exploration.addAction("end");
                    this._exploration.stop();
                    const HAS_MORE_THAN_START_END_ACTIONS = this._exploration.length > 2;
                    if (this._isRecording && HAS_MORE_THAN_START_END_ACTIONS) {
                        return this.sendExploration();
                    }     
                })
                .then(() => {
                    //console.log('sent');
                    this._isRecording = false;
                    this._exploration.reset();
                    this._screenshotList = [];
                    this._commentsUp = [];
                    const state = this.getStateForTabScript();
                    const tabIds = this._windowManager.getConnectedTabIds();
                    //console.log('will build promise');
                    return Promise.all(tabIds.map(id => this._tabScriptService.stopExploration(id, state)))
                })
                .then((_ : Array<void>) => {
                    //console.log('tab knows');
                })
    }

    restartExploration(): Promise<void> {
        //console.log('restart');
        return this.stopExploration()
            .then(() => {
                return this._windowManager.reloadConnectedWindow(this._sessionBaseURL);
            })
			.then(() => {
				return this.startExploration();
			})
			.catch((error) => {
				console.log(error);
			})			
	}


    sendExploration(): Promise<void> {
        const EXPLORATION_CONTAINS_START_ONLY = this._exploration.length === 1;
		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(
                        this._connectionCode,
                        explorationNumber,
                        this._screenshotList
                    );
                } else {
                    screenShotPromise = Promise.resolve();
                }           
                
                let mediaPromise
                if (this._mediaRecordManager.isPreparedToRecordMedia) {
                    let video = this._mediaRecordManager.getRecordedChunks();
                    if (video) {
                        //console.log('blob',video);
                        mediaPromise = this._aifexService.addVideo(
                            this._connectionCode,
                            explorationNumber,
                            video
                        )
                    } else {
                        mediaPromise = Promise.resolve();
                    }
                } else {
                    mediaPromise = Promise.resolve();
                }
                
                return Promise.all([screenShotPromise, mediaPromise]);
            })
            .then( () => {});
    }

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

    setIsTabScriptDisplayingUserView(isTabScriptDisplayingUserView : boolean) {
        this._isTabScriptDisplayingUserView = isTabScriptDisplayingUserView;
        const tabIds = this._windowManager.getConnectedTabIds();
        //console.log('background: reload will send:',tabIds);
        Promise.all(tabIds.map(id => this._tabScriptService.toggleUserView(id, this._isTabScriptDisplayingUserView)));
    }

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

    getStateForPopup() : StateForPopup {
        let 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();
        state.interactionList = this._exploration.interactions.map(interaction => interaction.toString());
        state.isPreparedToRecordMedia = this._mediaRecordManager.isPreparedToRecordMedia;
        state.shouldCreateNewWindowsOnConnect = this._shouldCreateNewWindowsOnConnect;
        state.shouldCloseWindowOnDisconnect = this._shouldCloseWindowOnDisconnect;
        return state;
    }

    getStateForTabScript() : StateForTabScript {
        let 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> {
		return this._browserService.takeScreenshot().then(image => {
			this._screenshotList.push({
				image,
				interactionIndex: this._exploration.length - 1,
			});
		})
    }

    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) {
        this._shouldCreateNewWindowsOnConnect = shouldCreateNewWindowOnConnect;
    }

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

}