modules/outilsMathjs.js

import { context } from './context.js'
import { texNombre2, obtenirListeFacteursPremiers } from './outils.js'
import { all, create } from 'mathjs'
import { Node, Negative, solveEquation, simplifyExpression, factor } from 'mathsteps'
import { getNewChangeNodes } from './Change.js'
import Decimal from 'decimal.js'

const math = create(all)

function searchFirstNode (node, op) {
  if (node.type === 'OperatorNode') {
    return searchFirstNode(node.args[0], node.op)
  } else if (node.type === 'ParenthesisNode') {
    return searchFirstNode(node.content, node.op)
  } else {
    return { node: node, op: op }
  }
}

function searchLastNode (node, op) {
  if (node.type === 'OperatorNode') {
    return searchLastNode(node.args[node.args.length - 1], node.op)
  } else if (node.type === 'ParenthesisNode') {
    return searchLastNode(node.content, node.op)
  } else {
    return { node: node, op: op }
  }
}

/**
 * Assignation de variables
 * @param {string} expression
 * @param {Object} variables
 * @returns {string}
 */
export function assignVariables (expression, variables) {
  const node = math.parse(expression).transform(
    function (node, path, parent) {
      if (node.isSymbolNode && variables[node.name] !== undefined) {
        return math.parse(variables[node.name].toString())
      } else {
        return node
      }
    }
  )
  return node.toString({ parenthesis: 'keep' })
}

function transformNode (node, parent, oldNode, params = { suppr1: true, suppr0: true, supprPlusMoins: true }) {
  params = Object.assign({ suppr1: true, suppr0: true, supprPlusMoins: true }, params)
  if (parent === null && node.isParenthesisNode) node = node.content
  if (oldNode === undefined || node.toString() !== oldNode.toString()) {
    oldNode = node.clone()
    /*
    * Retirer les parenthèses au dividende et diviseur d'un quotient
    * (n1)/(n2) devient n1/n2
    */
    if (node.isOperatorNode && node.op === '/') {
      if (node.args[0].isParenthesisNode) {
        node.args[0] = node.args[0].content
      }
      if (node.args[1].isParenthesisNode) {
        node.args[1] = node.args[1].content
      }
    }
    /*
    * Transformer -2/n en -(2/n)
    * Ne touche pas à (-2)/(-n)
    */
    if (node.isOperatorNode && node.op === '/') {
      if (
        node.args[0].type === 'OperatorNode' &&
        node.args[0].fn === 'unaryMinus' &&
        (node.args[0].args[0].type === 'ConstantNode' || node.args[0].args[0].type === 'SymbolNode') &&
        !(
          node.args[1].type === 'OperatorNode' &&
          node.args[1].fn === 'unaryMinus'
        )
      ) {
        let frac = Node.Creator.operator('/', [node.args[0].args[0], node.args[1]])
        frac = Node.Creator.parenthesis(frac)
        node = math.parse('-' + frac.toString())
      }
    }
    /* (Flatten divisions comme dans Mathsteps)
    * Transformer (1/2*3)/4 en 1/2*(3/4)
    */
    if (node.isOperatorNode && node.op === '/') {
      if (node.args[0].isOperatorNode && node.args[0].op === '*') {
        if (node.args[0].args[0].isOperatorNode && node.args[0].args[0].op === '/') {
          const frac1 = Node.Creator.operator('/', node.args[0].args[0].args)
          const frac2 = Node.Creator.operator('/', [node.args[0].args[1], node.args[1]])
          node = Node.Creator.operator('*', [frac1, frac2])
        }
      }
    }
    /*
    * Transformer (n1)+(n2) en n1+n2
    * Utile si n1 et/ou n2 sont des unaryMinus ou des fractions
    */
    if (node.isOperatorNode && node.op === '+') {
      if (params.supprPlusMoins) {
        if (node.args[0].isParenthesisNode) node.args[0] = node.args[0].content
        if (node.args.length > 1 && node.args[1].isParenthesisNode) node.args[1] = node.args[1].content
      } else {
        if (node.args[0].isParenthesisNode && node.args[0].content.toString()[0] !== '-' && node.args[0].content.toString()[0] !== '+') node.args[0] = node.args[0].content
        if (node.args.length > 1 && node.args[1].isParenthesisNode && node.args[1].content.toString()[0] !== '-' && node.args[1].content.toString()[0] !== '+') node.args[1] = node.args[1].content
      }
    }
    /*
    * Transformer n+0 en n
    */
    if (params.suppr0 && node.isOperatorNode && node.op === '+' && node.fn !== 'unaryPlus') {
      if (node.args[1].toString() === '0') {
        node = node.args[0]
      }
    }
    /*
    * Transformer n1+0*n2 en n1
    */
    if (params.suppr0 && node.isOperatorNode && node.op === '+' && node.fn !== 'unaryPlus') {
      if (
        node.args[1].isOperatorNode &&
        node.args[1].op === '*' &&
        (
          node.args[1].args[0].toString() === '0'
        )
      ) {
        node = node.args[0]
      }
    }
    /*
    * Transformer 1*n en n et -1*n en -n
    */
    if (params.suppr1 && node.isOperatorNode && node.op === '*' && searchFirstNode(node.args[1]).node.type !== 'ConstantNode') {
      if (node.args[0].toString() === '1') {
        node = node.args[1]
      } else if (node.args[0].toString() === '-1') {
        node = math.parse('-' + node.args[1].toString())
      }
    }
    /*
    * Transformer n/1 en n et n/-1 en -n
    */
    if (params.suppr1 && node.isOperatorNode && node.op === '/') {
      if (node.args[1].toString() === '1') {
        node = node.args[0]
      } else if (node.args[1].toString() === '-1') {
        node = math.parse('-' + node.args[0].toString())
      }
    }
    /*
    * Transformer (n1)-n2 en n1-n2
    * Transformer --c en -(-c)
    * transformer n1-(n2/n3) en n1-n2/n3
    */
    if (node.isOperatorNode && node.op === '-') { // Enlève les parenthèses au premier terme d'une soustraction et au second sous condition d'une /
      if (node.args[0].isParenthesisNode) node.args[0] = node.args[0].content
      if (node.args.length === 1 && node.args[0].isConstantNode && node.args[0].value < 0) { // Pour corriger --3 en -(-3)
        node.args[0] = Node.Creator.parenthesis(node.args[0])
      }
      if (node.args.length === 2 && node.args[1].isConstantNode && node.args[1].value < 0) { // Pour corriger 7--3 en 7-(-3)
        node.args[1] = Node.Creator.parenthesis(node.args[1])
      }
      if (node.args.length === 2 && node.args[1].isParenthesisNode && node.args[1].content.toString()[0] !== '-') node.args[1] = node.args[1].content
      /*
      * Code à simplifier ?
      */
      if (
        node.fn !== 'unaryMinus' && // On vérifie si c'est une vraie soustraction (avec deux termes)
    node.args[1].isParenthesisNode && // On vérifie que le second terme possède une parenthèse
    node.args[1].content.isOperatorNode && // On vérifie que le second terme contient une opération
    (
      node.args[1].content.op === '/' || // On teste si cette opération est une division
      (
        node.args[1].content.op === '*' && // On teste si c'est une multiplication
        (
          !node.args[1].content.args[0].isOperatorNode || // Si le premier facteur n'est pas une opération
          (
            node.args[1].content.args[0].isOperatorNode && // Ou si c'est une opération
            node.args[1].content.args[0].fn !== 'unaryMinus' // mais que le premier argument n'est pas -blabla
          )
        )
      )
    )
      ) node.args[1] = node.args[1].content
    }
    if (node.isOperatorNode && node.op === '*') { // Enlève les parenthèses aux deux facteurs d'une multiplication
      if (node.args[0].isParenthesisNode && // On cherche à l'intérieur d'une parenthèse
    (
      !node.args[0].content.isOperatorNode || // Il ne faut pas d'opération
      (node.args[0].content.isOperatorNode && node.args[0].content.op === '/') || // ou alors une divisions
      (node.args[0].content.isOperatorNode && node.args[0].content.fn === 'unaryMinus' && params.supprPlusMoins) // ou alors un -n
    )
      ) { // Si l'une des conditions est vérifiée alors :
        node.args[0] = node.args[0].content // on enlève la parenthèse
        node.implicit = false // on fait en sorte que la multiplication soit visible
      }
      if (node.args[1].isParenthesisNode &&
    (
      !node.args[1].content.isOperatorNode ||
      (
        node.args[1].content.isOperatorNode &&
        node.args[1].content.op === '/' &&
        !(
          node.args[1].content.args[0].type === 'OperatorNode' &&
          node.args[1].content.args[0].fn === 'unaryMinus' &&
          node.args[1].content.args[0].args[0].type === 'ConstantNode' &&
          !(node.args[1].content.args[1].type === 'OperatorNode' && node.args[1].content.args[1].fn === 'unaryMinus')
        )
      )
    )) {
        node.args[1] = node.args[1].content
        node.implicit = false
      }
    }
    if (node.type === 'OperatorNode' && node.op === '*') { // Corrige n*-c en n*(-c)
      if (node.args[1].toString()[0] === '-') {
        node.args[1] = Node.Creator.parenthesis(node.args[1])
      }
    }
    // Peut-être faut-il mettre à jour le mathjs de mathsteps car il semble que le code suivant ne fonctionne pas
    // dans mathsteps lorsqu'il est placé dans print.js de mathsteps
    // alors qu'il fonctionne avec la version mathjs de mathalea
    if (node.isOperatorNode && node.op === '*') { // Multiplication implicite 2*x devient 2x et 2*(x+3) devient 2(x+3)
      if ((node.args[1].isParenthesisNode || node.args[1].isSymbolNode) && !(searchLastNode(node.args[0]).node.isSymbolNode)) node.implicit = true
      if (node.args[1].isOperatorNode && node.args[1].op === '^' && node.args[1].args[0].isSymbolNode) node.implicit = true
    }
    if (node.isOperatorNode && node.op === '*') { // Multiplication explicite x*2 ou x*2/3
      if (node.args[1].isConstantNode) node.implicit = false
      if (node.args[1].isOperatorNode && node.args[1].args[0].isConstantNode) node.implicit = false
      if (node.args[1].isOperatorNode && node.args[1].op === '/') node.implicit = false
      if (node.args[1].isOperatorNode && node.args[1].args[0].isOperatorNode && node.args[1].args[0].op === '/') node.implicit = false
      if (node.args[1].isParenthesisNode && node.args[1].content.isOperatorNode && node.args[1].content.fn === 'unaryMinus') node.implicit = false
    }
    if (
      node.isParenthesisNode &&
      node.content.isOperatorNode &&
        (
          (node.content.op === '*' || node.content.op === '^') &&
          (node.content.toString()[0] !== '-')
        )
    ) {
      node = node.content
    }
    if (node.isParenthesisNode && node.content.isOperatorNode && node.content.op === '/') node = node.content
    if (node.isOperatorNode && node.fn === 'unaryMinus' && node.args[0].isParenthesisNode && node.args[0].content.isOperatorNode && node.args[0].content.op === '*') node.args[0] = node.args[0].content
    if (node.isOperatorNode && node.fn === 'unaryMinus' && node.args[0].isOperatorNode && node.args[0].op === '*') node = Node.Creator.operator('*', [Negative.negate(node.args[0].args[0]), node.args[0].args[1]])
    // n(c*n) = n*(c*n) Je ne sais plus pourquoi !
    if (
      node.isOperatorNode && node.op === '*') {
      const firstNode = searchFirstNode(node.args[1])
      if (
        firstNode.node.type === 'ConstantNode' &&
        firstNode.op === '*'
      ) { node.implicit = false }
    }
    return transformNode(node, parent, oldNode, params)
  } else {
    return node
  }
}

