/**
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2018
 * @license Apache-2.0
 */

import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js";

/**
 * Waiter to handle conversations with a ChefWorker in the background.
 */
class BackgroundWorkerWaiter {

    /**
     * BackgroundWorkerWaiter constructor.
     *
     * @param {App} app - The main view object for CyberChef.
     * @param {Manager} manager - The CyberChef event manager.
     */
    constructor(app, manager) {
        this.app = app;
        this.manager = manager;

        this.callbacks = {};
        this.callbackID = 0;
        this.completedCallback = -1;
        this.timeout = null;
    }


    /**
     * Sets up the ChefWorker and associated listeners.
     */
    registerChefWorker() {
        log.debug("Registering new background ChefWorker");
        this.chefWorker = new ChefWorker();
        this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));

        let docURL = document.location.href.split(/[#?]/)[0];
        const index = docURL.lastIndexOf("/");
        if (index > 0) {
            docURL = docURL.substring(0, index);
        }
        this.chefWorker.postMessage({"action": "docURL", "data": docURL});
    }


    /**
     * Handler for messages sent back by the ChefWorker.
     *
     * @param {MessageEvent} e
     */
    handleChefMessage(e) {
        const r = e.data;
        log.debug("Receiving '" + r.action + "' from ChefWorker in the background");

        switch (r.action) {
            case "bakeComplete":
            case "bakeError":
                if (typeof r.data.id !== "undefined") {
                    clearTimeout(this.timeout);
                    this.callbacks[r.data.id].bind(this)(r.data);
                    this.completedCallback = r.data.id;
                }
                break;
            case "workerLoaded":
                log.debug("Background ChefWorker loaded");
                break;
            case "optionUpdate":
            case "statusMessage":
            case "progressMessage":
                // Ignore these messages
                break;
            default:
                log.error("Unrecognised message from background ChefWorker", e);
                break;
        }
    }


    /**
     * Cancels the current bake by terminating the ChefWorker and creating a new one.
     */
    cancelBake() {
        if (this.chefWorker)
            this.chefWorker.terminate();
        this.registerChefWorker();
    }


    /**
     * Asks the ChefWorker to bake the input using the specified recipe.
     *
     * @param {string} input
     * @param {Object[]} recipeConfig
     * @param {Object} options
     * @param {number} progress
     * @param {boolean} step
     * @param {Function} callback
     */
    bake(input, recipeConfig, options, progress, step, callback) {
        const id = this.callbackID++;
        this.callbacks[id] = callback;

        this.chefWorker.postMessage({
            action: "bake",
            data: {
                input: input,
                recipeConfig: recipeConfig,
                options: options,
                progress: progress,
                step: step,
                id: id
            }
        });
    }


    /**
     * Asks the Magic operation what it can do with the input data.
     *
     * @param {string|ArrayBuffer} input
     */
    magic(input) {
        // If we're still working on the previous bake, cancel it before starting a new one.
        if (this.completedCallback + 1 < this.callbackID) {
            clearTimeout(this.timeout);
            this.cancelBake();
        }

        this.bake(input, [
            {
                "op": "Magic",
                "args": [3, false, false]
            }
        ], {}, 0, false, this.magicComplete);

        // Cancel this bake if it takes too long.
        this.timeout = setTimeout(this.cancelBake.bind(this), 3000);
    }


    /**
     * Handler for completed Magic bakes.
     *
     * @param {Object} response
     */
    magicComplete(response) {
        log.debug("--- Background Magic Bake complete ---");
        if (!response || response.error) return;

        this.manager.output.backgroundMagicResult(response.dish.value);
    }

}


export default BackgroundWorkerWaiter;
