import { BarcodeWASMResult } from "../barcode";
import { ImageSettings } from "../imageSettings";
import { Parser } from "../parser";

// WARNING
// ==========
// The "engine" function is extracted and executed in isolation as a WebWorker in the browser.
// We currently cannot use too advanced language features here as the code will not get transformed/polyfilled correctly
// by Rollup and Babel as it might refer to other externally defined variables/functions.
// This means we also cannot import and use variables from the rest of the project.
// The used language features should be compatible with (supported by) the browsers mentioned in the documentation.
// See rollup.config.js and .browserslistrc.worker for more details.
// TODO: This should be fixed...

// tslint:disable:no-any

/**
 * @hidden
 */
declare const self: any;
/**
 * @hidden
 */
declare const importScripts: (...urls: string[]) => Promise<void> | undefined; // Promise is used only during testing
/**
 * @hidden
 */
declare const postMessage: (message: any, transfer?: any[]) => void;
/**
 * @hidden
 *
 * Defined here as we cannot use too recent typescript type definitions...
 */
declare namespace WebAssembly {
  interface Instance {
    readonly exports: any;
  }

  interface WebAssemblyInstantiatedSource {
    instance: Instance;
    // tslint:disable-next-line:no-reserved-keywords
    module: {};
  }
}

/**
 * @hidden
 */
type FileSystemType = {};

/**
 * @hidden
 */
export declare type Module = {
  HEAPU8: Uint8Array;
  lengthBytesUTF8(str: string): number;
  UTF8ToString(ptr: number): string;
  stringToUTF8(str: string, outPtr: number, maxBytesToWrite: number): void;
  _malloc(size: number): number;
  _free(ptr: number): void;
  _create_context(ptr: number, debug: boolean): void;
  _scanner_settings_new_from_json(
    ptr: number,
    blurryDecodingEnabled: boolean,
    matrixScanEnabled: boolean,
    highQualitySingleFrameMode: boolean,
    gpuEnabled: boolean
  ): number;
  _scanner_image_settings_new(width: number, height: number, channels: number): void;
  _scanner_session_clear(): void;
  _can_hide_logo(): number;
  _scanner_scan(ptr: number): number;
  _parser_parse_string(parserType: number, ptr: number, stringDataLength: number, ptr2: number): number;
  canvas(): HTMLCanvasElement;
  instantiateWasm(importObject: object, successCallback: (instance: WebAssembly.Instance) => void): void;
  preRun(): void;
  onRuntimeInitialized(): void;
  callMain(): void;
};

/**
 * @hidden
 */
declare let Module: Module;

/**
 * @hidden
 */
declare namespace FS {
  function syncfs(populate: boolean, callback: (e: any) => any): void;
  function mount(fsType: FileSystemType, opts: any, mountpoint: string): any;
  function mkdir(path: string, mode?: number): any;
}

/**
 * @hidden
 */
declare const IDBFS: FileSystemType;

// tslint:enable:no-any

/**
 * @hidden
 */
declare type ScanWorkUnit = {
  requestId: number;
  data: Uint8ClampedArray;
  highQualitySingleFrameMode: boolean;
};

/**
 * @hidden
 */
declare type ParseWorkUnit = {
  requestId: number;
  dataFormat: Parser.DataFormat;
  dataString: string;
  options: string;
};

/**
 * @hidden
 */
export declare type Engine = {
  loadLibrary(
    deviceId: string,
    libraryLocation: string,
    locationPath: string,
    deviceModelName: string | undefined,
    uaBrowserName: string | undefined
  ): Promise<void>;
  createContext(newLicenseKey: string): void;
  setSettings(newSettings: string): void;
  setImageSettings(newImageSettings: ImageSettings): void;
  workOnScanQueue(): void;
  workOnParseQueue(): void;
  addScanWorkUnit(scanWorkUnit: ScanWorkUnit): void;
  addParseWorkUnit(parseWorkUnit: ParseWorkUnit): void;
  clearSession(): void;
};

/**
 * @hidden
 * @returns Engine
 */
