/**
 * @file obj.js
 * @module obj
 */

/**
 * @callback obj:EachCallback
 *
 * @param {Mixed} value
 *        La clé actuelle de l'objet sur lequel l'itération est effectuée.
 *
 * @param {string} key
 *        La valeur-clé actuelle de l'objet qui est en cours d'itération
 */

/**
 * @callback obj:ReduceCallback
 *
 * @param {Mixed} accum
 *        La valeur qui s'accumule au cours de la boucle de réduction.
 *
 * @param {Mixed} value
 *        La clé actuelle de l'objet sur lequel l'itération est effectuée.
 *
 * @param {string} key
 *        La valeur-clé actuelle de l'objet qui est en cours d'itération
 *
 * @return {Mixed}
 *         La nouvelle valeur accumulée.
 */
const toString = Object.prototype.toString ;

/**
 * Obtenir les clés d'un objet
 *
 * @param {Objet}
 *        L'objet à partir duquel les clés sont obtenues
 *
 * @return {string[]}
 *         Un tableau des clés de l'objet. Renvoie un tableau vide si le
 *         transmis n'était pas valide ou n'avait pas de clés.
 *
 * @private
 */
const keys = function(object) {
  return isObject(object) ? Object.keys(object) : [] ;
};

/**
 * Itération de type tableau pour les objets.
 *
 * @param {Object} object
 *        L'objet sur lequel itérer
 *
 * @param {obj:EachCallback} fn
 *        La fonction de rappel qui est appelée pour chaque clé de l'objet.
 */
export function each(object, fn) {
  keys(object).forEach(key => fn(object[key], key)) ;
}

/**
 * Réduction de type tableau pour les objets.
 *
 * @param {Object} object
 *        L'objet que vous souhaitez réduire.
 *
 * @param {Fonction} fn
 *         Une fonction de rappel qui est appelée pour chaque clé de l'objet. Il
 *         reçoit la valeur accumulée et la valeur par itération et la clé
 *         comme arguments.
 *
 * @param {Mixed} [initial = 0]
 *        Valeur de départ
 *
 * @return {Mixed}
 *         La valeur finale accumulée.
 */
export function reduce(object, fn, initial = 0) {
  return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial) ;
}

/**
 * Fusion/extension superficielle d'objets dans le style Object.assign.
 *
 * @param {Objet} target
 * @param {Objet} ...sources
 * @return {Object}
 */
export function assign(target, ...sources) {
  if (Object.assign) {
    return Object.assign(target, ...sources) ;
  }

  sources.forEach(source => {
    if (!source) {
      retour ;
    }

    each(source, (value, key) => {
      cible[clé] = valeur ;
    }) ;
  }) ;

  retourner la cible ;
}

/**
 * Indique si une valeur est un objet de n'importe quel type, y compris les nœuds du DOM,
 * tableaux, expressions régulières, etc. Mais pas de fonctions.
 *
 * Cela permet d'éviter le piège de l'utilisation de `typeof` sur une valeur `null`
 * aboutit à ``objet''.
 *
 * @param {Objet} valeur
 * @return {boolean}
 */
export function isObject(value) {
  return !!value && typeof value === 'object' ;
}

/**
 * Indique si un objet semble être un objet "ordinaire", c'est-à-dire un objet de type
 * instance directe de `Object`.
 *
 * @param {Objet} valeur
 * @return {boolean}
 */
export function isPlain(value) {
  return isObject(valeur) &&
    toString.call(value) === '[object Object] &&
    value.constructor === Object ;
}