/* Corrige le problème lié au conversions de (-3)² en -3² si on passe du format mathsteps au format mathjs
*/
function correctifNodeMathsteps (node) {
  node = node.transform(
    function (node, path, parent) {
      if (node.type === 'ConstantNode') {
        return math.parse(node.value.toString())
      }
      return node
    }
  )
  return node
}

/**
 * Retourne le format Latex d'un node mathjs ou mathsteps ou d'une expression ascii
 * Supprime les parenthèses inutiles, les 1 et les 0 inutiles, transforme les +- en -
 * @param {string|Object} node // Chaine de caractères décrivant une expression mathématique, une équation, une inéquation ou bien node mathjs
 * @param {Object} params // Paramètres
 * @returns {string} // Format latex
 * @example
 * toTex('3/2+4*x') -> \dfrac{3}{2}+4x
 * toTex('1*x+-3=6*x+0') -> x-3=6x
 * toTex('-3/4') -> -\dfrac{3}{4}
 * toTex('OA/OM=OB/ON',{OA: 1.2, OM: 1.5, OB: 1.7}) -> \dfrac{1{.}2}{1{.}5}=\dfrac{1{.}7}{OB}
 */
export function toTex (node, params = { suppr1: true, suppr0: true, supprPlusMoins: true, variables: undefined }) {
  params = Object.assign({ suppr1: true, suppr0: true, supprPlusMoins: true }, params)
  // On commence par convertir l'expression en arbre au format mathjs
  let comparator
  let sides = []
  const comparators = ['=', '<', '>', '<=', '>=']
  if (typeof node === 'string') {
    for (let i = 0; i < comparators.length; i++) {
      sides = node.split(comparators[i])
      if (sides.length > 1) {
        comparator = comparators[i]
      }
    }
    if (comparator !== undefined) {
      sides = node.split(comparator)
    } else {
      if (params.variables === undefined) {
        node = math.parse(node)
      } else {
        node = math.parse(aleaExpression(node, params.variables))
      }
    }
  } else {
    // Le format mathsteps s'il est en entrée ne permet pas a priori de modifier les implicit
    // De plus comme les multiplications peuvent avoir 3 ou plus de facteurs dans mathsteps
    // et que le paramètre implicit s'applique alors à tous les facteurs
    // cela devient impossible à traiter pour 4*x*(-5) qui donnerait 4x-5 avec implicit = true.
    // Mais il faut un pré-traitement car sinon le passage de mathsteps à mathjs
    // transforme les (-3)^2 en -3^2
    node = correctifNodeMathsteps(node) // Convertit d'abord tous les ConstantNode au format mathjs
    // node = math.parse(node.toString({ parenthesis: 'all' })) // Permet d'utiliser correctement les implicit
    node = math.parse(math.format(node, { notation: 'fixed' })) // Permet d'utiliser correctement les implicit
  }
  /* if (sides.length === 2) {
    const leftSide = toTex(sides[0], params)
    const rightSide = toTex(sides[1], params)
    return leftSide + comparator + rightSide
  } */
  if (sides.length > 1) {
    const members = []
    for (let i = 0; i < sides.length; i++) {
      members.push(toTex(sides[i], params))
    }
    return members.join(comparator.replaceAll('>=', '\\geqslant').replaceAll('<=', '\\leqslant'))
  }
  let nodeClone
  do { // À étudier, pour 79 et 85 et 50 cette boucle doit être maintenue
    nodeClone = node.cloneDeep() // Vérifier le fonctionnement de .clone() et .cloneDeep() (peut-être y a-t-il un problème avec implicit avec cloneDeep())
    node = node.transform(
      function (node, path, parent) {
        node = transformNode(node, parent, undefined, params)
        return node
      }
    )
  } while (node.toString() !== nodeClone.toString())

  let nodeTex = node.toTex({ implicit: 'hide', parenthesis: 'keep', notation: 'fixed' }).replaceAll('\\cdot', '\\times').replaceAll('.', '{,}').replaceAll('\\frac', '\\dfrac')

  nodeTex = nodeTex.replace(/\s*?\+\s*?-\s*?/g, ' - ')
  // Mathjs ajoute de manière non contrôlée des \mathrm pour certaines ConstantNode
  // En attendant de comprendre on les enlève (au risque d'avoir les {} restantes)
  nodeTex = nodeTex.replaceAll('\\mathrm', '')
  // Exclure les ~
  nodeTex = nodeTex.replaceAll('~', '')
  if (node.isConstantNode && node.value === undefined) nodeTex = ''
  return nodeTex
}

export function toString (node, params = { suppr1: true, suppr0: true, supprPlusMoins: true, variables: undefined }) {
  params = Object.assign({ suppr1: true, suppr0: true, supprPlusMoins: true }, params)
  // On commence par convertir l'expression en arbre au format mathjs
  let comparator
  let sides = []
  const comparators = ['=', '<', '>', '<=', '>=']
  if (typeof node === 'string') {
    for (let i = 0; i < comparators.length; i++) {
      sides = node.split(comparators[i])
      if (sides.length > 1) {
        comparator = comparators[i]
      }
    }
    if (comparator !== undefined) {
      sides = node.split(comparator)
    } else {
      if (params.variables === undefined) {
        node = math.parse(node.toString())
      } else {
        node = math.parse(aleaExpression(node, params.variables))
      }
    }
  } else {
    // Le format mathsteps s'il est en entrée ne permet pas a priori de modifier les implicit
    // De plus comme les multiplications peuvent avoir 3 ou plus de facteurs dans mathsteps
    // et que le paramètre implicit s'applique alors à tous les facteurs
    // cela devient impossible à traiter pour 4*x*(-5) qui donnerait 4x-5 avec implicit = true.
    // Mais il faut un pré-traitement car sinon le passage de mathsteps à mathjs
    // transforme les (-3)^2 en -3^2
    node = correctifNodeMathsteps(node) // Convertit d'abord tous les ConstantNode au format mathjs
    node = math.parse(node.toString({ parenthesis: 'all' })) // Permet d'utiliser correctement les implicit
  }
  /* if (sides.length === 2) {
    const leftSide = toTex(sides[0], params)
    const rightSide = toTex(sides[1], params)
    return leftSide + comparator + rightSide
  } */
  if (sides.length > 1) {
    const members = []
    for (let i = 0; i < sides.length; i++) {
      members.push(toTex(sides[i], params))
    }
    return members.join(comparator.replaceAll('>=', '\\geqslant').replaceAll('<=', '\\leqslant'))
  }
  let nodeClone
  do { // À étudier, pour 79 et 85 et 50 cette boucle doit être maintenue
    nodeClone = node.cloneDeep() // Vérifier le fonctionnement de .clone() et .cloneDeep() (peut-être y a-t-il un problème avec implicit avec cloneDeep())
    node = node.transform(
      function (node, path, parent) {
        node = transformNode(node, parent, undefined, params)
        return node
      }
    )
  } while (node.toString() !== nodeClone.toString())

  // if (node.isConstantNode && node.value === undefined) nodeTex = ''
  return node.toString({ implicit: 'show', parenthesis: 'keep' }).replace(/\s*?\+\s*?-\s*?/g, ' - ')
}