// tslint:disable-next-line:max-func-body-length
export function engine(): Engine {
  const scanQueue: ScanWorkUnit[] = [];
  const parseQueue: ParseWorkUnit[] = [];
  const gpuAccelerationAvailable: boolean = typeof self.OffscreenCanvas === "function";

  let browserName: string | undefined;
  let imageBufferPointer: number | undefined;
  let licenseKey: string;
  let settings: string;
  let imageSettings: ImageSettings;
  let scanWorkSubmitted: boolean = false;
  let fileSystemSynced: boolean = false;
  let runtimeLoaded: boolean = false;
  let wasmReady: boolean = false;
  let scannerSettingsReady: boolean = false;
  let scannerImageSettingsReady: boolean = false;
  let contextAvailable: boolean = false;
  let fsSyncInProgress: boolean | undefined;
  let fsSyncScheduled: boolean = false;

  // Public

  // Promise is used only during testing
  function loadLibrary(
    deviceId: string,
    libraryLocation: string,
    locationPath: string,
    deviceModelName: string | undefined,
    uaBrowserName: string | undefined
  ): Promise<void> {
    function start(): void {
      if (!wasmReady && fileSystemSynced && runtimeLoaded) {
        wasmReady = true;
        Module.callMain();
        postMessage(["status", "ready"]);
        workOnScanQueue();
        workOnParseQueue();
      }
    }

    const { jsURI, wasmURI } = getLibraryLocationURIs(libraryLocation);
    Module = <Module>(<unknown>{
      arguments: [deviceId],
      canvas: gpuAccelerationAvailable ? new self.OffscreenCanvas(32, 32) : /* istanbul ignore next */ undefined,
      instantiateWasm: (importObject: object, successCallback: (instance: WebAssembly.Instance) => void) => {
        // wasmJSVersion is globally defined inside scandit-engine-sdk.min.js
        let wasmJSVersion: string = self.wasmJSVersion;
        // istanbul ignore if
        if (wasmJSVersion == null) {
          wasmJSVersion = "undefined";
        }
        // istanbul ignore if
        if (wasmJSVersion !== "%VER%") {
          console.error(
            `The Scandit SDK Engine library JS file found at ${jsURI} seems invalid: ` +
              `expected version doesn't match (received: ${wasmJSVersion}, expected: ${"%VER%"}). ` +
              `Please ensure the correct Scandit SDK Engine file (with correct version) is retrieved.`
          );
        }

        if (typeof self.WebAssembly.instantiateStreaming === "function") {
          instantiateWebAssemblyStreaming(importObject, wasmURI, successCallback);
        } else {
          instantiateWebAssembly(importObject, wasmURI, successCallback);
        }

        return {};
      },
      noInitialRun: true,
      preRun: () => {
        try {
          FS.mkdir("/scandit_sync_folder");
        } catch (error) {
          // istanbul ignore next
          if (error.code !== "EEXIST") {
            throw error;
          }
        }
        FS.mount(IDBFS, {}, "/scandit_sync_folder");
        FS.syncfs(true, () => {
          fileSystemSynced = true;
          start();
        });
      },
      onRuntimeInitialized: () => {
        runtimeLoaded = true;
        start();
      }
    });
    browserName = uaBrowserName;
    self.window = self.document = self; // Fix some Emscripten quirks
    self.path = locationPath; // Used by the Scandit SDK Engine library
    self.deviceModelName = deviceModelName; // Used by the Scandit SDK Engine library

    function tryImportScripts(): Promise<void> {
      try {
        const importScriptsResults: Promise<void> | undefined = importScripts(jsURI);
        // istanbul ignore else
        if (importScriptsResults != null) {
          return importScriptsResults;
        } else {
          return Promise.resolve();
        }
      } catch (error) {
        return Promise.reject(error);
      }
    }

    return retryWithExponentialBackoff(tryImportScripts, 250, 4000, error => {
      console.warn(error);
      console.warn(`Couldn't retrieve Scandit SDK Engine library at ${jsURI}, retrying...`);
    }).catch(error => {
      console.error(error);
      console.error(
        `Couldn't retrieve Scandit SDK Engine library at ${jsURI}, did you configure the path for it correctly?`
      );

      return Promise.resolve(error); // Promise is used only during testing
    });
  }

  function createContext(newLicenseKey: string): void {
    licenseKey = newLicenseKey;
    if (contextAvailable || licenseKey == null || !wasmReady) {
      return;
    }

    const licenseKeyLength: number = Module.lengthBytesUTF8(licenseKey) + 1;
    const licenseKeyPointer: number = Module._malloc(licenseKeyLength);
    Module.stringToUTF8(licenseKey, licenseKeyPointer, licenseKeyLength);
    Module._create_context(licenseKeyPointer, false);
    Module._free(licenseKeyPointer);

    contextAvailable = true;

    postMessage([
      "license-features",
      {
        hiddenScanditLogoAllowed: Module._can_hide_logo() === 1
      }
    ]);
  }

  function setSettings(newSettings: string): void {
    settings = newSettings;
    applySettings();
  }

  function setImageSettings(newImageSettings: ImageSettings): void {
    imageSettings = newImageSettings;
    applyImageSettings();
  }

  function augmentErrorInformation(error: { errorCode: number; errorMessage: string }): void {
    if (error.errorCode === 260) {
      let hostname: string;
      // istanbul ignore if
      if (location.href != null && location.href.indexOf("blob:null/") === 0) {
        hostname = "localhost";
      } else {
        hostname = new URL(
          location.pathname != null && location.pathname !== "" && !location.pathname.startsWith("/")
            ? /* istanbul ignore next */ location.pathname
            : location.origin
        ).hostname;
      }
      // istanbul ignore next
      if (hostname[0].startsWith("[") && hostname.endsWith("]")) {
        hostname = hostname.slice(1, -1);
      }
      error.errorMessage = error.errorMessage.replace("domain name", `domain name (${hostname})`);
    }
  }

  function processScanWorkUnit(currentScanWorkUnit: ScanWorkUnit): void {
    if (currentScanWorkUnit.highQualitySingleFrameMode) {
      applySettings(true);
    }
    const resultData: string = scanImage(currentScanWorkUnit.data);
    if (currentScanWorkUnit.highQualitySingleFrameMode) {
      applySettings(false);
    }
    const result: {
      scanResult?: BarcodeWASMResult[];
      error?: { errorCode: number; errorMessage: string };
    } = JSON.parse(resultData);
    // Important! We transfer data back even if we don't use it on the receiving end on Firefox.
    // Not doing so can result in memory and stability issues.
    // https://developer.mozilla.org/en-US/docs/Web/API/Transferable
    // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
    const postMessageTransfer: Transferable[] | undefined =
      browserName === "Firefox" ? [currentScanWorkUnit.data.buffer] : undefined;
    if (result.error != null) {
      augmentErrorInformation(result.error);
      postMessage(
        [
          "work-error",
          {
            requestId: currentScanWorkUnit.requestId,
            error: result.error
          }
        ],
        postMessageTransfer
      );
    } else {
      // istanbul ignore else
      if (result.scanResult != null) {
        if (result.scanResult.length > 0 || fsSyncInProgress == null) {
          syncFS();
        }
        postMessage(
          [
            "work-result",
            {
              requestId: currentScanWorkUnit.requestId,
              result
            }
          ],
          postMessageTransfer
        );
      } else {
        console.error("Unrecognized Scandit Engine result:", result);
        postMessage([""], postMessageTransfer);
      }
    }
  }

  function workOnScanQueue(): void {
    if ((!scannerSettingsReady || !scannerImageSettingsReady) && scanQueue.length !== 0) {
      // First submitted work unit
      createContext(licenseKey);
      applySettings();
      applyImageSettings();
    }

    if (!scannerSettingsReady || !scannerImageSettingsReady || scanQueue.length === 0) {
      return;
    }

    while (scanQueue.length !== 0) {
      processScanWorkUnit(<ScanWorkUnit>scanQueue.shift());
    }
  }

  function processParseWorkUnit(parseWorkUnit: ParseWorkUnit): void {
    const resultData: string = parseString(parseWorkUnit.dataFormat, parseWorkUnit.dataString, parseWorkUnit.options);
    const result: { result?: string; error?: { errorCode: number; errorMessage: string } } = JSON.parse(resultData);
    if (result.error != null) {
      augmentErrorInformation(result.error);
      postMessage([
        "parse-string-error",
        {
          requestId: parseWorkUnit.requestId,
          error: result.error
        }
      ]);
    } else {
      // istanbul ignore else
      if (result.result != null) {
        postMessage([
          "parse-string-result",
          {
            requestId: parseWorkUnit.requestId,
            result: result.result
          }
        ]);
      } else {
        console.error("Unrecognized Scandit Parser result:", result);
        postMessage([
          "parse-string-error",
          {
            requestId: parseWorkUnit.requestId,
            error: {
              errorCode: -1,
              errorMessage: "Unknown Scandit Parser error"
            }
          }
        ]);
      }
    }
  }

  function workOnParseQueue(): void {
    if (!contextAvailable && parseQueue.length !== 0) {
      // First submitted work unit
      createContext(licenseKey);
    }

    if (!contextAvailable || !wasmReady || parseQueue.length === 0) {
      return;
    }

    while (parseQueue.length !== 0) {
      processParseWorkUnit(<ParseWorkUnit>parseQueue.shift());
    }

    syncFS();
  }

  function addScanWorkUnit(scanWorkUnit: ScanWorkUnit): void {
    scanWorkSubmitted = true;
    scanQueue.push(scanWorkUnit);
    workOnScanQueue();
  }

  function addParseWorkUnit(parseWorkUnit: ParseWorkUnit): void {
    parseQueue.push(parseWorkUnit);
    workOnParseQueue();
  }

  function clearSession(): void {
    if (scannerSettingsReady) {
      Module._scanner_session_clear();
    }
  }

  // Private

  function retryWithExponentialBackoff<T>(
    handler: () => Promise<T>,
    backoffMs: number,
    maxBackoffMs: number,
    singleTryRejectionCallback: (error: Error) => void
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      handler()
        .then(resolve)
        .catch(error => {
          const newBackoffMs: number = backoffMs * 2;
          if (newBackoffMs > maxBackoffMs) {
            return reject(error);
          }
          singleTryRejectionCallback(error);
          setTimeout(() => {
            retryWithExponentialBackoff(handler, newBackoffMs, maxBackoffMs, singleTryRejectionCallback)
              .then(resolve)
              .catch(reject);
          }, backoffMs);
        });
    });
  }

  function getLibraryLocationURIs(libraryLocation: string): { jsURI: string; wasmURI: string } {
    let cdnURI: boolean = false;

    if (/^https?:\/\/([^\/.]*\.)*cdn.jsdelivr.net\//.test(libraryLocation)) {
      libraryLocation = "https://cdn.jsdelivr.net/npm/scandit-sdk@%VER%/build/";
      cdnURI = true;
    } else if (/^https?:\/\/([^\/.]*\.)*unpkg.com\//.test(libraryLocation)) {
      libraryLocation = "https://unpkg.com/scandit-sdk@%VER%/build/";
      cdnURI = true;
    }

    if (cdnURI) {
      return {
        jsURI: `${libraryLocation}scandit-engine-sdk.min.js`,
        wasmURI: `${libraryLocation}scandit-engine-sdk.wasm`
      };
    }

    return {
      jsURI: `${libraryLocation}scandit-engine-sdk.min.js?v=%VER%`,
      wasmURI: `${libraryLocation}scandit-engine-sdk.wasm?v=%VER%`
    };
  }

  function arrayBufferToHexString(arrayBuffer: ArrayBuffer): string {
    return Array.from(new Uint8Array(arrayBuffer))
      .map(byteNumber => {
        const byteHex: string = byteNumber.toString(16);

        return byteHex.length === 1 ? /* istanbul ignore next */ `0${byteHex}` : byteHex;
      })
      .join("");
  }

  function applySettings(highQualitySingleFrameMode: boolean = false): void {
    if (settings == null || !contextAvailable || !wasmReady || !scanWorkSubmitted) {
      return;
    }

    scannerSettingsReady = false;

    const parsedSettings: {
      matrixScanEnabled: boolean;
      gpuAcceleration: boolean;
      blurryRecognition: boolean;
    } = JSON.parse(settings);
    const settingsLength: number = Module.lengthBytesUTF8(settings) + 1;
    const settingsPointer: number = Module._malloc(settingsLength);
    Module.stringToUTF8(settings, settingsPointer, settingsLength);
    const resultPointer: number = Module._scanner_settings_new_from_json(
      settingsPointer,
      parsedSettings.blurryRecognition,
      parsedSettings.matrixScanEnabled,
      highQualitySingleFrameMode,
      gpuAccelerationAvailable && parsedSettings.gpuAcceleration
    );
    Module._free(settingsPointer);

    const result: string = Module.UTF8ToString(resultPointer);
    if (result !== "") {
      scannerSettingsReady = true;
      console.debug(JSON.parse(result));
    }
  }

  function applyImageSettings(): void {
    if (imageSettings == null || !wasmReady || !scanWorkSubmitted) {
      return;
    }

    scannerImageSettingsReady = false;

    let channels: number;
    // TODO: For now it's not possible to use imported variables as the worker doesn't have access at runtime
    if (imageSettings.format.valueOf() === 1) {
      // RGB_8U
      channels = 3;
    } else if (imageSettings.format.valueOf() === 2) {
      // RGBA_8U
      channels = 4;
    } else {
      // GRAY_8U
      channels = 1;
    }
    Module._scanner_image_settings_new(imageSettings.width, imageSettings.height, channels);
    if (imageBufferPointer != null) {
      Module._free(imageBufferPointer);
      imageBufferPointer = undefined;
    }
    imageBufferPointer = Module._malloc(imageSettings.width * imageSettings.height * channels);

    scannerImageSettingsReady = true;
  }

  function scanImage(imageData: Uint8ClampedArray): string {
    Module.HEAPU8.set(imageData, imageBufferPointer);

    return Module.UTF8ToString(Module._scanner_scan(<number>imageBufferPointer));
  }

  function parseString(dataFormat: Parser.DataFormat, dataString: string, options: string): string {
    const dataStringLength: number = Module.lengthBytesUTF8(dataString) + 1;
    const dataStringPointer: number = Module._malloc(dataStringLength);
    Module.stringToUTF8(dataString, dataStringPointer, dataStringLength);
    const optionsLength: number = Module.lengthBytesUTF8(options) + 1;
    const optionsPointer: number = Module._malloc(optionsLength);
    Module.stringToUTF8(options, optionsPointer, optionsLength);
    const resultPointer: number = Module._parser_parse_string(
      dataFormat.valueOf(),
      dataStringPointer,
      dataStringLength - 1,
      optionsPointer
    );
    Module._free(dataStringPointer);
    Module._free(optionsPointer);

    return Module.UTF8ToString(resultPointer);
  }

  function verifiedWasmFetch(wasmURI: string, awaitFullResponse: boolean): Promise<Response> {
    function verifyResponseData(responseData: ArrayBuffer): void {
      // istanbul ignore else
      if (crypto.subtle != null && typeof crypto.subtle === "object" && typeof crypto.subtle.digest === "function") {
        crypto.subtle.digest("SHA-256", responseData).then(hash => {
          const hashString: string = arrayBufferToHexString(hash);
          // istanbul ignore if
          if (hashString !== "%________________________ENGINE_WASM_HASH________________________%") {
            console.error(
              `The Scandit SDK Engine library WASM file found at ${wasmURI} seems invalid: ` +
                `expected file hash doesn't match (received: ${hashString}, ` +
                `expected: ${"%________________________ENGINE_WASM_HASH________________________%"}). ` +
                `Please ensure the correct Scandit SDK Engine file (with correct version) is retrieved.`
            );
          }
        });
      } else {
        console.warn(
          "Insecure origin (see https://goo.gl/Y0ZkNV): " +
            `The hash of the Scandit SDK Engine library WASM file found at ${wasmURI} could not be verified`
        );
      }
    }

    function tryFetch(): Promise<Response> {
      return new Promise((resolve, reject) => {
        fetch(wasmURI)
          .then(response => {
            // istanbul ignore else
            if (response.ok) {
              response
                .clone()
                .arrayBuffer()
                .then(responseData => {
                  if (awaitFullResponse) {
                    resolve(response);
                  }
                  verifyResponseData(responseData);
                })
                .catch(
                  // istanbul ignore next
                  error => {
                    if (awaitFullResponse) {
                      reject(error);
                    }
                  }
                );

              if (!awaitFullResponse) {
                resolve(response);
              }
            } else {
              reject(new Error("HTTP status code is not ok"));
            }
          })
          .catch(error => {
            reject(error);
          });
      });
    }

    return retryWithExponentialBackoff(tryFetch, 250, 4000, error => {
      console.warn(error);
      console.warn(`Couldn't retrieve Scandit SDK Engine library at ${wasmURI}, retrying...`);
    }).catch(error => {
      console.error(error);
      console.error(
        `Couldn't retrieve/instantiate Scandit SDK Engine library at ${wasmURI}, ` +
          "did you configure the path for it correctly?"
      );

      return Promise.reject(error);
    });
  }
  function instantiateWebAssembly(
    importObject: object,
    wasmURI: string,
    successCallback: (instance: WebAssembly.Instance) => void
  ): void {
    verifiedWasmFetch(wasmURI, true)
      .then(response => {
        return response.arrayBuffer();
      })
      .then(bytes => {
        return self.WebAssembly.instantiate(bytes, importObject)
          .then((results: WebAssembly.WebAssemblyInstantiatedSource) => {
            successCallback(results.instance);
          })
          .catch((error: Error) => {
            console.error(error);
            console.error(
              `Couldn't instantiate Scandit SDK Engine library at ${wasmURI}, ` +
                "did you configure the path for it correctly?"
            );
          });
      })
      .catch(
        /* istanbul ignore next */ () => {
          // Ignored
        }
      );
  }

  function instantiateWebAssemblyStreaming(
    importObject: object,
    wasmURI: string,
    successCallback: (instance: WebAssembly.Instance) => void
  ): void {
    verifiedWasmFetch(wasmURI, false)
      .then(response => {
        self.WebAssembly.instantiateStreaming(response, importObject)
          .then((results: WebAssembly.WebAssemblyInstantiatedSource) => {
            successCallback(results.instance);
          })
          .catch((error: Error) => {
            console.warn(error);
            console.warn(
              "WebAssembly streaming compile failed. " +
                "Falling back to ArrayBuffer instantiation (this will make things slower)"
            );
            instantiateWebAssembly(importObject, wasmURI, successCallback);
          });
      })
      .catch(
        /* istanbul ignore next */ () => {
          // Ignored
        }
      );
  }

  function syncFS(): void {
    // istanbul ignore if
    if (fsSyncInProgress === true) {
      fsSyncScheduled = true;
    } else {
      fsSyncInProgress = true;
      fsSyncScheduled = false;
      FS.syncfs(false, () => {
        fsSyncInProgress = false;
        // istanbul ignore if
        if (fsSyncScheduled) {
          syncFS();
        }
      });
    }
  }

  return {
    loadLibrary,
    createContext,
    setSettings,
    setImageSettings,
    workOnScanQueue,
    workOnParseQueue,
    addScanWorkUnit,
    addParseWorkUnit,
    clearSession
  };
}

