/*
 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

var ToneGen = function() {
  /**
  * Initializes tone generator.
   */
  this.init = function() {
    this.audioContext = new AudioContext();
  }

  /**
   * Sets sample rate
   * @param {int} sample rate
   */
  this.setSampleRate = function(sampleRate) {
    this.sampleRate = sampleRate;
  }

  /**
   * Sets start/end frequencies and logarithmic sweep
   * @param {int} start frequency
   * @param {int} end frequency
   * @param {boolean} logarithmic sweep or not
   */
  this.setFreq = function(freqStart, freqEnd, sweepLog) {
    this.freqStart = freqStart;
    this.freqEnd = freqEnd;
    this.sweepLog = sweepLog;
  }

  /**
   * Sets tone duration
   * @param {float} duration in seconds
   */
  this.setDuration = function(duration) {
    this.duration = parseFloat(duration);
  }

  /**
   * Sets left and right gain value
   * @param {float} left gain between 0 and 1
   * @param {float} right gain between 0 and 1
   */
  this.setGain = function(leftGain, rightGain) {
    this.leftGain = parseFloat(leftGain);
    this.rightGain = parseFloat(rightGain);
  }

  /**
   * Generates sine tone buffer
   */
  this.genBuffer = function() {
    this.buffer = this.audioContext.createBuffer(2,
        this.sampleRate * this.duration, this.sampleRate);
    var leftChannel = this.buffer.getChannelData(0);
    var rightChannel = this.buffer.getChannelData(1);
    var phi;
    var k = this.freqEnd / this.freqStart;
    var beta = this.duration / Math.log(k);
    for (var i = 0; i < leftChannel.length; i++) {
      if (this.sweepLog) {
        phi = 2 * Math.PI * this.freqStart * beta *
            (Math.pow(k, i / leftChannel.length) - 1.0);
      } else {
        var f = this.freqStart + (this.freqEnd - this.freqStart) *
            i / leftChannel.length / 2;
        phi = f * 2 * Math.PI * i / this.sampleRate;
      }
      leftChannel[i] = this.leftGain * Math.sin(phi);
      rightChannel[i] = this.rightGain * Math.sin(phi);
    }
  }

  /**
   * Returns generated sine tone buffer
   * @return {AudioBuffer} audio buffer
   */
  this.getBuffer = function() {
    return this.buffer;
  }

  /**
   * Returns append buffer
   * @return {AudioBuffer} append audio buffer
   */
  this.getAppendTone = function(sampleRate) {
    var tone_freq = 1000, duration = 0.5;
    this.setFreq(tone_freq, tone_freq, false);
    this.setDuration(duration);
    this.setGain(1, 1);
    this.setSampleRate(sampleRate);
    this.genBuffer();
    return this.getBuffer();
  }

  this.init();
}

window.ToneGen = ToneGen;

var AudioPlay = function() {
  var playCallback = null;
  var sampleRate;
  var playing = false;

  /**
   * Initializes audio play object
   */
  this.init = function() {
    this.audioContext = new AudioContext();
    this.genChannel();
    this.buffer = null;
    sampleRate = this.audioContext.sampleRate;
  }

  /**
   * Loads audio file
   * @param {blob} audio file
   * @param {function} callback function when file loaded
   */
  this.loadFile = function(file_blob, done_cb) {
    if (file_blob) {
      var audioContext = this.audioContext;
      reader = new FileReader();
      reader.onloadend = function(e) {
        audioContext.decodeAudioData(e.target.result,
            function(buffer) {
              done_cb(file_blob.name, buffer);
            });
      };
      reader.readAsArrayBuffer(file_blob);
    }
  }

  /**
   * Sets audio path
   */
  this.genChannel = function() {
    this.node = (this.audioContext.createScriptProcessor ||
        this.audioContext.createJavaScriptNode).call(
        this.audioContext, 4096, 2, 2);
    this.splitter = this.audioContext.createChannelSplitter(2);
    this.merger = this.audioContext.createChannelMerger(2);
    this.node.connect(this.splitter);
    this.splitter.connect(this.merger, 0, 0);
    this.splitter.connect(this.merger, 1, 1);
    this.merger.connect(this.audioContext.destination);

    this.node.onaudioprocess = function(e) {
      for (var i = 0; i < e.inputBuffer.numberOfChannels; i++) {
        e.outputBuffer.getChannelData(i).set(
            e.inputBuffer.getChannelData(i), 0);
      }
      if (!playing) return;
      if (playCallback) {
        var tmpLeft = e.inputBuffer.getChannelData(0).subarray(
            -FFT_SIZE-1, -1);
        var tmpRight = e.inputBuffer.getChannelData(1).subarray(
            -FFT_SIZE-1, -1);
        playCallback(tmpLeft, tmpRight, sampleRate);
      }
    }
  }

  /**
   * Plays audio
   * @param {function} callback function when audio end
   * @param {function} callback function to get current buffer
   */
  this.play = function(done_cb, play_cb) {
    playCallback = play_cb;
    this.source = this.audioContext.createBufferSource();
    this.source.buffer = this.buffer;
    this.source.onended = function(e) {
      playing = false;
      this.disconnect();
      if (done_cb) {
        done_cb();
      }
    }
    this.source.connect(this.node);
    this.source.start(0);
    playing = true;
  }

  /**
   * Stops audio
   */
  this.stop = function() {
    playing = false;
    this.source.stop();
    this.source.disconnect();
  }

  /**
   * Sets audio buffer
   * @param {AudioBuffer} audio buffer
   * @param {boolean} append tone or not
   */
  this.setBuffer = function(buffer, append) {
    if (append) {
      function copyBuffer(src, dest, offset) {
        for (var i = 0; i < dest.numberOfChannels; i++) {
          dest.getChannelData(i).set(src.getChannelData(i), offset);
        }
      }
      var appendTone = tonegen.getAppendTone(buffer.sampleRate);
      var bufferLength = appendTone.length * 2 + buffer.length;
      var newBuffer = this.audioContext.createBuffer(buffer.numberOfChannels,
          bufferLength, buffer.sampleRate);
      copyBuffer(appendTone, newBuffer, 0);
      copyBuffer(buffer, newBuffer, appendTone.length);
      copyBuffer(appendTone, newBuffer, appendTone.length + buffer.length);
      this.buffer = newBuffer;
    } else {
      this.buffer = buffer;
    }
  }

  this.init();
}

window.AudioPlay = AudioPlay;