/**
 * @category Services/utils
 * @class ObjectUtils
 * Classe aidant à manipuler des Objets
 */
import {AnyJson} from "~/types/data";

export default class ObjectUtils {
  /**
   * Flatten un objet nested en un objet avec un seul niveau avec des noms de propriétés transformées comme cela 'foo.bar'
   * L'objet passé en paramètre reste inchangé car il est cloné
   *
   * @example  cloneAndFlatten({ a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } }, i: { j: 6 } }, ['i']) => { a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5, i: { j: 6 } } }
   * @param {AnyJson} object
   * @param {Array<string>} excludedProperties
   * @param excludedProperties
   * @return {AnyJson}
   */
  static cloneAndFlatten (object: AnyJson, excludedProperties: Array<string> = []): AnyJson {
    if (typeof object !== 'object') {
      throw new TypeError('Expecting an object parameter')
    }

    return Object.keys(object).reduce(
      (values: AnyJson, name: string) => {
        if (!object.hasOwnProperty(name)) {
          return values
        }

        if (this.isObject(object[name])) {
          if (!excludedProperties.includes(name)) {
            const flatObject = this.cloneAndFlatten(object[name])
            Object.keys(flatObject).forEach((flatObjectKey) => {
              if (!flatObject.hasOwnProperty(flatObjectKey)) { return }
              values[name + '.' + flatObjectKey] = flatObject[flatObjectKey]
            })
          } else {
            values[name] = this.clone(object[name])
          }
        } else {
          values[name] = object[name]
        }
        return values
      }, {}
    )
  };

  /**
   * Transforme un objet flattened en un objet nested. L'objet passé en paramètre reste inchangé
   *
   * @example cloneAndNest({ a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5 } ) => { a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } } }
   * @param {AnyJson} object
   * @return {AnyJson}
   */
  static cloneAndNest (object: AnyJson): AnyJson {
    if (typeof object !== 'object') {
      throw new TypeError('Expecting an object parameter')
    }
    return Object.keys(object).reduce((values, name) => {
      if (!object.hasOwnProperty(name)) {
        return values
      }
      name.split('.').reduce((previous: AnyJson, current: string, index: number, list: Array<string>) => {
        if (previous != null) {
          if (typeof previous[current] === 'undefined') {
            previous[current] = {}
          }
          if (index < (list.length - 1)) {
            return previous[current]
          }
          previous[current] = object[name]
        }
      }, values)
      return values
    }, {})
  }

  /**
   * Teste si le paramètre est un objet
   *
   * @param {AnyJson} value
   * @return {boolean}
   */
  static isObject (value: any): boolean {
    return value !== null &&
      typeof value === 'object' &&
      !Array.isArray(value) &&
      ObjectUtils.prototype.toString.call(value) !== '[object Date]'
  }

  /**
   * Clone l'objet et ses propriétés.
   *
   * @param {ObjectUtils} object
   * @return {ObjectUtils}
   */
  static clone (object: AnyJson): AnyJson {
    return Object.keys(object).reduce((values: AnyJson, name: string) => {
      if (object.hasOwnProperty(name)) {
        values[name] = object[name]
      }
      return values
    }, {})
  }

  /**
   * Trie un objet selon ses clés (par ordre alphanumérique)
   *
   * @example sortObjectsByKey({b:1, d:2, c:3, a:4}) => {a:4, b:1, c:3, d:2}
   * @param toSort
   */
  static sortObjectsByKey (toSort: AnyJson): any {
    if (typeof toSort !== 'object') {
      throw new TypeError('Expecting an object parameter')
    }
    return Object.keys(toSort).sort().reduce(
      (obj:any, key:string) => {
        obj[key] = toSort[key]
        return obj
      },
      {}
    )
  }
}
