/**
 * @license
 * Copyright 2019 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * =============================================================================
 */

import {memory, tensor3d, test_util} from '@tensorflow/tfjs-core';
// tslint:disable-next-line: no-imports-from-dist
import {describeWithFlags} from '@tensorflow/tfjs-core/dist/jasmine_util';
import {MEDIA_ENVS, replaceHTMLVideoElementSource, setupFakeVideoStream} from '../util/test_utils';
import {WebcamIterator} from './webcam_iterator';

describeWithFlags('WebcamIterator', MEDIA_ENVS, () => {
  beforeEach(() => {
    setupFakeVideoStream();
  });

  it('create webcamIterator with html element', async () => {
    const videoElement = document.createElement('video');
    videoElement.width = 160;
    videoElement.height = 90;

    const webcamIterator = await WebcamIterator.create(videoElement);

    await replaceHTMLVideoElementSource(videoElement);

    const result = await webcamIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.shape).toEqual([90, 160, 3]);
  });

  it('create webcamIterator with html element and capture', async () => {
    const videoElement = document.createElement('video');
    videoElement.width = 160;
    videoElement.height = 90;

    const webcamIterator = await WebcamIterator.create(videoElement);

    await replaceHTMLVideoElementSource(videoElement);

    const result = await webcamIterator.capture();
    expect(result.shape).toEqual([90, 160, 3]);
  });

  it('create webcamIterator with no html element', async () => {
    const webcamIterator = await WebcamIterator.create(
        null, {resizeWidth: 300, resizeHeight: 150});
    const result = await webcamIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.shape).toEqual([150, 300, 3]);
  });

  it('create webcamIterator with no html element and capture', async () => {
    const webcamIterator = await WebcamIterator.create(
        null, {resizeWidth: 300, resizeHeight: 150});
    const result = await webcamIterator.capture();
    expect(result.shape).toEqual([150, 300, 3]);
  });

  it('create webcamIterator with no html element and no size', async () => {
    try {
      await WebcamIterator.create();
      fail();
    } catch (e) {
      expect(e.message).toEqual(
          'Please provide webcam video element, or resizeWidth and ' +
          'resizeHeight to create a hidden video element.');
    }
  });

  it('resize and center crop has correct shape with html element',
     async () => {
       const videoElement = document.createElement('video');
       videoElement.width = 100;
       videoElement.height = 200;
       const webcamIterator = await WebcamIterator.create(
           videoElement,
           {resizeWidth: 30, resizeHeight: 40, centerCrop: true});
       const result = await webcamIterator.next();
       expect(result.done).toBeFalsy();
       expect(result.value.shape).toEqual([40, 30, 3]);
     });

  it('resize and center crop has correct pixel with html element',
     async () => {
       const videoElement = document.createElement('video');
       videoElement.width = 4;
       videoElement.height = 4;
       const webcamIterator = await WebcamIterator.create(
           videoElement, {resizeWidth: 2, resizeHeight: 2, centerCrop: true});
       const originalImg = tensor3d([
         [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
         [
           [1, 1, 1],
           [11, 12, 13],
           [14, 15, 16],
           [1, 1, 1],
         ],
         [
           [1, 1, 1],
           [1, 2, 3],
           [4, 5, 6],
           [1, 1, 1],
         ],
         [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
       ]);
       const croppedImg = webcamIterator.cropAndResizeFrame(originalImg);
       test_util.expectArraysClose(
           await croppedImg.array(),
           await tensor3d([
             [[6.625, 7.1875, 7.75], [8.3125, 8.875, 9.4375]],

             [[1, 1.5625, 2.125], [2.6875, 3.25, 3.8125]]
           ]).array());
     });

  it('resize in bilinear method has correct shape with html element',
     async () => {
       const videoElement = document.createElement('video');
       videoElement.width = 100;
       videoElement.height = 200;

       const webcamIterator = await WebcamIterator.create(
           videoElement,
           {resizeWidth: 30, resizeHeight: 40, centerCrop: false});
       const result = await webcamIterator.next();
       expect(result.done).toBeFalsy();
       expect(result.value.shape).toEqual([40, 30, 3]);
     });

  it('resize in bilinear method has correct pixel with html element',
     async () => {
       const videoElement = document.createElement('video');
       videoElement.width = 4;
       videoElement.height = 4;
       const webcamIterator = await WebcamIterator.create(
           videoElement,
           {resizeWidth: 2, resizeHeight: 2, centerCrop: false});
       const originalImg = tensor3d([
         [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
         [
           [1, 1, 1],
           [11, 12, 13],
           [14, 15, 16],
           [1, 1, 1],
         ],
         [
           [1, 1, 1],
           [1, 2, 3],
           [4, 5, 6],
           [1, 1, 1],
         ],
         [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
       ]);
       const croppedImg = webcamIterator.cropAndResizeFrame(originalImg);
       test_util.expectArraysClose(
           await croppedImg.array(), await tensor3d([
                                       [[1, 1, 1], [1, 1, 1]],

                                       [[1, 1, 1], [1, 1, 1]]
                                     ]).array());
     });

  it('webcamIterator could stop', async () => {
    const videoElement = document.createElement('video');
    videoElement.width = 160;
    videoElement.height = 90;

    const webcamIterator = await WebcamIterator.create(videoElement);
    await replaceHTMLVideoElementSource(videoElement);
    const result1 = await webcamIterator.next();
    expect(result1.done).toBeFalsy();
    expect(result1.value.shape).toEqual([90, 160, 3]);

    await webcamIterator.stop();
    const result2 = await webcamIterator.next();
    expect(result2.done).toBeTruthy();
    expect(result2.value).toBeNull();
  });

  it('webcamIterator could restart', async () => {
    const videoElement = document.createElement('video');
    videoElement.width = 160;
    videoElement.height = 90;

    const webcamIterator = await WebcamIterator.create(videoElement);
    await replaceHTMLVideoElementSource(videoElement);
    const result1 = await webcamIterator.next();
    expect(result1.done).toBeFalsy();
    expect(result1.value.shape).toEqual([90, 160, 3]);

    webcamIterator.stop();
    const result2 = await webcamIterator.next();
    expect(result2.done).toBeTruthy();
    expect(result2.value).toBeNull();

    // Skip validation when it's in Firefox and Mac OS, because BrowserStack
    // for Firefox does not trigger the readyState event when restarting.
    if (navigator.userAgent.search('Firefox') < 0 &&
        navigator.userAgent.search('OS X') < 0) {
      await webcamIterator.start();
      await replaceHTMLVideoElementSource(videoElement);
      const result3 = await webcamIterator.next();
      expect(result3.done).toBeFalsy();
      expect(result3.value.shape).toEqual([90, 160, 3]);
    }
  });

  it('capture with cropAndResize has no memory leaks', async () => {
    const videoElement = document.createElement('video');
    videoElement.width = 100;
    videoElement.height = 200;
    const webcamIterator = await WebcamIterator.create(
        videoElement, {resizeWidth: 30, resizeHeight: 40, centerCrop: true});

    const before = memory();

    const result = await webcamIterator.capture();
    result.dispose();

    const after = memory();

    expect(after.numTensors).toEqual(before.numTensors);
  });
});
