/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow

// This worker is imported as WebWorker since it's conflicting with the Worker
// global type.
import WebWorker from './worker-factory';
import { ensureExists } from './flow';

type WorkerMessage =
  | {|
      +msgID: number,
      +type: 'error',
      +error: mixed,
    |}
  | {|
      +msgID: number,
      +type: 'success',
      +result: mixed,
    |};

export function provideHostSide<T: Object>(workerFilename: string, methods: T) {
  return function HostClass(...constructorArguments: mixed[]) {
    const worker = new WebWorker(workerFilename);
    const callbacks = new Map(); // msgID -> { resolve, reject }
    let nextMessageID = 0;

    worker.onmessage = ({ data }) => {
      const message = ((data: any): WorkerMessage);
      const { msgID } = message;
      const { resolve, reject } = ensureExists(
        callbacks.get(msgID),
        'Could not find a callback for a worker'
      );
      callbacks.delete(msgID);

      switch (message.type) {
        case 'success':
          resolve(message.result);
          return;
        case 'error':
          reject(message.error);
          return;
        default:
          throw new Error(`Unhandled message case ${(message: empty)}.`);
      }
    };

    function makeMethod(method) {
      return function(...paramArray) {
        const msgID = nextMessageID++;
        worker.postMessage({ msgID, type: 'method', method, paramArray });
        return new Promise((resolve, reject) => {
          callbacks.set(msgID, { resolve, reject });
        });
      };
    }

    for (const method of methods) {
      this[method] = makeMethod(method);
    }

    worker.postMessage({ type: 'constructor', constructorArguments });
  };
}

export function provideWorkerSide(workerGlobal: Worker, theClass: Function) {
  let theObject = {};
  workerGlobal.onmessage = ({ data }) => {
    const message = ((data: any): WorkerMessage);
    // $FlowFixMe Error introduced by upgrading to v0.96.0.
    if (message.type === 'constructor') {
      theObject = new theClass(...message.constructorArguments);
    } else if (message.type === 'method') {
      const { msgID, method, paramArray } = message;
      theObject[method](...paramArray).then(
        result => {
          workerGlobal.postMessage({ msgID, type: 'success', result });
        },
        error => {
          workerGlobal.postMessage({
            msgID,
            type: 'error',
            error: error.toString(),
          });
        }
      );
    }
  };
}

@@ -390,29 +390,29 @@
 ;%0A%0Atype 
-Worker
 Message
+ToHost
  =%0A  %7C %7B
@@ -554,24 +554,239 @@
 esult: mixed
+%5B%5D,%0A    %7C%7D;%0A%0Atype MessageToWorker =%0A  %7C %7B%7C%0A      +type: 'constructor',%0A      +constructorArguments: mixed,%0A    %7C%7D%0A  %7C %7B%7C%0A      +type: 'method',%0A      +msgID: number,%0A      +method: string,%0A      +paramArray: mixed%5B%5D
 ,%0A    %7C%7D;%0A%0Ae
@@ -1143,37 +1143,37 @@
 data: any): 
-Worker
 Message
+ToHost
 );%0A      con
@@ -2313,83 +2313,25 @@
 y): 
-Worker
 Message
-);%0A    // $FlowFixMe Error introduced by upgrading to v0.96.0.
+ToWorker);
 %0A   

Fix flow errors for utils