import { FeatureDetection } from "../../Source/Cesium.js";
import { TaskProcessor } from "../../Source/Cesium.js";
import absolutize from "../absolutize.js";
import { when } from "../../Source/Cesium.js";

describe("Core/TaskProcessor", function () {
  var taskProcessor;

  afterEach(function () {
    if (taskProcessor && !taskProcessor.isDestroyed()) {
      taskProcessor = taskProcessor.destroy();
    }
  });

  it("works with a simple worker defined as relative to TaskProcessor._workerModulePrefix", function () {
    TaskProcessor._workerModulePrefix = absolutize("../Specs/TestWorkers/");
    taskProcessor = new TaskProcessor("returnParameters.js");

    var parameters = {
      prop: "blah",
      obj: {
        val: true,
      },
    };

    return taskProcessor
      .scheduleTask(parameters)
      .then(function (result) {
        expect(result).toEqual(parameters);
      })
      .always(function () {
        TaskProcessor._workerModulePrefix =
          TaskProcessor._defaultWorkerModulePrefix;
      });
  });

  it("can be destroyed", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnParameters.js")
    );

    expect(taskProcessor.isDestroyed()).toEqual(false);

    taskProcessor.destroy();

    expect(taskProcessor.isDestroyed()).toEqual(true);
  });

  it("can transfer array buffer", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnByteLength.js")
    );

    var byteLength = 100;
    var parameters = new ArrayBuffer(byteLength);
    expect(parameters.byteLength).toEqual(byteLength);

    return when(TaskProcessor._canTransferArrayBuffer, function (
      canTransferArrayBuffer
    ) {
      var promise = taskProcessor.scheduleTask(parameters, [parameters]);

      if (canTransferArrayBuffer) {
        // array buffer should be neutered when transferred
        expect(parameters.byteLength).toEqual(0);
      }

      // the worker should see the array with proper byte length
      return promise.then(function (result) {
        expect(result).toEqual(byteLength);
      });
    });
  });

  it("can transfer array buffer back from worker", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/transferArrayBuffer.js")
    );

    var byteLength = 100;
    var parameters = {
      byteLength: byteLength,
    };

    // the worker should see the array with proper byte length
    return taskProcessor.scheduleTask(parameters).then(function (result) {
      expect(result.byteLength).toEqual(100);
    });
  });

  it("rejects promise if worker throws", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/throwError.js")
    );

    var message = "foo";
    var parameters = {
      message: message,
    };

    return taskProcessor
      .scheduleTask(parameters)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error.message).toEqual(message);
      });
  });

  it("rejects promise if worker returns a non-clonable result", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnNonCloneable.js")
    );

    var message = "foo";
    var parameters = {
      message: message,
    };

    return taskProcessor
      .scheduleTask(parameters)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(error).toContain("postMessage failed");
      });
  });

  it("successful task raises the taskCompletedEvent", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnParameters.js")
    );

    var parameters = {
      prop: "blah",
      obj: {
        val: true,
      },
    };
    var eventRaised = false;
    var removeListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(
      function () {
        eventRaised = true;
      }
    );

    return taskProcessor
      .scheduleTask(parameters)
      .then(function (result) {
        expect(eventRaised).toBe(true);
      })
      .always(function () {
        removeListenerCallback();
      });
  });

  it("unsuccessful task raises the taskCompletedEvent with error", function () {
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnNonCloneable.js")
    );

    var message = "foo";
    var parameters = {
      message: message,
    };

    var eventRaised = false;
    var removeListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(
      function (error) {
        eventRaised = true;
        expect(error).toBeDefined();
      }
    );

    return taskProcessor
      .scheduleTask(parameters)
      .then(function () {
        fail("should not be called");
      })
      .otherwise(function (error) {
        expect(eventRaised).toBe(true);
      })
      .always(function () {
        removeListenerCallback();
      });
  });

  it("can load and compile web assembly module", function () {
    var binaryUrl = absolutize("../Specs/TestWorkers/TestWasm/testWasm.wasm");
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnWasmConfig.js", 5)
    );
    var promise = taskProcessor.initWebAssemblyModule({
      modulePath: "TestWasm/testWasmWrapper",
      wasmBinaryFile: binaryUrl,
      fallbackModulePath: "TestWasm/testWasmFallback",
    });

    return promise.then(function (result) {
      expect(result).toBeDefined();
      if (FeatureDetection.supportsWebAssembly()) {
        expect(result.modulePath).toMatch(/TestWasm\/testWasmWrapper/);
        expect(result.wasmBinary).toBeDefined();
      }
    });
  });

  it("uses a backup module if web assembly is not supported", function () {
    var binaryUrl = absolutize("../Specs/TestWorkers/TestWasm/testWasm.wasm");
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnWasmConfig.js", 5)
    );

    spyOn(FeatureDetection, "supportsWebAssembly").and.returnValue(false);

    var promise = taskProcessor.initWebAssemblyModule({
      modulePath: "TestWasm/testWasmWrapper",
      wasmBinaryFile: binaryUrl,
      fallbackModulePath: "TestWasm/testWasmFallback",
    });

    return promise.then(function (result) {
      expect(result).toBeDefined();
      expect(result.modulePath).toMatch(/TestWasm\/testWasmFallback/);
      expect(result.wasmBinary).not.toBeDefined();
    });
  });

  it("throws runtime error if web assembly is not supported and no backup is provided", function () {
    var binaryUrl = absolutize("../Specs/TestWorkers/TestWasm/testWasm.wasm");
    taskProcessor = new TaskProcessor(
      absolutize("../Specs/TestWorkers/returnWasmConfig.js", 5)
    );

    spyOn(FeatureDetection, "supportsWebAssembly").and.returnValue(false);

    expect(function () {
      taskProcessor.initWebAssemblyModule({
        modulePath: "TestWasm/testWasmWrapper",
        wasmBinaryFile: binaryUrl,
      });
    }).toThrowRuntimeError();
  });
});
