import { dew as _utilsDewDew } from "../utils.dew.js";
import { dew as _ConvertWorkerDewDew } from "./ConvertWorker.dew.js";
import { dew as _GenericWorkerDewDew } from "./GenericWorker.dew.js";
import { dew as _base64DewDew } from "../base64.dew.js";
import { dew as _supportDewDew } from "../support.dew.js";
import { dew as _externalDewDew } from "../external.dew.js";
import { dew as _NodejsStreamOutputAdapterDewDew } from "../nodejs/NodejsStreamOutputAdapter.dew.js";
import _buffer from "/npm:@jspm/core@1/nodelibs/buffer.js";
var exports = {},
    _dewExec = false;
export function dew() {
  if (_dewExec) return exports;
  _dewExec = true;
  var Buffer = _buffer.Buffer;

  var utils = _utilsDewDew();

  var ConvertWorker = _ConvertWorkerDewDew();

  var GenericWorker = _GenericWorkerDewDew();

  var base64 = _base64DewDew();

  var support = _supportDewDew();

  var external = _externalDewDew();

  var NodejsStreamOutputAdapter = null;

  if (support.nodestream) {
    try {
      NodejsStreamOutputAdapter = _NodejsStreamOutputAdapterDewDew();
    } catch (e) {}
  }
  /**
   * Apply the final transformation of the data. If the user wants a Blob for
   * example, it's easier to work with an U8intArray and finally do the
   * ArrayBuffer/Blob conversion.
   * @param {String} type the name of the final type
   * @param {String|Uint8Array|Buffer} content the content to transform
   * @param {String} mimeType the mime type of the content, if applicable.
   * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format.
   */


  function transformZipOutput(type, content, mimeType) {
    switch (type) {
      case "blob":
        return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType);

      case "base64":
        return base64.encode(content);

      default:
        return utils.transformTo(type, content);
    }
  }
  /**
   * Concatenate an array of data of the given type.
   * @param {String} type the type of the data in the given array.
   * @param {Array} dataArray the array containing the data chunks to concatenate
   * @return {String|Uint8Array|Buffer} the concatenated data
   * @throws Error if the asked type is unsupported
   */


  function concat(type, dataArray) {
    var i,
        index = 0,
        res = null,
        totalLength = 0;

    for (i = 0; i < dataArray.length; i++) {
      totalLength += dataArray[i].length;
    }

    switch (type) {
      case "string":
        return dataArray.join("");

      case "array":
        return Array.prototype.concat.apply([], dataArray);

      case "uint8array":
        res = new Uint8Array(totalLength);

        for (i = 0; i < dataArray.length; i++) {
          res.set(dataArray[i], index);
          index += dataArray[i].length;
        }

        return res;

      case "nodebuffer":
        return Buffer.concat(dataArray);

      default:
        throw new Error("concat : unsupported type '" + type + "'");
    }
  }
  /**
   * Listen a StreamHelper, accumulate its content and concatenate it into a
   * complete block.
   * @param {StreamHelper} helper the helper to use.
   * @param {Function} updateCallback a callback called on each update. Called
   * with one arg :
   * - the metadata linked to the update received.
   * @return Promise the promise for the accumulation.
   */


  function accumulate(helper, updateCallback) {
    return new external.Promise(function (resolve, reject) {
      var dataArray = [];
      var chunkType = helper._internalType,
          resultType = helper._outputType,
          mimeType = helper._mimeType;
      helper.on('data', function (data, meta) {
        dataArray.push(data);

        if (updateCallback) {
          updateCallback(meta);
        }
      }).on('error', function (err) {
        dataArray = [];
        reject(err);
      }).on('end', function () {
        try {
          var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType);
          resolve(result);
        } catch (e) {
          reject(e);
        }

        dataArray = [];
      }).resume();
    });
  }
  /**
   * An helper to easily use workers outside of JSZip.
   * @constructor
   * @param {Worker} worker the worker to wrap
   * @param {String} outputType the type of data expected by the use
   * @param {String} mimeType the mime type of the content, if applicable.
   */


  function StreamHelper(worker, outputType, mimeType) {
    var internalType = outputType;

    switch (outputType) {
      case "blob":
      case "arraybuffer":
        internalType = "uint8array";
        break;

      case "base64":
        internalType = "string";
        break;
    }

    try {
      // the type used internally
      this._internalType = internalType; // the type used to output results

      this._outputType = outputType; // the mime type

      this._mimeType = mimeType;
      utils.checkSupport(internalType);
      this._worker = worker.pipe(new ConvertWorker(internalType)); // the last workers can be rewired without issues but we need to
      // prevent any updates on previous workers.

      worker.lock();
    } catch (e) {
      this._worker = new GenericWorker("error");

      this._worker.error(e);
    }
  }

  StreamHelper.prototype = {
    /**
     * Listen a StreamHelper, accumulate its content and concatenate it into a
     * complete block.
     * @param {Function} updateCb the update callback.
     * @return Promise the promise for the accumulation.
     */
    accumulate: function (updateCb) {
      return accumulate(this, updateCb);
    },

    /**
     * Add a listener on an event triggered on a stream.
     * @param {String} evt the name of the event
     * @param {Function} fn the listener
     * @return {StreamHelper} the current helper.
     */
    on: function (evt, fn) {
      var self = this;

      if (evt === "data") {
        this._worker.on(evt, function (chunk) {
          fn.call(self, chunk.data, chunk.meta);
        });
      } else {
        this._worker.on(evt, function () {
          utils.delay(fn, arguments, self);
        });
      }

      return this;
    },

    /**
     * Resume the flow of chunks.
     * @return {StreamHelper} the current helper.
     */
    resume: function () {
      utils.delay(this._worker.resume, [], this._worker);
      return this;
    },

    /**
     * Pause the flow of chunks.
     * @return {StreamHelper} the current helper.
     */
    pause: function () {
      this._worker.pause();

      return this;
    },

    /**
     * Return a nodejs stream for this helper.
     * @param {Function} updateCb the update callback.
     * @return {NodejsStreamOutputAdapter} the nodejs stream.
     */
    toNodejsStream: function (updateCb) {
      utils.checkSupport("nodestream");

      if (this._outputType !== "nodebuffer") {
        // an object stream containing blob/arraybuffer/uint8array/string
        // is strange and I don't know if it would be useful.
        // I you find this comment and have a good usecase, please open a
        // bug report !
        throw new Error(this._outputType + " is not supported by this method");
      }

      return new NodejsStreamOutputAdapter(this, {
        objectMode: this._outputType !== "nodebuffer"
      }, updateCb);
    }
  };
  exports = StreamHelper;
  return exports;
}