export function expressionLitterale (expression = '(a*x+b)*(c*x-d)', assignations = { a: 1, b: 2, c: 3, d: -6 }) {
  // Ne pas oublier le signe de la multiplication
  return math.simplify(expression, [{ l: '1*n', r: 'n' }, { l: '-1*n', r: '-n' }, { l: 'n/1', r: 'n' }, { l: 'c/c', r: '1' }, { l: '0*v', r: '0' }, { l: '0+v', r: 'v' }], assignations)
}

export function aleaExpression (expression = '(a*x+b)*(c*x-d)', assignations = { a: 1, b: 2, c: 3, d: -6 }) {
  // const assignationsDecimales = Object.assign({}, assignations)
  const assignationsDecimales = Object.assign({}, aleaVariables(assignations))
  for (const v of Object.keys(assignationsDecimales)) {
    if (typeof assignationsDecimales[v] !== 'number') {
      assignationsDecimales[v] = assignationsDecimales[v].valueOf()
    }
  }
  return assignVariables(expression, assignationsDecimales).toString({ parenthesis: 'keep' })
  // return math.simplify(expression, [], assignationsDecimales).toString({ parenthesis: 'all' })
}

/**
 * @description Retourne des valeurs aléatoires sous certaines contraintes données.
 * Les calculs se font si possible avec mathjs au format fraction
 * @param {Object} variables // Propriété réservée : test
 * @param {Object} params // valueOf à true pour avoir les valeurs décimales, format à true pour appliquer texNombre2
 * // type à 'decimal' et valueOf à true pour obtenir des instances de Decimal()
 * @returns {Object}
 * @see {@link https://mathjs.org/docs/expressions/syntax.html|Mathjs}
 * @see {@link https://coopmaths.fr/documentation/tutorial-Outils_Mathjs.html|Mathjs}
 * @example
 * aleaVariable({a: true}, {valueOf: true}) --> {a: -3} // Génère un entier non nul entre -10 et 10
 * aleaVariable({a: true, b: true}, {valueOf: true}) --> {a: 5, b: -7}
 * aleaVariable({a: false, b: false}, {valueOf: true}) --> {a: 4, b: 1} // false => entier entre 1 et 10
 * aleaVariable({a: true, b: true, test: 'a>b'}, {valueOf: true}) --> {a: 3, b: 1}
 * aleaVariable({a: true, b: true, test: 'a+b>2'}, {valueOf: true}) --> {a: 10, b: -6}
 * aleaVariables({a: true}) --> {a: Fraction} // Fraction est un objet de mathjs
 * @author Frédéric PIOU
 */
export function aleaVariables (variables = { a: true, b: true, c: true, d: true }, params = { valueOf: true, format: false, type: 'number' }) {
  // Conservation de la graine aléatoire
  math.config({ randomSeed: context.graine })
  // Placer dans cet objet chacune des variables après calcul
  const assignations = {}
  // Un compteur pour vérifier que les contraintes ne sont pas excessives
  let cpt = 0
  // Le test pour vérifier que les contraintes sont respectées
  // Remarque : Il serait plus pratique de pouvoir écrire le test en plusieurs lignes
  let test = true
  do { // Une boucle tant que les contraintes ne sont pas vérifiées et tant qu'on ne dépasse pas 1000 essais.
    cpt++
    for (const v of Object.keys(variables)) { // On parcourt chaque variable
      switch (typeof variables[v]) {
        case 'object':
          break
        case 'boolean': // On génère un nombre aléatoire non nul entre 1 et 10 si false et entre -10 et 10 si true
          assignations[v] = math.fraction( // On contraint le résultat à être une fraction
            math.evaluate(
              '(pickRandom([-1,1]))^(n)*randomInt(1,10)',
              { n: variables[v] }
            )
          )
          break
        case 'number': // On ne fait que le convertir en fraction
          if (params.type === 'decimal') assignations[v] = math.bignumber(variables[v])
          else assignations[v] = math.fraction(variables[v])
          break
        case 'string':
          // Parser l'expression
          // Parcourir le noeud et repérer les points sensibles (division, décimaux)
          try { // On tente les calculs exacts avec mathjs
            if (params.type === 'decimal') {
              math.config({ number: 'BigNumber' })
              assignations[v] = math.evaluate(variables[v], assignations)
              math.config({ number: 'number' })
            } else if (params.type === 'fraction') {
              math.config({ number: 'Fraction' })
              assignations[v] = math.evaluate(variables[v], assignations)
              math.config({ number: 'number' })
            } else {
              assignations[v] = math.evaluate(variables[v], assignations)
            }
          } catch { // Sinon on cherche à la transformer en fraction après coup
            try {
              if (params.type === 'decimal') assignations[v] = math.bignumber(math.evaluate(variables[v], assignations))
              else assignations[v] = math.fraction(math.evaluate(variables[v], assignations))
            } catch { // Sinon on fait sans mais on revient à des nombres de type 'number'
              const values = Object.assign({}, assignations)
              for (const v of Object.keys(values)) {
                values[v] = values[v].valueOf()
              }
              if (params.type === 'decimal') {
                math.config({ number: 'BigNumber' })
                assignations[v] = math.evaluate(variables[v], values)
                math.config({ number: 'number' })
              } else assignations[v] = math.evaluate(variables[v], values)
            }
          }
          break
      }
    }
    // On teste maintenant si les contraintes sont vérifiées
    if (variables.test !== undefined) test = math.evaluate(variables.test, assignations)
  } while (!test && cpt < 1000)
  if (cpt === 1000) window.notify('Attention ! 1000 essais dépassés.\n Trop de contraintes.\n Le résultat ne vérifiera pas le test.')
  if (params.valueOf) {
    for (const v of Object.keys(assignations)) {
      if (typeof assignations[v] !== 'number') {
        if (!(assignations[v] instanceof Decimal)) assignations[v] = assignations[v].valueOf()
      }
    }
  }
  if (params.format) {
    for (const v of Object.keys(assignations)) {
      assignations[v] = texNombre2(assignations[v])
    }
  }
  return assignations
}

/*
* Objet mathsteps : Permet de traverser toutes les étapes et sous-étapes
*/
export function traverserEtapes (steps, changeType = [], result = []) {
  steps.forEach(function (step, i) {
    if (changeType.length === 0) {
      if (step.substeps.length === 0) result.push(step)
      return traverserEtapes(step.substeps, changeType, result)
    } else {
      result.push(step)
      if (changeType.some(x => step.changeType === x)) {
        return traverserEtapes(step.substeps, changeType, result)
      }
    }
  })
  return result
}

/**
 * @description Retourne toutes les étapes de calculs d'une expression numérique ou de développement-réduction d'une expression littérale
 * @param {string} expression // Une expression à calculer ou à développer
 * @param {Objet} params // Les paramètres (commentaires visibles , sous-étapes visibles, fraction-solution au format MixedNumber)
*/
export function calculer (expression, params) {
  params = Object.assign({ comment: false, comments: {}, substeps: false, mixed: false, name: undefined, suppr1: true }, params)
  // La fonction simplifyExpression est une fonction mathsteps
  // Elle renvoie toutes les étapes d'un calcul numérique ou d'un développement-réduction
  // L'addition de deux fractions est classée dans les sous-étapes bizarrement
  // Les calculs se font de la gauche vers la droite et dès que c'est possible dans le respect des priorités
  // Les termes de même nature sont regroupés avant d'effectuer les calculs :
  // Les SymbolNode par exposant décroissant, puis Les constantes et enfin les fractions
  // Parfois les COLLECT LIKE TERMS ne donnent pas de changement ???
  // A faire : Virer l'étape précédent un REMOVE MULTIPLYING BY ONE et lui prendre son commentaire
  // A faire : Même chose pour REMOVE ADDING ZERO
  // La suite de CANCEL TERMS peut être applanie
  // Refaire la méthode transform() pour qu'elle ne modifie rien d'autre de notre noeud que ce qu'on souhaite
  // Si ça fonctionne on peut régler le problème des implicit qui disparaissent ? des (-3)² qui deviennent -3² ?
  // A faire : Ajouter un paramètre parenthesis à chaque noeud, ou il faudrait le faire dans Mathjs ?
  // BUG : http://localhost:8080/mathalea.html?ex=betaEquations,s=125
  if (params.variables !== undefined) expression = aleaExpression(expression, params.variables)
  const expressionPrint = toTex(expression, params)
  const steps = params.substeps ? traverserEtapes(simplifyExpression(expression)) : simplifyExpression(expression)
  const stepsExpression = []
  // const commentaires = []
  const comments = []
  steps.forEach(function (step, i) {
    const oldNode = step.oldNode !== null ? toTex(step.oldNode, params) : ''
    const newNode = toTex(step.newNode, params)
    if (newNode === oldNode) stepsExpression.pop()
    if (params.comment) {
      const comment = commentStep(step, params.comments)
      // const commentaire = `\\text{${step.changeType}}`.replaceAll('_', ' ')
      // commentaires.push(commentaire)
      comments.push(comment)
      if (stepsExpression.length === 0 || i === steps.length - 1) {
        if (params.name === undefined) {
          stepsExpression.push(`${expressionPrint}&=${newNode}&&${comment}`)
        } else {
          if (stepsExpression.length === 0) {
            stepsExpression.push(`${params.name}&=${expressionPrint}&&${comment}`)
            stepsExpression.push(`&=${newNode}&&${comment}`)
          } else {
            stepsExpression.push(`${params.name}&=${newNode}&&${comment}`)
          }
        }
      } else {
        stepsExpression.push(`&=${newNode}&&${comment}`)
      }
    } else {
      if (stepsExpression.length === 0 || i === steps.length - 1) {
        if (params.name === undefined) {
          stepsExpression.push(`${expressionPrint}&=${newNode}`)
        } else {
          if (stepsExpression.length === 0) {
            stepsExpression.push(`${params.name}&=${expressionPrint}`)
            stepsExpression.push(`&=${newNode}`)
          } else {
            stepsExpression.push(`${params.name}&=${newNode}`)
          }
        }
      } else {
        stepsExpression.push(`&=${newNode}`)
      }
    }
  })
  if (params.mixed === true && steps[steps.length - 1].newNode.type === 'OperatorNode' &&
  steps[steps.length - 1].newNode.op === '/' &&
  steps[steps.length - 1].newNode.args[0].type === 'ConstantNode' &&
  steps[steps.length - 1].newNode.args[1].type === 'ConstantNode' &&
  (
    Math.abs(steps[steps.length - 1].newNode.args[0].value) > steps[steps.length - 1].newNode.args[1].value ||
    steps[steps.length - 1].newNode.args[0].value < 0
  )
  ) {
    const plus = steps[steps.length - 1].newNode.args[0].value < 0 ? '-' : '+'
    stepsExpression.push(
      '&=' + toTex(
        math.parse(
          math.fraction(
            steps[steps.length - 1].newNode.args[0].value,
            steps[steps.length - 1].newNode.args[1].value
          ).toFraction(true).replace(' ', plus)
        ), params
      )
    )
  }
  const texte = `Calculer $${expressionPrint}$.`
  const texteCorr = `$\\begin{aligned}\n${stepsExpression.join('\\\\\n')}\n\\end{aligned}$`
  return { result: steps.length > 0 ? steps[steps.length - 1].newNode.toString() : expressionPrint, printResult: steps.length > 0 ? toTex(steps[steps.length - 1].newNode, params.totex) : expressionPrint, netapes: stepsExpression.length, texteDebug: texte + texteCorr, texte: texte, texteCorr: texteCorr, stepsLatex: stepsExpression, steps: steps, commentaires: comments, printExpression: expressionPrint, name: params.name }
}

