/**
 * @Datei obj.js
 * @modul obj
 * /

/**
 * @callback obj:EachCallback
 *
 * @param {Mixed} Wert
 *        Der aktuelle Schlüssel für das Objekt, über das iteriert wird.
 *
 * @param {string} key
 *        Der aktuelle Schlüsselwert für das Objekt, über das iteriert wird
 * /

/**
 * @callback obj:ReduceCallback
 *
 * @param {Mixed} accum
 *        Der Wert, der sich in der Reduzierungsschleife ansammelt.
 *
 * @param {Mixed} Wert
 *        Der aktuelle Schlüssel für das Objekt, über das iteriert wird.
 *
 * @param {string} key
 *        Der aktuelle Schlüsselwert für das Objekt, über das iteriert wird
 *
 * @return {Mixed}
 *         Der neue kumulierte Wert.
 * /
const toString = Object.prototype.toString;

/**
 * Abrufen der Schlüssel eines Objekts
 *
 * @param {Object}
 *        Das Objekt, von dem die Schlüssel abgerufen werden
 *
 * @return {string[]}
 *         Ein Array mit den Schlüsseln des Objekts. Gibt ein leeres Array zurück, wenn die
 *         das übergebene Objekt war ungültig oder hatte keine Schlüssel.
 *
 * @privat
 * /
const keys = function(object) {
  return isObject(object) ? Object.keys(object) : [];
};

/**
 * Array-ähnliche Iteration für Objekte.
 *
 * @param {Object} object
 *        Das Objekt, über das iteriert werden soll
 *
 * @param {obj:EachCallback} fn
 *        Die Callback-Funktion, die für jeden Schlüssel des Objekts aufgerufen wird.
 * /
export function each(object, fn) {
  keys(object).forEach(key => fn(object[key], key));
}

/**
 * Array-ähnliche Reduktion für Objekte.
 *
 * @param {Object} object
 *        Das Objekt, das Sie reduzieren möchten.
 *
 * @param {Funktion} fn
 *         Eine Callback-Funktion, die für jeden Schlüssel des Objekts aufgerufen wird. Es
 *         erhält den akkumulierten Wert und den Wert und den Schlüssel für jeden Durchlauf
 *         als Argumente.
 *
 * @param {Mixed} [initial = 0]
 *        Ausgangswert
 *
 * @return {Mixed}
 *         Der endgültige kumulierte Wert.
 * /
export function reduce(object, fn, initial = 0) {
  return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
}

/**
 * Objekt.assign-Stil Objekt untiefe Zusammenführung/Erweiterung.
 *
 * @param {Object} target
 * @param {Object} ...sources
 * @return {Object}
 * /
export function assign(target, ...sources) {
  if (Object.assign) {
    return Object.assign(Ziel, ...Quellen);
  }

  sources.forEach(source => {
    if (!Quelle) {
      rückkehr;
    }

    each(Quelle, (Wert, Schlüssel) => {
      ziel[Schlüssel] = Wert;
    });
  });

  ziel zurückgeben;
}

/**
 * Gibt zurück, ob ein Wert ein Objekt irgendeiner Art ist - einschließlich DOM-Knoten,
 * arrays, reguläre Ausdrücke, usw. Funktioniert jedoch nicht.
 *
 * Dies vermeidet das Problem, dass die Verwendung von `typeof` auf einen `null`-Wert
 * ergibt "Objekt".
 *
 * @param {Object} value
 * @return {boolean}
 * /
export function isObject(value) {
  return !!value && typeof value === 'object';
}

/**
 * Gibt zurück, ob ein Objekt ein "einfaches" Objekt zu sein scheint - das heißt, ein
 * direkte Instanz von `Object`.
 *
 * @param {Object} value
 * @return {boolean}
 * /
export function isPlain(value) {
  return isObject(Wert) &&
    toString.call(value) === '[object Object]' &&
    value.constructor === Object;
}