/**
 * @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 {tensor2d, tensor3d, test_util} from '@tensorflow/tfjs-core';
// TODO(kangyizhang): import from index once microphone is exported.
import * as tfd from '../readers';
// tslint:disable-next-line: no-imports-from-dist
import {describeWithFlags} from '@tensorflow/tfjs-core/dist/jasmine_util';
import {MEDIA_ENVS, setupFakeAudioStream} from '../util/test_utils';

describeWithFlags('MicrophoneIterator', MEDIA_ENVS, () => {
  beforeEach(() => {
    setupFakeAudioStream();
  });

  it('gets tensor with default shape with no config', async () => {
    const microphoneIterator = await tfd.microphone();
    const result = await microphoneIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.spectrogram.shape).toEqual([43, 1024, 1]);
  });

  it('throws error when sample rate is not available', async () => {
    try {
      await tfd.microphone({sampleRateHz: 48000});
      fail();
    } catch (e) {
      expect(e.message).toEqual(
          'Mismatch in sampling rate: Expected: 48000; Actual: 44100');
    }
  });

  it('gets sample rate', async () => {
    const microphoneIterator = await tfd.microphone({sampleRateHz: 44100});
    expect(microphoneIterator.getSampleRate()).toEqual(44100);
  });

  it('uses available sample rate on device when it is not provided',
     async () => {
       const microphoneIterator = await tfd.microphone();
       expect(microphoneIterator.getSampleRate()).toEqual(44100);
     });

  it('gets tensor in correct shape with fftSize', async () => {
    const microphoneIterator = await tfd.microphone({fftSize: 16});
    const result = await microphoneIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.spectrogram.shape).toEqual([43, 16, 1]);
  });

  it('throws error with invalid fftSize', async () => {
    try {
      await tfd.microphone({fftSize: 1000});
      fail();
    } catch (e) {
      expect(e.message).toEqual(
          'Invalid fftSize: it must be a power of 2 between 2 to 4 and ' +
          '2 to 14, but got 1000');
    }
  });

  it('gets tensor in correct shape with columnTruncateLength', async () => {
    const microphoneIterator =
        await tfd.microphone({columnTruncateLength: 232, fftSize: 128});
    const result = await microphoneIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.spectrogram.shape).toEqual([43, 232, 1]);
  });

  it('gets tensor in correct shape with numFramesPerSpectrogram',
     async () => {
       const microphoneIterator =
           await tfd.microphone({numFramesPerSpectrogram: 3, fftSize: 16});
       const result = await microphoneIterator.next();
       expect(result.done).toBeFalsy();
  
       expect(result.value.spectrogram.shape).toEqual([3, 16, 1]);
     });

  it('gets tensor in correct shape with full spectrogram config',
     async () => {
       const microphoneIterator = await tfd.microphone({
         sampleRateHz: 44100,
         fftSize: 16,
         numFramesPerSpectrogram: 10,
         columnTruncateLength: 10
       });
       const result = await microphoneIterator.next();
       expect(result.done).toBeFalsy();
  
       expect(result.value.spectrogram.shape).toEqual([10, 10, 1]);
     });

  it('provides both spectrogram and waveform', async () => {
    const microphoneIterator = await tfd.microphone(
        {includeSpectrogram: true, includeWaveform: true, fftSize: 16});
    const result = await microphoneIterator.next();
    expect(result.done).toBeFalsy();
    expect(result.value.spectrogram.shape).toEqual([43, 16, 1]);
    expect(result.value.waveform.shape).toEqual([688, 1]);
  });

  it('stops and restarts microphone', async () => {
    const microphoneIterator = await tfd.microphone({fftSize: 16});
    const result1 = await microphoneIterator.next();
    expect(result1.done).toBeFalsy();
    expect(result1.value.spectrogram.shape).toEqual([43, 16, 1]);
    microphoneIterator.stop();
    const result2 = await microphoneIterator.next();
    expect(result2.done).toBeTruthy();
    expect(result2.value).toBeNull();
    microphoneIterator.start();
    expect(result1.done).toBeFalsy();
    expect(result1.value.spectrogram.shape).toEqual([43, 16, 1]);
  });

  it('stops microphone multiple times', async () => {
    const microphoneIterator =
        await tfd.microphone({fftSize: 16, numFramesPerSpectrogram: 2});
    const result1 = await microphoneIterator.next();
    expect(result1.done).toBeFalsy();
    expect(result1.value.spectrogram.shape).toEqual([2, 16, 1]);
    microphoneIterator.stop();
    const result2 = await microphoneIterator.next();
    expect(result2.done).toBeTruthy();
    expect(result2.value).toBeNull();
    microphoneIterator.stop();
    const result3 = await microphoneIterator.next();
    expect(result3.done).toBeTruthy();
    expect(result3.value).toBeNull();
  });

  it('gets spectrogram and waveform tensor with correct value', async () => {
    const microphoneIterator = await tfd.microphone({
      numFramesPerSpectrogram: 1,
      fftSize: 16,
      includeSpectrogram: true,
      includeWaveform: true
    });
    const result = await microphoneIterator.next();
    expect(result.done).toBeFalsy();
    const value = result.value;
    expect(value.spectrogram.shape).toEqual([1, 16, 1]);
    test_util.expectArraysClose(
        await value.spectrogram.array(),
        await tensor3d([[
          [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12],
          [13], [14], [15]
        ]]).array());
    expect(value.waveform.shape).toEqual([16, 1]);
    test_util.expectArraysClose(
        await value.waveform.array(),
        await tensor2d([
          [-16], [-17], [-18], [-19], [-20], [-21], [-22], [-23], [-24],
          [-25], [-26], [-27], [-28], [-29], [-30], [-31]
        ]).array());
  });

  it('calls iterator.next() concurrently', async () => {
    let timesRun = 0;
    let tensorsReturned = 0;
    const microphoneIterator = await tfd.microphone(
        {numFramesPerSpectrogram: 10, columnTruncateLength: 10, fftSize: 32});

    // This function will be called 3 times. Between each call there is a
    // 200ms interval. The spectrogram tensor will be returned after 464ms.
    /**
     * The events happen in sequence are:
     * call 1st at 0ms,    timesRun:1, tensorsReturned:0;
     * call 2nd at 1ms,  timesRun:2, tensorsReturned:0;
     * call 3rd at 2ms,  timesRun:3, tensorsReturned:0;
     * tensor returned from 1st call at ~7ms, timesRun:3, tensorsReturned:1;
     * tensor returned from 2nd call,  timesRun:3, tensorsReturned:2;
     * tensor returned from 3rd call,  timesRun:3, tensorsReturned:3.
     */
    const getTensor = async () => {
      // Clear the interval after it ran 3 times.
      if (timesRun === 3) {
        clearInterval(interval);
      } else {
        timesRun++;
        expect(tensorsReturned).toBe(0);
        const result = await microphoneIterator.next();
        tensorsReturned++;
        // When the first tensor got returned (~464ms), getTensor() function
        // should have been called 3 times (at 400ms).
        expect(timesRun).toBe(3);
        expect(result.done).toBeFalsy();
   
        const value = result.value;
        expect(value.spectrogram.shape).toEqual([10, 10, 1]);
      }
    };

    // Call iterator.next() every 200 milliseconds, stop after 3 times.
    const interval = setInterval(getTensor, 1);

    // Wait 3 seconds for the intervals to run.
    await new Promise<void>(resolve => {
      setTimeout(() => {
        resolve();
      }, 100);
    });
    // Assert the intervals run 3 times.
    expect(timesRun).toBe(3);
    expect(tensorsReturned).toBe(3);
  });

  it('gets spectrogram from iterator.capture', async () => {
    const microphoneIterator =
        await tfd.microphone({fftSize: 16, numFramesPerSpectrogram: 1});
    const result = await microphoneIterator.capture();
    expect(result.spectrogram.shape).toEqual([1, 16, 1]);
  });

  it('gets waveform from iterator.capture', async () => {
    const microphoneIterator = await tfd.microphone({
      includeSpectrogram: false,
      includeWaveform: true,
      fftSize: 16,
      numFramesPerSpectrogram: 1
    });
    const result = await microphoneIterator.capture();
    expect(result.waveform.shape).toEqual([16, 1]);
  });

  it('gets spectrogram and waveform from iterator.capture', async () => {
    const microphoneIterator = await tfd.microphone({
      includeSpectrogram: true,
      includeWaveform: true,
      fftSize: 16,
      numFramesPerSpectrogram: 1
    });
    const result = await microphoneIterator.capture();
    expect(result.spectrogram.shape).toEqual([1, 16, 1]);
    expect(result.waveform.shape).toEqual([16, 1]);
  });
});