export function aleaEquation (equation = 'a*x+b=c*x-d', variables = { a: false, b: false, c: false, d: false, test: 'a>b or true' }, debug = false) { // Ne pas oublier le signe de la multiplication
  const comparators = ['<=', '>=', '=', '<', '>']
  const assignations = aleaVariables(variables, debug)
  for (const v of Object.keys(assignations)) {
    assignations[v] = math.number(assignations[v])
  }
  let comparator
  let sides
  for (let i = 0; i < comparators.length; i++) {
    const comparatorSearch = comparators[i]
    sides = equation.split(comparatorSearch)
    if (sides.length === 2) {
      comparator = comparatorSearch
    }
  }
  sides = equation.split(comparator)
  const leftNode = expressionLitterale(sides[0], assignations, debug).toString()
  const rightNode = expressionLitterale(sides[1], assignations, debug).toString()
  if (debug) {
    console.log('Equation à résoudre : ', `${leftNode}${comparator}${rightNode}`)
  }
  return `${leftNode}${comparator}${rightNode}`
}

export function resoudreEquation (equation = '5(x-7)=3(x+1)', debug = false) {
  const comparators = ['<=', '>=', '=', '<', '>']
  let comparator
  let sides
  for (let i = 0; i < comparators.length; i++) {
    const comparatorSearch = comparators[i]
    sides = equation.split(comparatorSearch)
    if (sides.length === 2) {
      comparator = comparatorSearch
    }
  }
  sides = equation.split(comparator)
  // const equation0 = equation.replace(comparator, `+0${comparator}0+`)
  let equationPrint
  const steps = solveEquation(equation)
  if (debug) {
    console.log('* steps :')
    console.log(steps)
  }
  const stepsNewEquation = []
  let repetition = 0
  steps.forEach(function (step, i) {
    const changement = step.changeType
    if (step.oldEquation !== null) {
      if (step.oldEquation.leftNode.toString() === step.newEquation.leftNode.toString() || step.oldEquation.rightNode.toString() === step.newEquation.rightNode.toString()) {
        if (changement !== 'REMOVE_ADDING_ZEROS') repetition = (repetition + 1) % 3
      } else {
        repetition = 0
      }
    }
    const oldLeftNode = step.oldEquation !== null ? toTex(step.oldEquation.leftNode) : ''
    let newLeftNode = toTex(step.newEquation.leftNode)
    const oldRightNode = step.oldEquation !== null ? toTex(step.oldEquation.rightNode) : ''
    let newRightNode = toTex(step.newEquation.rightNode)
    /* const oldLeftNode = step.oldEquation !== null ? printMS.latex(step.oldEquation.leftNode, false) : ''
    let newLeftNode = printMS.latex(step.newEquation.leftNode, false)
    const oldRightNode = step.oldEquation !== null ? printMS.latex(step.oldEquation.rightNode, false) : ''
    let newRightNode = printMS.latex(step.newEquation.rightNode, false) */
    if (debug) {
      console.log(changement)
      console.log(newLeftNode.toString() + step.newEquation.comparator + newRightNode.toString())
    }
    if (i === 0) {
      equationPrint = `${oldLeftNode}${step.newEquation.comparator}${oldRightNode}`
    }
    const color = repetition === 2 ? 'black' : 'red'
    newLeftNode = `{\\color{${color}}${newLeftNode.replace(oldLeftNode, `{\\color{black}${oldLeftNode}}`)}}`
    newRightNode = `{\\color{${color}}${newRightNode.replace(oldRightNode, `{\\color{black}${oldRightNode}}`)}}`
    if (debug) console.log(newLeftNode + step.newEquation.comparator + newRightNode)
    const stepChange = getNewChangeNodes(step).length > 0 ? toTex(math.parse(getNewChangeNodes(step)[0].toString(), { parenthesis: 'auto' })) : ''
    let commentaires = {
      MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE: String.raw`\text{Multiplier les deux membres par }-1`,
      SUBTRACT_FROM_BOTH_SIDES: String.raw`\text{Soustraire }${stepChange}\text{ à chaque membre}`,
      ADD_TO_BOTH_SIDES: String.raw`\text{Ajouter }${stepChange}\text{ à chaque membre}`,
      MULTIPLY_TO_BOTH_SIDES: String.raw`\text{Multiplier chaque membre par }${stepChange}`,
      DIVIDE_FROM_BOTH_SIDES: String.raw`\text{Diviser chaque membre par }${stepChange}`,
      MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION: String.raw`\text{Multiplier chaque membre par }${stepChange}`
    }
    if (debug) {
      commentaires = Object.assign(commentaires, {
        STATEMENT_IS_FALSE: String.raw`\text{L'égalité est fausse}`,
        STATEMENT_IS_TRUE: String.raw`\text{L'égalité est vraie}`,
        DISTRIBUTE: String.raw`\text{Distribution}`,
        SIMPLIFY_RIGHT_SIDE: String.raw`\text{Simplifier le membre de droite}`,
        SIMPLIFY_LEFT_SIDE: String.raw`\text{Simplifier le membre de gauche}`,
        COLLECT_AND_COMBINE_LIKE_TERMS: String.raw`\text{Regrouper et réduire les termes de même nature}`,
        SIMPLIFY_ARITHMETIC: String.raw`\text{Calcul arithmétique}`,
        SIMPLIFY_FRACTION: String.raw`\text{Simplifier une fraction}`,
        REMOVE_MULTIPLYING_BY_NEGATIVE_ONE: String.raw`\text{Calculer la multiplication par }-1`,
        REMOVE_ADDING_ZERO: String.raw`\text{Enlever des zéros}`,
        SWAP_SIDES: String.raw`\text{Echanger les deux membres}`,
        CANCEL_MINUSES: String.raw`\text{Annuler les signes moins}`,
        FIND_ROOTS: String.raw`\text{Trouver la (ou les) solution(s)}`,
        SIMPLIFY_SIGNS: String.raw`\text{Simplifier le signe}`,
        MULTIPLY_BY_ZERO: String.raw`\text{Multiplication par zéro}`,
        ADD_FRACTIONS: String.raw`\text{Additionner des fractions}`,
        BREAK_UP_FRACTION: String.raw`\text{Séparer une fraction}`,
        CANCEL_TERMS: String.raw`\text{Annuler les termes}`,
        REMOVE_MULTIPLYING_BY_ONE: String.raw`\text{Retirer la multiplication par } 1`
      })
    }
    if (commentaires[changement] === undefined) commentaires[changement] = ''
    if (repetition === 2) {
      repetition = 0
      stepsNewEquation.pop()
      if (changement !== 'REMOVE_ADDING_ZERO') stepsNewEquation.push(String.raw`${newLeftNode}&${step.newEquation.comparator}${newRightNode}&&${commentaires[changement]}`)
    } else {
      if (changement !== 'REMOVE_ADDING_ZERO') stepsNewEquation.push(String.raw`${newLeftNode}&${step.newEquation.comparator}${newRightNode}&&${commentaires[changement]}`)
    }
    if (debug) console.log('changement', commentaires[changement])
  })
  let texte = String.raw`Résoudre $${equationPrint}$`
  const texteCorr = String.raw`
  $\begin{aligned}
  ${stepsNewEquation.join('\\\\')}
  \end{aligned}$
  `
  if (debug) texte = texteCorr
  return { texte: texte, texteCorr: texteCorr, equation: equationPrint }
}

