'use strict';

exports.isIterable = isIterable;
exports.isArrayLike = isArrayLike;
exports.isCollection = isCollection;
exports.getIterator = getIterator;
exports.getIteratorMethod = getIteratorMethod;
exports.createIterator = createIterator;
exports.forEach = forEach;
exports.isAsyncIterable = isAsyncIterable;
exports.getAsyncIterator = getAsyncIterator;
exports.getAsyncIteratorMethod = getAsyncIteratorMethod;
exports.createAsyncIterator = createAsyncIterator;
exports.forAwaitEach = forAwaitEach;

var SYMBOL = typeof Symbol === 'function' ? Symbol : void 0;

var SYMBOL_ITERATOR = SYMBOL && SYMBOL.iterator;

var $$iterator = exports.$$iterator = SYMBOL_ITERATOR || '@@iterator';

function isIterable(obj) {
  return !!getIteratorMethod(obj);
}

function isArrayLike(obj) {
  var length = obj != null && obj.length;
  return typeof length === 'number' && length >= 0 && length % 1 === 0;
}

function isCollection(obj) {
  return Object(obj) === obj && (isArrayLike(obj) || isIterable(obj));
}

function getIterator(iterable) {
  var method = getIteratorMethod(iterable);
  if (method) {
    return method.call(iterable);
  }
}

function getIteratorMethod(iterable) {
  if (iterable != null) {
    var method = SYMBOL_ITERATOR && iterable[SYMBOL_ITERATOR] || iterable['@@iterator'];
    if (typeof method === 'function') {
      return method;
    }
  }
}

function createIterator(collection) {
  if (collection != null) {
    var iterator = getIterator(collection);
    if (iterator) {
      return iterator;
    }
    if (isArrayLike(collection)) {
      return new ArrayLikeIterator(collection);
    }
  }
}

function ArrayLikeIterator(obj) {
  this._o = obj;
  this._i = 0;
}

ArrayLikeIterator.prototype[$$iterator] = function () {
  return this;
};

ArrayLikeIterator.prototype.next = function () {
  if (this._o === void 0 || this._i >= this._o.length) {
    this._o = void 0;
    return { value: void 0, done: true };
  }
  return { value: this._o[this._i++], done: false };
};

function forEach(collection, callback, thisArg) {
  if (collection != null) {
    if (typeof collection.forEach === 'function') {
      return collection.forEach(callback, thisArg);
    }
    var i = 0;
    var iterator = getIterator(collection);
    if (iterator) {
      var step;
      while (!(step = iterator.next()).done) {
        callback.call(thisArg, step.value, i++, collection);

        if (i > 9999999) {
          throw new TypeError('Near-infinite iteration.');
        }
      }
    } else if (isArrayLike(collection)) {
      for (; i < collection.length; i++) {
        if (collection.hasOwnProperty(i)) {
          callback.call(thisArg, collection[i], i, collection);
        }
      }
    }
  }
}

var SYMBOL_ASYNC_ITERATOR = SYMBOL && SYMBOL.asyncIterator;

var $$asyncIterator = exports.$$asyncIterator = SYMBOL_ASYNC_ITERATOR || '@@asyncIterator';

function isAsyncIterable(obj) {
  return !!getAsyncIteratorMethod(obj);
}

function getAsyncIterator(asyncIterable) {
  var method = getAsyncIteratorMethod(asyncIterable);
  if (method) {
    return method.call(asyncIterable);
  }
}

function getAsyncIteratorMethod(asyncIterable) {
  if (asyncIterable != null) {
    var method = SYMBOL_ASYNC_ITERATOR && asyncIterable[SYMBOL_ASYNC_ITERATOR] || asyncIterable['@@asyncIterator'];
    if (typeof method === 'function') {
      return method;
    }
  }
}

function createAsyncIterator(source) {
  if (source != null) {
    var asyncIterator = getAsyncIterator(source);
    if (asyncIterator) {
      return asyncIterator;
    }
    var iterator = createIterator(source);
    if (iterator) {
      return new AsyncFromSyncIterator(iterator);
    }
  }
}

function AsyncFromSyncIterator(iterator) {
  this._i = iterator;
}

AsyncFromSyncIterator.prototype[$$asyncIterator] = function () {
  return this;
};

AsyncFromSyncIterator.prototype.next = function () {
  var step = this._i.next();
  return Promise.resolve(step.value).then(function (value) {
    return { value: value, done: step.done };
  });
};

function forAwaitEach(source, callback, thisArg) {
  var asyncIterator = createAsyncIterator(source);
  if (asyncIterator) {
    var i = 0;
    return new Promise(function (resolve, reject) {
      function next() {
        asyncIterator.next().then(function (step) {
          if (!step.done) {
            Promise.resolve(callback.call(thisArg, step.value, i++, source)).then(next).catch(reject);
          } else {
            resolve();
          }

          return null;
        }).catch(reject);

        return null;
      }
      next();
    });
  }
}

