const InlineWorker = require("inline-worker");

console.log('global', global);

export class Recorder {
  config = {
    bufferLen: 4096, // The length of the buffer that the internal JavaScriptNode uses to capture the audio. Can be tweaked if experiencing performance issues. Defaults to 4096.
    numChannels: 1, // 默认单声道
    mimeType: 'audio/wav', // The type of the Blob generated by exportWAV. Defaults to 'audio/wav'.
    onaudioprocess: null
  };

  recording = false;

  callbacks = {
    getBuffer: [],
    encode: [],
    exportWAV: [] // A default callback to be used with exportWAV.
  };

  constructor(source, cfg) {
    Object.assign(this.config, cfg);
    this.context = source.context;
    this.node = (
      this.context.createScriptProcessor || this.context.createJavaScriptNode
    ).call(
      this.context,
      this.config.bufferLen,
      this.config.numChannels,
      this.config.numChannels
    );

    this.node.onaudioprocess = e => {
      if (!this.recording) return;
      var buffer = [];

      for (var channel = 0; channel < this.config.numChannels; channel++) {
        buffer.push(e.inputBuffer.getChannelData(channel));
      }

      // 发送给worker
      this.worker.postMessage({
        command: 'record',
        buffer: buffer
      });

      // 数据回调
      if (this.config.onaudioprocess) {
        this.config.onaudioprocess(buffer[0]);
      }
    };

    source.connect(this.node);
    this.node.connect(this.context.destination); //this should not be necessary

    let self = {};
    // TODO uglifyjs后会报错
    this.worker = new InlineWorker(function(self) {
      
      let recLength = 0,
        recBuffers = [],
        sampleRate,
        numChannels;
      
      console.log('InlineWorker=========>>>>>>>>>');
      console.log(', self',  self);
      
      
      self.onmessage = function(e) {

        switch (e.data.command) {
          case 'init':
            init(e.data.config);
            break;
          case 'record':
            record(e.data.buffer);
            break;
          case 'encode':
            encode(e.data.buffer, e.data.sampleRate);
            break;
          case 'exportWAV':
            exportWAV(e.data.type);
            break;
          case 'getBuffer':
            
            getBuffer();
            break;
          case 'clear':
            clear();
            break;
        }
      };

      function init(config) {
        sampleRate = config.sampleRate;
        numChannels = config.numChannels;
        initBuffers();
      }

      function record(inputBuffer) {
        for (var channel = 0; channel < numChannels; channel++) {
          recBuffers[channel].push(inputBuffer[channel]);
        }
        recLength += inputBuffer[0].length;
      }

      /**
       * 将数据编码为制定码率
       * @param inputBuffer
       * @param desiredSamplingRate
       */
      function encode(inputBuffer, desiredSamplingRate) {
        // 默认为16k
        desiredSamplingRate = desiredSamplingRate || 16000;
        // 需要转换采样率
        if (desiredSamplingRate != sampleRate) {
          // 插值去点
          inputBuffer = interpolateArray(
            inputBuffer,
            desiredSamplingRate,
            sampleRate
          );
        }
        var buffer = new ArrayBuffer(inputBuffer.length * 2);
        var view = new DataView(buffer);
        floatTo16BitPCM(view, 0, inputBuffer);
        var audioBlob = new Blob([view], { type: '' });
        self.postMessage({ command: 'encode', data: audioBlob });
      }

      /**
       * 导出wav
       * @param type
       * @param desiredSamplingRate 期望的采样率
       */
      function exportWAV(type, desiredSamplingRate) {
        // 默认为16k
        desiredSamplingRate = desiredSamplingRate || 16000;
        var buffers = [];
        for (var channel = 0; channel < numChannels; channel++) {
          var buffer = mergeBuffers(recBuffers[channel], recLength);
          // 需要转换采样率
          if (desiredSamplingRate != sampleRate) {
            // 插值去点
            buffer = interpolateArray(buffer, desiredSamplingRate, sampleRate);
          }
          buffers.push(buffer);
        }
        var interleaved =
          numChannels === 2 ? interleave(buffers[0], buffers[1]) : buffers[0];
        var dataview = encodeWAV(interleaved, desiredSamplingRate);
        var audioBlob = new Blob([dataview], { type: type });
        self.postMessage({ command: 'exportWAV', data: audioBlob });
      }

      /**
       * 转换采样率
       * @param data
       * @param newSampleRate 目标采样率
       * @param oldSampleRate 原始数据采样率
       * @returns {any[]|Array}
       */
      function interpolateArray(data, newSampleRate, oldSampleRate) {
        var fitCount = Math.round(
          data.length * (newSampleRate / oldSampleRate)
        );
        var newData = new Array();
        var springFactor = new Number((data.length - 1) / (fitCount - 1));
        newData[0] = data[0]; // for new allocation
        for (var i = 1; i < fitCount - 1; i++) {
          var tmp = i * springFactor;
          var before = new Number(Math.floor(tmp)).toFixed();
          var after = new Number(Math.ceil(tmp)).toFixed();
          var atPoint = tmp - before;
          newData[i] = this.linearInterpolate(
            data[before],
            data[after],
            atPoint
          );
        }
        newData[fitCount - 1] = data[data.length - 1]; // for new allocation
        return newData;
      }

      function linearInterpolate(before, after, atPoint) {
        return before + (after - before) * atPoint;
      }

      function getBuffer() {
        var buffers = [];
        for (var channel = 0; channel < numChannels; channel++) {
          buffers.push(mergeBuffers(recBuffers[channel], recLength));
        }
        self.postMessage({ command: 'getBuffer', data: buffers });
      }

      function clear() {
        recLength = 0;
        recBuffers = [];
        initBuffers();
      }

      function initBuffers() {
        for (let channel = 0; channel < numChannels; channel++) {
          recBuffers[channel] = [];
        }
      }

      function mergeBuffers(recBuffers, recLength) {
        let result = new Float32Array(recLength);
        let offset = 0;
        for (let i = 0; i < recBuffers.length; i++) {
          result.set(recBuffers[i], offset);
          offset += recBuffers[i].length;
        }
        return result;
      }

      function interleave(inputL, inputR) {
        let length = inputL.length + inputR.length;
        let result = new Float32Array(length);

        let index = 0,
          inputIndex = 0;

        while (index < length) {
          result[index++] = inputL[inputIndex];
          result[index++] = inputR[inputIndex];
          inputIndex++;
        }
        return result;
      }

      function floatTo16BitPCM(output, offset, input) {
        for (let i = 0; i < input.length; i++, offset += 2) {
          let s = Math.max(-1, Math.min(1, input[i]));
          output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
        }
      }

      function writeString(view, offset, string) {
        for (let i = 0; i < string.length; i++) {
          view.setUint8(offset + i, string.charCodeAt(i));
        }
      }

      /**
       * 编码为wav
       * @param samples 采样点
       * @param sampleRate 采样率
       * @returns {DataView}
       */
      function encodeWAV(samples, sampleRate) {
        let buffer = new ArrayBuffer(44 + samples.length * 2);
        let view = new DataView(buffer);

        /* RIFF identifier */
        writeString(view, 0, 'RIFF');
        /* RIFF chunk length */
        view.setUint32(4, 36 + samples.length * 2, true);
        /* RIFF type */
        writeString(view, 8, 'WAVE');
        /* format chunk identifier */
        writeString(view, 12, 'fmt ');
        /* format chunk length */
        view.setUint32(16, 16, true);
        /* sample format (raw) */
        view.setUint16(20, 1, true);
        /* channel count */
        view.setUint16(22, numChannels, true);
        /* sample rate */
        view.setUint32(24, sampleRate, true);
        /* byte rate (sample rate * block align) */
        view.setUint32(28, sampleRate * 4, true);
        /* block align (channel count * bytes per sample) */
        view.setUint16(32, numChannels * 2, true);
        /* bits per sample */
        view.setUint16(34, 16, true);
        /* data chunk identifier */
        writeString(view, 36, 'data');
        /* data chunk length */
        view.setUint32(40, samples.length * 2, true);

        floatTo16BitPCM(view, 44, samples);

        return view;
      }
    }, self);


    this.worker.postMessage({
      command: 'init',
      config: {
        sampleRate: this.context.sampleRate,
        numChannels: this.config.numChannels
      }
    });

    this.worker.onmessage = e => {
      let cb = this.callbacks[e.data.command].pop();
      if (typeof cb == 'function') {
        cb(e.data.data);
      }
    };
  }

