/**
 * @module Mel/UsefulFunctions
 */

export {
  isNullOrUndefined,
  isArrayLike,
  Classes,
  toHex,
  isDecimal,
  capitalize,
  getRelativePos,
  isAsync,
};

/**
 * Vérivie si un nombre est un nombre entier ou décimal.
 * @param {number} number
 * @returns {boolean} Si vrai, alors il s'agit d'un nombre décimal.
 * @example const state = isDecimal(value) ? 1 : 0;
 */
function isDecimal(number) {
  return ~~number !== number;
}

/**
 * Renvoie vrai si la variable vaut `null` ou `undefined`.
 * @param {?*} item Variable à tester
 * @returns {boolean}
 */
function isNullOrUndefined(item) {
  return item === null || item === undefined;
}

/**
 * Vérifie si une varible est un tableau ou quelque chose qui y ressemble
 * @param {*} item
 * @returns {bool}
 */
function isArrayLike(item) {
  return (
    !!item &&
    typeof item === 'object' &&
    // eslint-disable-next-line no-prototype-builtins
    item.hasOwnProperty('length') &&
    typeof item.length === 'number' &&
    item.length > 0 &&
    item.length - 1 in item
  );
}

/**
 * Change un nombre en base 10 en hexadécimal
 * @param {number} number
 * @returns {string} Hexadecimal number
 */
function toHex(number) {
  return number.toString(16);
}

/**
 * @typedef Class
 * Indique qu'il s'agit d'un objet qui est une classe. Le mot clé "new" peut être utiliser.
 */

/**
 * @typedef Classes
 * Indique qu'il s'agit d'un mélange de plusieurs classes. Le mot clé "new" peut être utiliser.
 */

/**
 * Permet de créer une "fusion" de deux classes.
 *
 * Cela permet d'avoir une classe qui hérite de 2 classe.
 * @param {Class} baseClass Classe de base
 * @param  {...Classes} mixins Autres classes
 * @returns {}
 */
const Classes = (baseClass, ...mixins) => {
  class base extends baseClass {
    constructor(...args) {
      super(...args);
      mixins.forEach((mixin) => {
        copyProps(this, new mixin());
      });
    }
  }
  let copyProps = (target, source) => {
    // this function copies all properties and symbols, filtering out some special ones
    Object.getOwnPropertyNames(source)
      .concat(Object.getOwnPropertySymbols(source))
      .forEach((prop) => {
        if (
          !prop.match(
            /^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/,
          )
        )
          Object.defineProperty(
            target,
            prop,
            Object.getOwnPropertyDescriptor(source, prop),
          );
      });
  };
  mixins.forEach((mixin) => {
    // outside contructor() to allow aggregation(A,B,C).staticFunction() to be called etc.
    copyProps(base.prototype, mixin.prototype);
    copyProps(base, mixin);
  });
  return base;
};

function capitalize(s) {
  if (typeof s !== 'string') return '';

  s = s.toLowerCase();
  return s.charAt(0).toUpperCase() + s.slice(1);
}

function getRelativePos(elm) {
  let pPos = elm.parentNode.getBoundingClientRect(), // parent pos
    cPos = elm.getBoundingClientRect(), // target pos
    pos = {};

  (pos.top = cPos.top - pPos.top + elm.parentNode.scrollTop),
    (pos.right = cPos.right - pPos.right),
    (pos.bottom = cPos.bottom - pPos.bottom),
    (pos.left = cPos.left - pPos.left);

  return pos;
}

function isAsync(func) {
  return func.constructor.name === 'AsyncFunction';
}