export function commentStep (step, comments) {
  const changement = step.changeType
  const stepChange = step.stepChange
  // const stepChange = getNewChangeNodes(step).length > 0 ? toTex(math.parse(getNewChangeNodes(step)[0].toString(), { parenthesis: 'auto' })) : ''
  const defaultComments = {
    CROSS_PRODUCT_EQUALITY: `Egalité des produits en croix si $${stepChange}$.`,
    MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE: 'Multiplier les deux membres par $-1$.',
    SUBTRACT_FROM_BOTH_SIDES: `Soustraire $${stepChange}$ à chaque membre.`,
    ADD_TO_BOTH_SIDES: `Ajouter $${stepChange}$ à chaque membre`,
    MULTIPLY_TO_BOTH_SIDES: `Multiplier chaque membre par $${stepChange}$.`,
    DIVIDE_FROM_BOTH_SIDES: `Diviser chaque membre par $${stepChange}$.`,
    MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION: `Multiplier chaque membre par $${stepChange}$.`,
    SWAP_SIDES: 'Echanger les deux membres.',
    STATEMENT_IS_FALSE: 'L\'égalité est fausse.',
    STATEMENT_IS_TRUE: 'L\'égalité est vraie.',
    DISTRIBUTE: 'Distribution.',
    SIMPLIFY_RIGHT_SIDE: 'Simplifier le membre de droite.',
    SIMPLIFY_LEFT_SIDE: 'Simplifier le membre de gauche.',
    COLLECT_AND_COMBINE_LIKE_TERMS: 'Regrouper et réduire les termes de même nature.',
    SIMPLIFY_ARITHMETIC: 'Calcul arithmétique.',
    SIMPLIFY_FRACTION: 'Simplifier une fraction.',
    REMOVE_MULTIPLYING_BY_NEGATIVE_ONE: 'Calculer la multiplication par $-1$.',
    REMOVE_ADDING_ZERO: 'Enlever des zéros.',
    CANCEL_MINUSES: 'Annuler les signes moins.',
    FIND_ROOTS: 'Trouver la (ou les) solution(s).',
    SIMPLIFY_SIGNS: 'Simplifier le signe.',
    MULTIPLY_BY_ZERO: 'Multiplication par zéro.',
    ADD_FRACTIONS: 'Additionner des fractions.',
    BREAK_UP_FRACTION: 'Séparer une fraction.',
    CANCEL_TERMS: 'Annuler les termes.',
    REMOVE_MULTIPLYING_BY_ONE: 'Retirer la multiplication par $1$.',
    COLLECT_LIKE_TERMS: 'Regrouper les termes.',
    MULTIPLY_DENOMINATORS: 'Calculer les dénominateurs.',
    ADD_EXPONENT_OF_ONE: 'Ajouter l\'exposant 1.',
    COLLECT_POLYNOMIAL_EXPONENTS: 'Ajouter l\'exposant 1.',
    COMMON_DENOMINATOR: 'Obtenir le même dénominateur.',
    MULTIPLY_NUMERATORS: 'Calculer.',
    COMBINE_NUMERATORS: 'Combiner les numérateurs.',
    ADD_NUMERATORS: 'Additionner les numérateurs.',
    ADD_COEFFICIENT_OF_ONE: 'Ajouter le coefficient $1$',
    GROUP_COEFFICIENTS: 'Regrouper les coefficients.',
    FIND_GCD: 'Trouver le plus grand diviseur commun.',
    CANCEL_GCD: 'Simplifier par le PGCD.',
    MULTIPLY_FRACTIONS: 'Multiplier deux fractions.'
  }
  comments = Object.assign(defaultComments, comments)
  return (comments[changement] !== undefined) ? `\\text{${comments[changement].replaceAll('{stepChange}', `$${stepChange}$`)}}` : ''
}

/**
 * Check if x is a decimal number
 * @param {Object} x // Object type = Fraction (mathjs)
 * @returns {boolean}
 */
export function isDecimal (value) {
  let f
  if (typeof value === 'number') {
    f = math.fraction(value)
  } else if (typeof value === 'string') {
    f = math.fraction(value.replaceAll(' ', ''))
  } else {
    f = value.clone()
  }
  return f.d !== 1 && !obtenirListeFacteursPremiers(f.d).some(x => x !== 2 && x !== 5)
}

/**
 * Find if there is a SymbolNode in the node
 * @param {Mathnode} node
 * @returns {boolean}
 */
function isContentSymbolNode (node) {
  let result = false
  node.traverse(x => { if (x.isSymbolNode) result = true })
  return result
}

