"use strict";

var _assign = require("object-assign");

/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 */
var KIND_KEYS = 'keys';
var KIND_VALUES = 'values';
var KIND_ENTRIES = 'entries';
/**
 * Specific Array iterators.
 */

var ArrayIterators = function () {
  var hasNative = hasNativeIterator(Array);
  var ArrayIterator;

  if (!hasNative) {
    ArrayIterator =
    /*#__PURE__*/
    function () {
      // 22.1.5.1 CreateArrayIterator Abstract Operation
      function ArrayIterator(array, kind) {
        this._iteratedObject = array;
        this._kind = kind;
        this._nextIndex = 0;
      } // 22.1.5.2.1 %ArrayIteratorPrototype%.next()


      var _proto = ArrayIterator.prototype;

      _proto.next = function next() {
        if (this._iteratedObject == null) {
          return {
            value: undefined,
            done: true
          };
        }

        var array = this._iteratedObject;
        var len = this._iteratedObject.length;
        var index = this._nextIndex;
        var kind = this._kind;

        if (index >= len) {
          this._iteratedObject = undefined;
          return {
            value: undefined,
            done: true
          };
        }

        this._nextIndex = index + 1;

        if (kind === KIND_KEYS) {
          return {
            value: index,
            done: false
          };
        } else if (kind === KIND_VALUES) {
          return {
            value: array[index],
            done: false
          };
        } else if (kind === KIND_ENTRIES) {
          return {
            value: [index, array[index]],
            done: false
          };
        }
      }; // 22.1.5.2.2 %ArrayIteratorPrototype%[@@iterator]()


      _proto[Symbol.iterator] = function () {
        return this;
      };

      return ArrayIterator;
    }();
  }

  return {
    keys: hasNative ? function (array) {
      return array.keys();
    } : function (array) {
      return new ArrayIterator(array, KIND_KEYS);
    },
    values: hasNative ? function (array) {
      return array.values();
    } : function (array) {
      return new ArrayIterator(array, KIND_VALUES);
    },
    entries: hasNative ? function (array) {
      return array.entries();
    } : function (array) {
      return new ArrayIterator(array, KIND_ENTRIES);
    }
  };
}(); // -----------------------------------------------------------------

/**
 * Specific String iterators.
 */


var StringIterators = function () {
  var hasNative = hasNativeIterator(String);
  var StringIterator;

  if (!hasNative) {
    StringIterator =
    /*#__PURE__*/
    function () {
      // 21.1.5.1 CreateStringIterator Abstract Operation
      function StringIterator(string) {
        this._iteratedString = string;
        this._nextIndex = 0;
      } // 21.1.5.2.1 %StringIteratorPrototype%.next()


      var _proto2 = StringIterator.prototype;

      _proto2.next = function next() {
        if (this._iteratedString == null) {
          return {
            value: undefined,
            done: true
          };
        }

        var index = this._nextIndex;
        var s = this._iteratedString;
        var len = s.length;

        if (index >= len) {
          this._iteratedString = undefined;
          return {
            value: undefined,
            done: true
          };
        }

        var ret;
        var first = s.charCodeAt(index);

        if (first < 0xD800 || first > 0xDBFF || index + 1 === len) {
          ret = s[index];
        } else {
          var second = s.charCodeAt(index + 1);

          if (second < 0xDC00 || second > 0xDFFF) {
            ret = s[index];
          } else {
            ret = s[index] + s[index + 1];
          }
        }

        this._nextIndex = index + ret.length;
        return {
          value: ret,
          done: false
        };
      }; // 21.1.5.2.2 %StringIteratorPrototype%[@@iterator]()


      _proto2[Symbol.iterator] = function () {
        return this;
      };

      return StringIterator;
    }();
  }

  return {
    keys: function keys() {
      throw TypeError("Strings default iterator doesn't implement keys.");
    },
    values: hasNative ? function (string) {
      return string[Symbol.iterator]();
    } : function (string) {
      return new StringIterator(string);
    },
    entries: function entries() {
      throw TypeError("Strings default iterator doesn't implement entries.");
    }
  };
}();

function hasNativeIterator(classObject) {
  return typeof classObject.prototype[Symbol.iterator] === 'function' && typeof classObject.prototype.values === 'function' && typeof classObject.prototype.keys === 'function' && typeof classObject.prototype.entries === 'function';
} // -----------------------------------------------------------------

/**
 * Generic object iterator.
 */


var ObjectIterator =
/*#__PURE__*/
function () {
  function ObjectIterator(object, kind) {
    this._iteratedObject = object;
    this._kind = kind;
    this._keys = Object.keys(object);
    this._nextIndex = 0;
  }

  var _proto3 = ObjectIterator.prototype;

  _proto3.next = function next() {
    var len = this._keys.length;
    var index = this._nextIndex;
    var kind = this._kind;
    var key = this._keys[index];

    if (index >= len) {
      this._iteratedObject = undefined;
      return {
        value: undefined,
        done: true
      };
    }

    this._nextIndex = index + 1;

    if (kind === KIND_KEYS) {
      return {
        value: key,
        done: false
      };
    } else if (kind === KIND_VALUES) {
      return {
        value: this._iteratedObject[key],
        done: false
      };
    } else if (kind === KIND_ENTRIES) {
      return {
        value: [key, this._iteratedObject[key]],
        done: false
      };
    }
  };

  _proto3[Symbol.iterator] = function () {
    return this;
  };

  return ObjectIterator;
}();
/**
 * Generic object iterator, iterates over all own enumerable
 * properties. Used only if if no specific iterator is available,
 * and object don't implement iterator protocol.
 */


var GenericIterators = {
  keys: function keys(object) {
    return new ObjectIterator(object, KIND_KEYS);
  },
  values: function values(object) {
    return new ObjectIterator(object, KIND_VALUES);
  },
  entries: function entries(object) {
    return new ObjectIterator(object, KIND_ENTRIES);
  }
}; // -----------------------------------------------------------------

/**
 * Main iterator function. Returns default iterator based
 * on the class of an instance.
 */

function enumerate(object, kind) {
  // First check specific iterators.
  if (typeof object === 'string') {
    return StringIterators[kind || KIND_VALUES](object);
  } else if (Array.isArray(object)) {
    return ArrayIterators[kind || KIND_VALUES](object); // Then see if an object implements own.
  } else if (object[Symbol.iterator]) {
    return object[Symbol.iterator](); // And fallback to generic with entries.
  } else {
    return GenericIterators[kind || KIND_ENTRIES](object);
  }
}

_assign(enumerate, {
  /**
   * Export constants
   */
  KIND_KEYS: KIND_KEYS,
  KIND_VALUES: KIND_VALUES,
  KIND_ENTRIES: KIND_ENTRIES,

  /**
   * Convenient explicit iterators for special kinds.
   */
  keys: function keys(object) {
    return enumerate(object, KIND_KEYS);
  },
  values: function values(object) {
    return enumerate(object, KIND_VALUES);
  },
  entries: function entries(object) {
    return enumerate(object, KIND_ENTRIES);
  },
  generic: GenericIterators.entries
});

module.exports = enumerate;