  record() {
    this.recording = true;
  }

  stop() {
    this.recording = false;
  }

  clear() {
    this.worker.postMessage({ command: 'clear' });
  }

  encode(cb, buffer, sampleRate) {
    cb = cb || this.config.callback;
    if (!cb) throw new Error('Callback not set');
    this.callbacks.encode.push(cb);
    this.worker.postMessage({
      command: 'encode',
      buffer: buffer,
      sampleRate: sampleRate
    });
  }

  getBuffer(cb) {
    console.log('getBuffer ', cb);
    
    cb = cb || this.config.callback;
    if (!cb) throw new Error('Callback not set');

    this.callbacks.getBuffer.push(cb);
    console.log('postMessage getBuffer ');

    this.worker.postMessage({ command: 'getBuffer' });
  }

  exportWAVAndUpload(url, callback) {
    var _url = url;
    exportWAV(function(blob) {
      var fd = new FormData();
      fd.append('audioData', blob);
      var xhr = new XMLHttpRequest();
      if (callback) {
        xhr.upload.addEventListener(
          'progress',
          function(e) {
            callback('uploading', e);
          },
          false
        );
        xhr.addEventListener(
          'load',
          function(e) {
            callback('ok', e);
          },
          false
        );
        xhr.addEventListener(
          'error',
          function(e) {
            callback('error', e);
          },
          false
        );
        xhr.addEventListener(
          'abort',
          function(e) {
            callback('cancel', e);
          },
          false
        );
      }
      xhr.open('POST', url);
      xhr.send(fd);
    });
  }