/**
* @description Retourne toutes les étapes de résolution d'une équation ou d'une inéquation
* @param {Objet} params // Les paramètres (commentaires visibles)
* @param {string} equation // Une équation ou une inéquation
* @example
* resoudre('2*x+4=4*x-5') --> Donne les étapes de la résolution de l'équation
* resoudre('2*x+4=4*x-5'), {comment: true}) --> Ajoute les commentaires
* resoudre('2*x+4=4*x-5', {color: blue}) -> Met en bleu les changements à chaque étape
* resoudre('2*x+4=4*x-5', {substeps: true}) --> Ajoute les sous-étapes
* resoudre('2*x+4=4*x-5', {produitsencroix: true}) --> Utilise les produits en croix lorsque l'inconnue est au dénominateur a/f(x)=b/c
* resoudre('2*x+4=4*x-5', {verifications: true}) --> Ajoute les vérifications de la solution
* resoudre('a*x+c=b*x+d', {variables: {a: true, b: true, c: true, d: true, test: 'a!=b'}}) --> a, b, c et d sont choisis au hasard voir la fonction aleaVariables()
* resoudre('2*x+4=4*x-5', {comment: true, comments: commentairesPersonnalises}) --> commentairesPersonnalises est un tableau avec des commentaires personnalisés (voir fonction commentStep())
*/
export function resoudre (equation, params) {
  /*
    formatSolution
      2 (défaut) : décimal si la solution a 2 chiffres ou moins après la virgule, fraction sinon
      n : décimal si la solution a n chiffres ou moins après la virgule, fraction sinon
      'decimal' : decimal lorsque c'est possible, sinon fraction
      'fraction' : fraction (ou entier lorsque c'est possible)
  */
  params = Object.assign({ comment: false, color: 'red', comments: {}, reduceSteps: true, formatSolution: 2, substeps: false, changeType: [], produitsencroix: false, verifications: false }, params)
  // Un bug de mathsteps ne permet pas de résoudre 2/x=2 d'où la ligne suivante qui permettait de le contourner
  // const equation0 = equation.replace(comparator, `+0${comparator}0+`)
  // A priori le traitement actuel n'occure plus ce bug (raison ?).
  if (params.variables !== undefined) equation = aleaEquation(equation, params.variables)
  let printEquation
  let steps = params.substeps ? traverserEtapes(solveEquation(equation), params.changeType) : solveEquation(equation)
  // Rechercher une équation de la forme a/x=b/c ou a/b=c/x et utiliser l'égalité des produits en croix
  let newSteps = []
  if (params.produitsencroix && steps[0].oldEquation.comparator === '=') {
    let i = 0
    do {
      const leftNode = steps[i].oldEquation.leftNode.cloneDeep()
      const rightNode = steps[i].oldEquation.rightNode.cloneDeep()
      if (leftNode.isOperatorNode && leftNode.fn === 'divide' && isContentSymbolNode(leftNode.args[1])) {
        if (rightNode.isOperatorNode && rightNode.fn === 'divide') {
          steps[i].newEquation.rightNode = new math.OperatorNode('*', 'multiply', [leftNode.args[0], rightNode.args[1]])
          steps[i].newEquation.leftNode = new math.OperatorNode('*', 'multiply', [rightNode.args[0], leftNode.args[1]])
          steps[i].changeType = 'CROSS_PRODUCT_EQUALITY'
          const stepChanges = []
          if (isContentSymbolNode(leftNode.args[1])) stepChanges.push(toTex(leftNode.args[1].toString()) + '\\neq 0')
          if (isContentSymbolNode(rightNode.args[1])) stepChanges.push(toTex(rightNode.args[1].toString()) + '\\neq 0')
          steps[i].stepChange = stepChanges.join('\\text{ et }')
          const newEquation = steps[i].newEquation.ascii()
          newSteps = params.substeps ? traverserEtapes(solveEquation(newEquation), params.changeType) : solveEquation(newEquation)
          steps = steps.slice(0, i + 1).concat(newSteps)
        }
      } else if (rightNode.isOperatorNode && rightNode.fn === 'divide' && isContentSymbolNode(rightNode.args[1])) {
        if (leftNode.isOperatorNode && leftNode.fn === 'divide') {
          steps[i].newEquation.rightNode = new math.OperatorNode('*', 'multiply', [rightNode.args[0], leftNode.args[1]])
          steps[i].newEquation.leftNode = new math.OperatorNode('*', 'multiply', [leftNode.args[0], rightNode.args[1]])
          steps[i].changeType = 'CROSS_PRODUCT_EQUALITY'
          const stepChanges = []
          if (isContentSymbolNode(leftNode.args[1])) stepChanges.push(toTex(leftNode.args[1].toString()) + '\\neq 0')
          if (isContentSymbolNode(rightNode.args[1])) stepChanges.push(toTex(rightNode.args[1].toString()) + '\\neq 0')
          steps[i].stepChange = stepChanges.join('\\text{ et }')
          const newEquation = steps[i].newEquation.ascii()
          newSteps = params.substeps ? traverserEtapes(solveEquation(newEquation), params.changeType) : solveEquation(newEquation)
          steps = steps.slice(0, i + 1).concat(newSteps)
        }
      }
      i += 1
    } while (i < steps.length && newSteps.length < 1)
  }
  const stepsNewEquation = []
  let repetition = 0
  steps.forEach(function (step, i) {
    const changement = step.changeType
    if (step.changeType !== 'CROSS_PRODUCT_EQUALITY') step.stepChange = getNewChangeNodes(step).length > 0 ? toTex(math.parse(getNewChangeNodes(step)[0].toString(), { parenthesis: 'auto' })) : ''
    if (step.oldEquation !== null) {
      if (params.reduceSteps && (step.oldEquation.leftNode.toString() === step.newEquation.leftNode.toString() || step.oldEquation.rightNode.toString() === step.newEquation.rightNode.toString())) {
        if (changement !== 'REMOVE_ADDING_ZEROS') repetition = (repetition + 1) % 3
      } else {
        repetition = 0
      }
    }
    const oldLeftNode = step.oldEquation !== null ? toTex(step.oldEquation.leftNode, params) : ''
    let newLeftNode = toTex(step.newEquation.leftNode, params)
    const oldRightNode = step.oldEquation !== null ? toTex(step.oldEquation.rightNode, params) : ''
    let newRightNode = toTex(step.newEquation.rightNode, params)
    const newEquationComparator = toTex(step.newEquation.comparator)
    if (i === 0) {
      // printEquation = `${oldLeftNode}${step.newEquation.comparator}${oldRightNode}`
      printEquation = `${toTex(step.oldEquation.ascii())}`
      stepsNewEquation.push(
        String.raw`${oldLeftNode}&${toTex(step.oldEquation.comparator)}${oldRightNode}`)
    }
    if (params.color !== 'black') {
      const color = repetition === 2 ? 'black' : params.color
      newLeftNode = `{\\color{${color}}${newLeftNode.replace(oldLeftNode, `{\\color{black}${oldLeftNode}}`)}}`
      newRightNode = `{\\color{${color}}${newRightNode.replace(oldRightNode, `{\\color{black}${oldRightNode}}`)}}`
    }
    const comment = commentStep(step, params.comments)
    if (repetition === 2) {
      repetition = 0
      stepsNewEquation.pop()
      stepsNewEquation.push(`${newLeftNode}&${newEquationComparator}${newRightNode}${params.comment ? `&&${comment}` : ''}`)
    } else {
      stepsNewEquation.push(`${newLeftNode}&${newEquationComparator}${newRightNode}${params.comment ? `&&${comment}` : ''}`)
    }
  })
  const lastEquation = steps[steps.length - 1].newEquation
  let answer = lastEquation.rightNode
  if (params.formatSolution !== 'fraction' && !(answer.isConstantNode)) {
    try {
      // On ve tenter d'obtenir le résultat sous forme de fraction, si ce n'est pas possible on quitte le try
      math.config({ number: 'Fraction' })
      answer = math.evaluate(answer.eval())
      math.config({ number: 'number' })
      // On regarde si le résultat a un nombre fini de chiffres après la virgule et n'est pas un entier
      if (isDecimal(answer)) {
        answer = math.round(answer.valueOf(), 15) // convertit la fraction en nombre décimal en évitant les problèmes de float
        if (params.formatSolution === 'decimal' || (typeof params.formatSolution === 'number' && answer.toString().split('.')[1].length <= params.formatSolution)) {
          // On rajoute une étape de conversion de la fraction en nombre décimal
          stepsNewEquation.push(`${toTex(lastEquation.leftNode, params)}&${toTex(lastEquation.comparator + answer.toString())}`)
        }
      }
    } catch (e) {}
  }

  const texte = `Résoudre $${printEquation}$.`
  let texteCorr = `$\\begin{aligned}\n${stepsNewEquation.join('\\\\\n')}\n\\end{aligned}$`
  const solution = {
    printDecimal: texNombre2(math.evaluate(steps[steps.length - 1].newEquation.ascii().split(steps[steps.length - 1].newEquation.comparator)[1])),
    decimal: math.evaluate(steps[steps.length - 1].newEquation.ascii().split(steps[steps.length - 1].newEquation.comparator)[1]),
    exact: steps[steps.length - 1].newEquation.ascii().split(steps[steps.length - 1].newEquation.comparator)[1],
    print: toTex(steps[steps.length - 1].newEquation.ascii())
  }
  let calculateLeftSide, calculateRightSide
  if (steps[steps.length - 1].newEquation.leftNode.isSymbolNode) {
    const sides = equation.split(steps[0].oldEquation.comparator)
    const SymbolNode = steps[steps.length - 1].newEquation.leftNode.toString()
    const thesolution = steps[steps.length - 1].newEquation.rightNode.toString()
    calculateLeftSide = calculer(sides[0].replaceAll(SymbolNode, `(${thesolution})`))
    calculateRightSide = calculer(sides[1].replaceAll(SymbolNode, `(${thesolution})`))
  }
  if (params.verifications) {
    texteCorr = `<br>
          ${texteCorr}<br>
          La solution est $${solution.print}$.
          <br>
          $\\textit{Vérification :}$
          <br>
          $\\bullet$ D'une part : $${calculateLeftSide.printExpression}=${calculateLeftSide.printResult}$
          <br>
          $\\bullet$ D'autre part : $${calculateRightSide.printExpression}=${calculateRightSide.printResult}$
          `
  }
  return {
    solution: solution,
    texte: texte,
    texteCorr: texteCorr,
    equation: printEquation,
    verifLeftSide: calculateLeftSide,
    verifRightSide: calculateRightSide,
    steps: steps,
    printSteps: stepsNewEquation
  }
}

