text: 'use strict';
  
  var crypto = require('crypto');
  var Md5 = require('spark-md5');
  var setImmediateShim = global.setImmediate || global.setTimeout;
  var MD5_CHUNK_SIZE = 32768;
  
  function sliceShim(arrayBuffer, begin, end) {
    if (typeof arrayBuffer.slice === 'function') {
      if (!begin && !end) {
        return arrayBuffer.slice();
      } else if (!end) {
        return arrayBuffer.slice(begin);
      } else {
        return arrayBuffer.slice(begin, end);
      }
    }
    //
    // shim for IE courtesy of http://stackoverflow.com/a/21440217
    //
  
    //If `begin`/`end` is unspecified, Chrome assumes 0, so we do the same
    //Chrome also converts the values to integers via flooring
    begin = Math.floor(begin || 0);
    end = Math.floor(end || 0);
  
    var len = arrayBuffer.byteLength;
  
    //If either `begin` or `end` is negative, it refers to an
    //index from the end of the array, as opposed to from the beginning.
    //The range specified by the `begin` and `end` values is clamped to the
    //valid index range for the current array.
    begin = begin < 0 ? Math.max(begin + len, 0) : Math.min(len, begin);
    end = end < 0 ? Math.max(end + len, 0) : Math.min(len, end);
  
    //If the computed length of the new ArrayBuffer would be negative, it
    //is clamped to zero.
    if (end - begin <= 0) {
      return new ArrayBuffer(0);
    }
  
    var result = new ArrayBuffer(end - begin);
    var resultBytes = new Uint8Array(result);
    var sourceBytes = new Uint8Array(arrayBuffer, begin, end - begin);
  
    resultBytes.set(sourceBytes);
  
    return result;
  }
  
  // convert a 64-bit int to a binary string
  function intToString(int) {
    var bytes = [
      (int & 0xff),
      ((int >>> 8) & 0xff),
      ((int >>> 16) & 0xff),
      ((int >>> 24) & 0xff)
    ];
    return bytes.map(function (byte) {
      return String.fromCharCode(byte);
    }).join('');
  }
  
  // convert an array of 64-bit ints into
  // a base64-encoded string
  function rawToBase64(raw) {
    var res = '';
    for (var i = 0; i < raw.length; i++) {
      res += intToString(raw[i]);
    }
    return btoa(res);
  }
  
  module.exports = function (data, callback) {
    if (!process.browser) {
      var base64 = crypto.createHash('md5').update(data).digest('base64');
      callback(null, base64);
      return;
    }
    var inputIsString = typeof data === 'string';
    var len = inputIsString ? data.length : data.byteLength;
    var chunkSize = Math.min(MD5_CHUNK_SIZE, len);
    var chunks = Math.ceil(len / chunkSize);
    var currentChunk = 0;
    var buffer = inputIsString ? new Md5() : new Md5.ArrayBuffer();
  
    function append(buffer, data, start, end) {
      if (inputIsString) {
        buffer.appendBinary(data.substring(start, end));
      } else {
        buffer.append(sliceShim(data, start, end));
      }
    }
  
    function loadNextChunk() {
      var start = currentChunk * chunkSize;
      var end = start + chunkSize;
      currentChunk++;
      if (currentChunk < chunks) {
        append(buffer, data, start, end);
        setImmediateShim(loadNextChunk);
      } else {
        append(buffer, data, start, end);
        var raw = buffer.end(true);
        var base64 = rawToBase64(raw);
        callback(null, base64);
        buffer.destroy();
      }
    }
    loadNextChunk();
  };
  