  exportWAV(cb, mimeType) {
    mimeType = mimeType || this.config.mimeType;
    cb = cb || this.config.callback;
    if (!cb) throw new Error('Callback not set');

    this.callbacks.exportWAV.push(cb);

    this.worker.postMessage({
      command: 'exportWAV',
      type: mimeType
    });
  }

  static forceDownload(blob, filename, el) {
    let url = (window.URL || window.webkitURL).createObjectURL(blob);
    // el.onclick = ()=> {
    //     console.log('');
    //     location.href = url;
    // }
    // let link = window.document.createElement('a');
    let link = el;
    link.href = url;
    link.download = filename || 'output.wav';
    // TODO
    let click = document.createEvent('Event');
    click.initEvent('click', true, true);
    link.dispatchEvent(click);
  }

  static throwError(message, fallback) {
    // alert(message);
    console.log('message', message);
    
    fallback && fallback();
    // throw new function() {
    //   this.toString = function() {
    //     return message;
    //   };
    // }();
  }

  /**
   * 检测音频是否可用
   * @param {*} param
   */
  static checkAudio({success, fallback}) {
    navigator.getUserMedia = 
      navigator.getUserMedia ||
      navigator.webkitGetUserMedia ||
      navigator.mozGetUserMedia ||
      navigator.msGetUserMedia;

    if (navigator.getUserMedia) {
      navigator.getUserMedia({
        audio: true //只启用音频
          // audio: {
          //   mandatory: {
          //       googEchoCancellation: false,
          //       googAutoGainControl: false,
          //       googNoiseSuppression: false,
          //       googHighpassFilter: false
          //   },
          //   optional: []
      },
      function(stream) {
        success && success(stream);
      },
      function(error){
        switch (error.code || error.name) {
          case 'PERMISSION_DENIED':
            Recorder.throwError('用户拒绝提供信息。。。', fallback);
            break;
          case 'NotAllowedError':
            Recorder.throwError('用户拒绝提供信息。。', fallback);
            break;
          case 'PermissionDeniedError':
            Recorder.throwError('用户拒绝提供信息。', fallback);
            break
          case 'NOT_SUPPORTED_ERROR':
          case 'NotSupportedError':
            Recorder.throwError('浏览器不支持硬件设备。', fallback);
            break;
          case 'MANDATORY_UNSATISFIED_ERROR':
          case 'MandatoryUnsatisfiedError':
            Recorder.throwError('无法发现指定的硬件设备。', fallback);
            break;
          default:
            Recorder.throwError(
              '无法打开麦克风。异常信息:' + (error.code || error.name), fallback
            );
            break;
        }
      })
    } else {
      Recorder.throwError('当前浏览器不支持录音功能。', fallback);
    }
  }  

  // 只支持https
  static createRecorder(callback, config, fallback) {
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    window.URL = window.URL || window.webkitURL;

    Recorder.checkAudio({
      success: (stream)=> {
        var audio_context = new AudioContext();
        console.log('audio_context', audio_context);
        var input = audio_context.createMediaStreamSource(stream);
        var rec = new Recorder(input, config);
        callback(rec);
      },
      fallback: fallback
    })
  }
}

export default Recorder;