export function programmeCalcul (stepProg = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x'], nombreChoisi, debug = false) {
  const rules = math.simplify.rules
  rules[13] = { l: 'n', r: 'n' } // Pour éviter la factorisation
  rules[14] = { l: 'n', r: 'n' } // Pour éviter la factorisation
  // rules.push({ l: 'n1+-n2', r: 'n1-n2' }) // Peut être utile pour des nombres négatifs
  const variables = {}
  variables.symbolsOp = Object.values(stepProg)
  const symbolsOp = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x']
  const op = ['+', '-', '*', '/', '^', '+', '+', '-', '-', '+', '-', '+', '-', '*', '/']
  const namesOp = [
    'add', 'subtract', 'multiply', 'divide',
    'pow',
    'add', 'add',
    'subtract', 'subtract',
    'add', 'subtract',
    'add', 'subtract',
    'multiply', 'divide']
  const namesOpInv = {
    add: 'subtract',
    subtract: 'add',
    multiply: 'divide',
    divide: 'multiply'
  }
  const symbolsOpInv = {
    add: '-',
    subtract: '+',
    multiply: '/',
    divide: '*'
  }
  const debutsPhrase = [
    'Ajouter ', 'Soustraire ', 'Multiplier par ', 'Diviser par ',
    'Elever au carré',
    'Ajouter le double du nombre choisi', 'Ajouter le triple du nombre choisi',
    'Soustraire le double du nombre choisi', 'Soustraire le triple du nombre choisi',
    'Ajouter le carré du nombre choisi', 'Soustraire le carré du nombre choisi',
    'Ajouter le nombre choisi', 'Soustraire le nombre choisi',
    'Multiplier par le nombre choisi', 'Diviser par le nombre choisi'
  ]
  const debutsPhraseInv = [
    'Soustraire ', 'Ajouter ', 'Diviser par ', 'Multiplier par ',
    'Prendre la racine carré',
    'Soustraire le double du nombre choisi', 'Soustraire le triple du nombre choisi',
    'Ajouter le double du nombre choisi', 'Ajouter le triple du nombre choisi',
    'Soustraire le carré du nombre choisi', 'Ajouter le carré du nombre choisi',
    'Soustraire le nombre choisi', 'Ajouter le nombre choisi',
    'Diviser par le nombre choisi', 'Multiplier par le nombre choisi'
  ]
  const nombresAutorises1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  const nombresAutorises2 = [2, 3, 4, 5, 6, 7, 8, 9]
  variables.namesOp = Object.values(variables.symbolsOp)
  variables.debutsPhrase = Object.values(variables.symbolsOp)
  variables.debutsPhraseInv = Object.values(variables.symbolsOp)
  variables.op = Object.values(variables.symbolsOp)
  variables.namesOp.forEach(function (n, i) {
    variables.namesOp[i] = namesOp[symbolsOp.indexOf(n)]
  })
  variables.debutsPhrase.forEach(function (n, i) {
    variables.debutsPhrase[i] = debutsPhrase[symbolsOp.indexOf(n)]
  })
  variables.debutsPhraseInv.forEach(function (n, i) {
    variables.debutsPhraseInv[i] = debutsPhraseInv[symbolsOp.indexOf(n)]
  })
  variables.op.forEach(function (n, i) {
    variables.op[i] = op[symbolsOp.indexOf(n)]
  })
  const nodes = [new math.SymbolNode('x')]
  const nodesInv = []
  const phrases = ['Choisir un nombre.']
  const steps = ['x']
  const stepsNode = [new math.SymbolNode('x')]
  const stepsSimplified = ['x']
  const stepsInv = ['x']
  const stepsSimplifiedInv = ['x']
  const phrasesInv = ['On obtient le nombre choisi.']
  nombreChoisi = math.simplify(math.format(nombreChoisi))
  const resultatIntermediaire = [nombreChoisi]
  const calculIntermediaire = [nombreChoisi]
  let step
  const longueur = variables.symbolsOp.length + 1
  for (let i = 1; i < longueur; i++) {
    const choix = i - 1
    let symbolOp = variables.symbolsOp[choix]
    const nameOp = variables.namesOp[choix]
    const debutPhrase = variables.debutsPhrase[choix]
    const debutPhraseInv = variables.debutsPhraseInv[choix]
    const op = variables.op[choix]
    let stepPrint = ''
    switch (symbolOp) {
      case '/':
        step = new math.ConstantNode(math.pickRandom(nombresAutorises2))
        break
      case '*':
        step = new math.ConstantNode(math.pickRandom(nombresAutorises2))
        break
      case '^':
        step = new math.ConstantNode(2)
        break
      case '-':
        step = new math.ConstantNode(math.pickRandom(nombresAutorises1))
        break
      case '+':
        step = new math.ConstantNode(math.pickRandom(nombresAutorises2))
        break
      default :
        if (symbolOp[0] === '-') symbolOp = symbolOp.replace('-', '')
        step = math.parse(symbolOp)
    }
    stepsNode.push(step)
    if (step.isConstantNode) stepPrint = `$${step.toString()}$`
    let nodeSimplifie = math.simplify(nodes[i - 1].toString({ parenthesis: 'keep' }), rules)
    nodes.push(new math.OperatorNode(op, nameOp, [new math.ParenthesisNode(nodeSimplifie), step]))
    steps.push(toTex(nodes[i], { suppr1: false }, debug))
    nodeSimplifie = math.simplify(nodes[i].toString({ parenthesis: 'auto' }), rules)
    nodesInv.push(new math.OperatorNode(symbolsOpInv[nameOp], namesOpInv[nameOp], [new math.ParenthesisNode(nodeSimplifie), step]))
    stepsInv.push(toTex(nodesInv[i - 1], { suppr1: false }, debug))
    stepsSimplified.push(toTex(nodeSimplifie, { suppr1: false }, debug))
    const nodeSimplifieInv = math.parse(nodesInv[i - 1].toString({ parenthesis: 'auto' }))
    stepsSimplifiedInv.push(toTex(nodeSimplifieInv, { suppr1: false }, debug))
    phrases.push(debutPhrase + stepPrint)
    phrasesInv.push(debutPhraseInv + stepPrint)
    if (i === variables.symbolsOp.length) {
      steps.push(toTex(nodes[i], { suppr1: false }, debug))
      stepsSimplified.push(toTex(nodeSimplifie, { suppr1: false }, debug))
      stepsInv.push(toTex(nodesInv[i - 1], { suppr1: false }, debug))
      stepsSimplifiedInv.push(toTex(nodeSimplifie, { suppr1: false }, debug))
      phrases.push('Écrire le résultat')
      // phrasesInv.push(debutPhraseInv + stepPrint)
      phrasesInv.push('Résultat du programme')
    }
    if (i === longueur) {
      calculIntermediaire.push(calculIntermediaire[i - 1])
      resultatIntermediaire.push(calculIntermediaire[i - 1])
    } else if (i > 0) {
      calculIntermediaire.push(new math.OperatorNode(variables.op[choix], nameOp, [resultatIntermediaire[i - 1], math.simplify(step, [{ l: 'n', r: 'n' }], { x: nombreChoisi })]))
      resultatIntermediaire.push(math.simplify(calculIntermediaire[i], { x: nombreChoisi }))
    }
  }
  const resultatIntermediaireInv = [resultatIntermediaire[longueur - 1]]
  const calculIntermediaireInv = [resultatIntermediaire[longueur - 1]]
  for (let i = 1; i < longueur; i++) {
    const choix = i - 1
    const nameOp = variables.namesOp[longueur - 2 - choix]
    if (i === longueur) {
      calculIntermediaireInv.push(calculIntermediaireInv[i])
      resultatIntermediaireInv.push(calculIntermediaireInv[i])
    } else if (i < longueur) {
      calculIntermediaireInv.push(new math.OperatorNode(symbolsOpInv[nameOp], namesOpInv[nameOp], [resultatIntermediaireInv[i - 1], math.simplify(stepsNode[longueur - i], [{ l: 'n', r: 'n' }], { x: nombreChoisi })]))
      resultatIntermediaireInv.push(math.simplify(calculIntermediaireInv[i], { x: nombreChoisi }))
    }
  }
  return { phrases: phrases, steps: steps, stepsSimplified: stepsSimplified, stepsInv: stepsInv, stepsSimplifiedInv: stepsSimplifiedInv, phrasesInv: phrasesInv, nodes: nodes, stepProg: stepProg, calculIntermediaire: calculIntermediaire, resultatIntermediaire: resultatIntermediaire, calculIntermediaireInv: calculIntermediaireInv, resultatIntermediaireInv: resultatIntermediaireInv }
}

export function traduireProgrammeCalcul (stepProg = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x'], nombreChoisi, debug = false) {
  const programme = programmeCalcul(stepProg, nombreChoisi, debug)
  const stepsSolutionDetaillee = Object.values(programme.phrases) // Clone de phrases pour ne pas être touchée par les modifications
  stepsSolutionDetaillee.forEach(function (step, i) {
    stepsSolutionDetaillee[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}&'
    programme.phrases[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}'
    stepsSolutionDetaillee[i] += '&' + programme.steps[i]
    if (programme.steps[i] !== programme.stepsSimplified[i]) stepsSolutionDetaillee[i] += '&=' + programme.stepsSimplified[i]
  })
  let texte = String.raw` Voici un programme de calcul.
          <br>
          $\begin{aligned}
          ${programme.phrases.join('\\\\')}
          \end{aligned}$
          <br>
          Notons $x$ le nombre choisi.
          <br>
          Écrire le résultat du programme de calcul en fonction de $x$.
          `
  const texteCorr = String.raw`Solution détaillée
          <br>
          $\begin{aligned}
          ${stepsSolutionDetaillee.join('\\\\')}
          \end{aligned}$`
  if (debug) texte = `${texte}<br>${texteCorr}`
  return { texte: texte, texteCorr: texteCorr }
}

export function ecrireProgrammeCalcul (stepProg = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x'], nombreChoisi, debug = false) {
  const programme = programmeCalcul(stepProg, nombreChoisi, debug)
  const stepsSolutionDetaillee = Object.values(programme.phrases) // Clone de phrases pour ne pas être touchée par les modifications
  stepsSolutionDetaillee.forEach(function (step, i) {
    stepsSolutionDetaillee[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}&'
    programme.phrases[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}'
    stepsSolutionDetaillee[i] += '&' + programme.steps[i]
    if (programme.steps[i] !== programme.stepsSimplified[i]) stepsSolutionDetaillee[i] += '&=' + programme.stepsSimplified[i]
  })
  let texte = String.raw`Voici une expression. Écrire le programme de calcul correspondant.
          <br>
          $${programme.stepsSimplified[programme.stepsSimplified.length - 1]}$
          `
  const texteCorr = String.raw`Solution détaillée
          <br>
          $\begin{aligned}
          ${stepsSolutionDetaillee.join('\\\\')}
          \end{aligned}$`
  if (debug) texte = `${texte}<br>${texteCorr}`
  return { texte: texte, texteCorr: texteCorr }
}

export function remonterProgrammeCalcul (stepProg = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x'], nombreChoisi, debug = false) {
  const programme = programmeCalcul(stepProg, nombreChoisi, debug)
  const stepsSolutionDetaillee = Object.values(programme.phrases) // Clone de phrases pour ne pas être touchée par les modifications
  const stepsSolutionDetailleeInv = Object.values(programme.phrases) // Clone de phrases pour ne pas être touchée par les modifications
  const longueur = stepsSolutionDetaillee.length
  stepsSolutionDetaillee.forEach(function (step, i) {
    stepsSolutionDetaillee[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}&'
    programme.phrases[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}'
    stepsSolutionDetaillee[i] += '&' + programme.steps[i]
    stepsSolutionDetailleeInv[i] = '&\\bullet~\\text{' + programme.phrasesInv[i] + '}&'
    programme.phrasesInv[i] = '&\\bullet~\\text{' + programme.phrasesInv[i] + '}'
    if (i === 0) {
      stepsSolutionDetailleeInv[i] += '&' + toTex(programme.resultatIntermediaireInv[longueur - 2])
    } else if (i < stepsSolutionDetaillee.length - 1) {
      stepsSolutionDetailleeInv[i] += '&' + toTex(programme.calculIntermediaireInv[longueur - 1 - i]) + '&&=' + toTex(programme.resultatIntermediaireInv[longueur - 1 - i])
    } else {
      stepsSolutionDetailleeInv[i] += '&' + toTex(programme.resultatIntermediaireInv[0])
    }
  })
  nombreChoisi = math.simplify(math.format(nombreChoisi))
  let texte = String.raw`On obtient le nombre $${toTex(programme.resultatIntermediaireInv[0])}$ avec le programme suivant.
          <br>
          $\begin{aligned}
          ${programme.phrases.join('\\\\')}
          \end{aligned}$
          <br>
          Quel était le nombre choisi ?
          `
  const texteCorr = String.raw`Solution détaillée
          <br>
          $\begin{aligned}
          ${stepsSolutionDetailleeInv.reverse().join('\\\\')}
          \end{aligned}$
          <br>
          Le nombre choisi était donc $${toTex(nombreChoisi)}$.
          `
  if (debug) texte = `${texte}<br>${texteCorr}`
  return { texte: texte, texteCorr: texteCorr }
}

