/**
 * @fileoverview StreamingWorkletProcessor, the AudioWorkletProcessor
 * for the Google text-to-speech extension.
 *
 * An AudioWorkletProcessor runs in the audio thread, it can only communicate
 * with the rest of the extension via message-passing.
 *
 * The design is very simple: It listens for just two commands from the
 * corresponding AudioWorkletNode's message port: 'addBuffer' gets a single
 * buffer of mono float32 audio samples, in exactly the length expected
 * by AudioWorkletProcessor.process, and adds it to a queue. 'clearBuffers'
 * clears the queue. Then, every time |process| is called, it just shifts
 * the front of the queue and outputs it.
 */
class StreamingWorkletProcessor extends AudioWorkletProcessor {
  constructor() {
    super();

    this.port.onmessage = this.onEvent.bind(this);

    // TODO: add type annotations
    this.bufferDatas_ = [];
    this.active_ = false;
    this.first_ = true;
  }

  /**
   * Implement process() from the AudioWorkletProcessor interface.
   * TODO: find externs so we can use @override.
   * @param {!object} inputs Unimportant here since we only do audio output.
   * @param {!object} outputs sequence<sequence<Float32Array>> the output
   *     audio buffer that is to be consumed by the user agent.
   * @return {boolean} True to keep processing audio.
   */
  process(inputs, outputs) {
    if (!this.active_) {
      return true;
    }

    if (this.bufferDatas_.length == 0) {
      this.active_ = false;
      this.port.postMessage({type: 'empty'});
      return true;
    }

    let bufferData = this.bufferDatas_.shift();
    let buffer = bufferData.buffer;
    let charIndices = bufferData.charIndices;
    if (charIndices.length) {
      this.port.postMessage({type: 'charIndices', charIndices});
    }

    let output = outputs[0];
    if (this.first_) {
      this.first_ = false;
    }
    for (let channel = 0; channel < output.length; ++channel)
      output[channel].set(buffer);

    return true;
  }

  /**
   * Handle events sent to our message port.
   * @param {!DOMEvent} event The incoming event.
   */
  onEvent(event) {
    switch (event.data.command) {
      case 'addBuffer':
        this.active_ = true;
        this.bufferDatas_.push(event.data);
        break;
      case 'clearBuffers':
        this.active_ = false;
        this.bufferDatas_.length = 0;
        break;
    }
  }
}

registerProcessor('streaming-worklet-processor', StreamingWorkletProcessor);