/**
 * @hidden
 */
// istanbul ignore next
export function engineWorkerFunction(): void {
  const engineInstance: Engine = engine();

  onmessage = e => {
    // Setting settings triggers license verification and activation: delay until first frame processed
    // tslint:disable:no-reserved-keywords max-union-size
    const data:
      | {
          type: "load-library";
          deviceId: string;
          libraryLocation: string;
          path: string;
          deviceModelName?: string;
          uaBrowserName?: string;
        }
      | { type: "license-key"; licenseKey: string }
      | { type: "settings"; settings: string }
      | { type: "image-settings"; imageSettings: ImageSettings }
      | { type: "work"; requestId: number; data: Uint8ClampedArray; highQualitySingleFrameMode: boolean }
      | { type: "parse-string"; requestId: number; dataFormat: Parser.DataFormat; dataString: string; options: string }
      | { type: "clear-session" } = e.data;
    // tslint:enable:no-reserved-keywords max-union-size
    switch (data.type) {
      case "load-library":
        // tslint:disable-next-line: no-floating-promises
        engineInstance.loadLibrary(
          data.deviceId,
          data.libraryLocation,
          data.path,
          data.deviceModelName,
          data.uaBrowserName
        );
        break;
      case "license-key":
        engineInstance.createContext(data.licenseKey);
        engineInstance.workOnParseQueue();
        break;
      case "settings":
        engineInstance.setSettings(data.settings);
        engineInstance.workOnScanQueue();
        break;
      case "image-settings":
        engineInstance.setImageSettings(data.imageSettings);
        engineInstance.workOnScanQueue();
        break;
      case "work":
        engineInstance.addScanWorkUnit({
          requestId: data.requestId,
          data: data.data,
          highQualitySingleFrameMode: data.highQualitySingleFrameMode
        });
        break;
      case "parse-string":
        engineInstance.addParseWorkUnit({
          requestId: data.requestId,
          dataFormat: data.dataFormat,
          dataString: data.dataString,
          options: data.options
        });
        break;
      case "clear-session":
        engineInstance.clearSession();
        break;
      default:
        break;
    }
  };
}

/**
 * @hidden
 */
export const engineWorkerBlob: Blob = new Blob(
  [`var Module;${engine.toString()}(${engineWorkerFunction.toString()})()`],
  {
    type: "text/javascript"
  }
);