export function appliquerProgrammeCalcul (stepProg = ['+', '-', '*', '/', '^2', '2*x', '3*x', '-2*x', '-3*x', 'x^2', '-x^2', 'x', '-x', '*x', '/x'], nombreChoisi, debug = false) {
  const programme = programmeCalcul(stepProg, nombreChoisi, debug)
  const stepsSolutionDetaillee = Object.values(programme.phrases) // Clone de phrases pour ne pas être touchée par les modifications
  stepsSolutionDetaillee.forEach(function (step, i) {
    stepsSolutionDetaillee[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}&'
    programme.phrases[i] = '&\\bullet~\\text{' + programme.phrases[i] + '}'
    // stepsSolutionDetaillee[i] += '&' + programme.steps[i]
    if (i === 0) {
      stepsSolutionDetaillee[i] += '&' + toTex(programme.resultatIntermediaire[0])
    } else if (i < stepsSolutionDetaillee.length - 1) {
      stepsSolutionDetaillee[i] += '&' + toTex(programme.calculIntermediaire[i]) + '&&=' + toTex(programme.resultatIntermediaire[i])
    } else {
      stepsSolutionDetaillee[i] += '&' + toTex(programme.resultatIntermediaire[i - 1])
    }
  })
  nombreChoisi = math.simplify(math.format(nombreChoisi))
  let texte = String.raw`Choisir le nombre $${toTex(nombreChoisi)}$ et effectuer le programme de calcul suivant.
          <br>
          $\begin{aligned}
          ${programme.phrases.join('\\\\')}
          \end{aligned}$
          <br>
          `
  const texteCorr = String.raw`Solution détaillée
          <br>
          $\begin{aligned}
          ${stepsSolutionDetaillee.join('\\\\')}
          \end{aligned}$`
  if (debug) texte = `${texte}<br>${texteCorr}`
  return { texte: texte, texteCorr: texteCorr }
}

export function calculExpression2 (expression = '4/3+5/6', factoriser = false, debug = false) {
  const steps = factoriser ? traverserEtapes(factor(expression)) : traverserEtapes(simplifyExpression(expression))
  if (debug) {
    console.log('* steps :')
    console.log(steps)
  }
  let repetition = 0
  const stepsExpression = []
  let expressionPrint = ''
  steps.forEach(function (step, i) {
    const changement = step.changeType
    if (step.oldNode !== null) {
      if (step.oldNode.toString() === step.newNode.toString()) {
        if (changement !== 'REMOVE_ADDING_ZEROS') repetition = (repetition + 1) % 2
      } else {
        repetition = 0
      }
    }
    if (debug) {
      console.log(changement)
      console.log(step.newNode.toString())
    }
    const oldNode = step.oldNode !== null ? toTex(step.oldNode, { suppr1: true }, debug) : ''
    const newNode = toTex(step.newNode, { suppr1: true }, debug)
    if (debug) {
      console.log(newNode.toString())
    }
    if (i === 0) {
      expressionPrint = `${oldNode}`
    }
    if (debug) console.log(newNode)
    const commentairesExclus = {
      REMOVE_ADDING_ZERO: String.raw`\text{Enlever des zéros}`,
      EXPAND_EXPONENT: String.raw`\text{Signification des exposants}`,
      MULTIPLY_COEFFICIENTS: String.raw`\text{Multiplier les coefficients}`,
      COLLECT_LIKE_TERMS: String.raw`\text{Regrouper les termes}`,
      MULTIPLY_DENOMINATORS: String.raw`\text{Calculer les dénominateurs}`,
      ADD_EXPONENT_OF_ONE: String.raw`\text{Ajouter l'exposant 1}`,
      COLLECT_POLYNOMIAL_EXPONENTS: String.raw`\text{Ajouter l'exposant 1}`,
      DISTRIBUTE: String.raw`\text{Distribution}`,
      ADD_COEFFICIENT_OF_ONE: String.raw`\text{Ajouter le coefficient }1`,
      GROUP_COEFFICIENTS: String.raw`\text{Regrouper les coefficients}`,
      REMOVE_MULTIPLYING_BY_ONE: String.raw`\text{Retirer la multiplication par } 1`
    }
    let commentaires = {
      COMMON_DENOMINATOR: String.raw`\text{Obtenir le même dénominateur}`,
      MULTIPLY_NUMERATORS: String.raw`\text{Calculer}`,
      COMBINE_NUMERATORS: String.raw`\text{Combiner les numérateurs}`,
      ADD_NUMERATORS: String.raw`\text{Additionner les numérateurs}`,
      FIND_GCD: String.raw`\text{Trouver le plus grand diviseur commun.}`,
      CANCEL_GCD: String.raw`\text{Simplifier par le PGCD.}`
    }
    if (debug) {
      commentaires = Object.assign(commentaires, {
        STATEMENT_IS_FALSE: String.raw`\text{L'égalité est fausse}`,
        STATEMENT_IS_TRUE: String.raw`\text{L'égalité est vraie}`,
        SIMPLIFY_RIGHT_SIDE: String.raw`\text{Simplifier le membre de droite}`,
        SIMPLIFY_LEFT_SIDE: String.raw`\text{Simplifier le membre de gauche}`,
        COLLECT_AND_COMBINE_LIKE_TERMS: String.raw`\text{Regrouper et réduire les termes de même nature}`,
        SIMPLIFY_ARITHMETIC: String.raw`\text{Calcul arithmétique}`,
        SIMPLIFY_FRACTION: String.raw`\text{Simplifier une fraction}`,
        REMOVE_MULTIPLYING_BY_NEGATIVE_ONE: String.raw`\text{Calculer la multiplication par }-1`,
        REMOVE_ADDING_ZERO: String.raw`\text{Enlever des zéros}`,
        SWAP_SIDES: String.raw`\text{Echanger les deux membres}`,
        CANCEL_MINUSES: String.raw`\text{Annuler les signes moins}`,
        FIND_ROOTS: String.raw`\text{Trouver la (ou les) solution(s)}`,
        SIMPLIFY_SIGNS: String.raw`\text{Simplifier le signe}`,
        MULTIPLY_BY_ZERO: String.raw`\text{Multiplication par zéro}`,
        ADD_FRACTIONS: String.raw`\text{Additionner des fractions}`,
        BREAK_UP_FRACTION: String.raw`\text{Séparer une fraction}`,
        CANCEL_TERMS: String.raw`\text{Annuler les termes}`
      })
    }
    if (commentaires[changement] === undefined) commentaires[changement] = ''
    if (commentairesExclus[changement] === undefined) stepsExpression.push(String.raw`&=${newNode}`)
    if (debug) console.log('changement', commentaires[changement])
  })
  let texte = String.raw`Développer et réduire $${expressionPrint}$.`
  const texteCorr = String.raw`Simplifier $${expressionPrint}$.
  <br>
  $\begin{aligned}
  ${expressionPrint}${stepsExpression.slice(stepsExpression.length - 4, stepsExpression.length).join('\\\\')}
  \end{aligned}$
  `
  if (debug) texte = texteCorr
  return { texte: texte, texteCorr: texteCorr }
}

/**
 * Retourne des noms de points (ou des objets) dans un ordre aléatoire.
 * @param {string|Array} names // Liste des lettres sous format string ou array
 * @param {number} n // Nombre de lettres à retourner
 * @param {string|Array} result // S'il n'y a qu'un seul nom en sortie c'est un string sinon c'est un array
 * @remarque // Les lettres Q,W,X,Y,Z ont été exclues par défaut
 * @example
 * aleaName() --> 'F'
 * aleaName(3) --> ['G', 'J', 'K']
 * aleaName('ABC') --> ['B','A','C']
 * aleaName(['chat','chien','poisson']) --> ['chien','poisson','chat']
 * aleaName(['chat','chien','poisson'],2) --> ['poisson','chat']
 * aleaName([Objet1,Objet2,Objet3]) --> [Objet2,Objet1,Objet3] où Objet peut être un Object, un Array etc.
 * @returns {Array}
 */
export function aleaName (names, n = names.length, result = []) {
  if (typeof names === 'string') {
    names = names.split('')
  } else if (typeof names === 'number') {
    n = 0 + names
    names = 'ABCDEFGHIJKLMNOPRSTUV'.split('')
  } else if (Array.isArray(names) && names.length === 0) {
    n = 1
    names = 'ABCDEFGHIJKLMNOPRSTUV'.split('')
  }
  result.push(names.splice(Math.floor(Math.random() * names.length), 1)[0])
  if (result.length === n) {
    return result
  } else {
    return aleaName(names, n, result)
  }
}