text: /* *************************************************************************************************
   *                                                                                                *
   * Plese read the following tutorial before implementing tasks:                                   *
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling  *
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration              *
   *                                                                                                *
   ************************************************************************************************ */
  
  /**
   * Returns the 'Fizz','Buzz' or an original number using the following rules:
   * 1) return original number
   * 2) but if number multiples of three return 'Fizz'
   * 3) for the multiples of five return 'Buzz'
   * 4) for numbers which are multiples of both three and five return 'FizzBuzz'
   *
   * @param {number} num
   * @return {any}
   *
   * @example
   *   2 =>  2
   *   3 => 'Fizz'
   *   5 => 'Buzz'
   *   4 => 4
   *  15 => 'FizzBuzz'
   *  20 => 'Buzz'
   *  21 => 'Fizz'
   *
   */
  function getFizzBuzz(num) {
    if (num % 3 === 0 && num % 5 === 0) return 'FizzBuzz';
    if (num % 3 === 0) return 'Fizz';
    if (num % 5 === 0) return 'Buzz';
    return num;
  }
  
  /**
   * Returns the factorial of the specified integer n.
   *
   * @param {number} n
   * @return {number}
   *
   * @example:
   *   1  => 1
   *   5  => 120
   *   10 => 3628800
   */
  function getFactorial(n) {
    let result = 1;
  
    for (let i = 1; i <= n; i += 1) {
      result *= i;
    }
  
    return result;
  }
  
  /**
   * Returns the sum of integer numbers between n1 and n2 (inclusive).
   *
   * @param {number} n1
   * @param {number} n2
   * @return {number}
   *
   * @example:
   *   1,2   =>  3  ( = 1+2 )
   *   5,10  =>  45 ( = 5+6+7+8+9+10 )
   *   -1,1  =>  0  ( = -1 + 0 + 1 )
   */
  function getSumBetweenNumbers(n1, n2) {
    let result = 0;
  
    for (let i = n1; i <= n2; i += 1) {
      result += i;
    }
  
    return result;
  }
  
  /**
   * Returns true, if a triangle can be built with the specified sides a, b, c
   * and false in any other ways.
   *
   * @param {number} a
   * @param {number} b
   * @param {number} c
   * @return {bool}
   *
   * @example:
   *   1,2,3    =>  false
   *   3,4,5    =>  true
   *   10,1,1   =>  false
   *   10,10,10 =>  true
   */
  function isTriangle(a, b, c) {
    if (a + b <= c || a + c <= b || c + b <= a) return false;
    return true;
  }
  
  /**
   * Returns true, if two specified axis-aligned rectangles overlap, otherwise false.
   * Each rectangle representing by object
   *  {
   *     top: 5,
   *     left: 5,
   *     width: 20,
   *     height: 10
   *  }
   *
   *  (5;5)
   *     -------------
   *     |           |
   *     |           |  height = 10
   *     -------------
   *        width=20
   *
   * NOTE: Please use canvas coordinate space (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes#The_grid),
   * it differs from Cartesian coordinate system.
   *
   * @param {object} rect1
   * @param {object} rect2
   * @return {bool}
   *
   * @example:
   *   { top: 0, left: 0, width: 10, height: 10 },
   *   { top: 5, left: 5, width: 20, height: 20 }    =>  true
   *
   *   { top: 0, left: 0, width: 10, height: 10 },
   *   { top:20, left:20, width: 20, height: 20 }    =>  false
   *
   */
  function doRectanglesOverlap(rect1, rect2) {
    if (rect1.left + rect1.width < rect2.left) return false;
    if (rect1.top + rect1.height < rect2.top) return false;
    // if (rect1.top + rect1.width >= rect2.top) return true;
    return true;
  }
  
  /**
   * Returns true, if point lies inside the circle, otherwise false.
   * Circle is an object of
   *  {
   *     center: {
   *       x: 5,
   *       y: 5
   *     },
   *     radius: 20
   *  }
   *
   * Point is object of
   *  {
   *     x: 5,
   *     y: 5
   *  }
   *
   * @param {object} circle
   * @param {object} point
   * @return {bool}
   *
   * @example:
   *   { center: { x:0, y:0 }, radius:10 },  { x:0, y:0 }     => true
   *   { center: { x:0, y:0 }, radius:10 },  { x:10, y:10 }   => false
   *
   */
  function isInsideCircle(circle, point) {
    const { x, y } = circle.center;
    const dist = Math.sqrt((point.x - x) ** 2 + (point.y - y) ** 2);
    if (dist >= circle.radius) return false;
    return true;
  }
  
  /**
   * Returns the first non repeated char in the specified strings otherwise returns null.
   *
   * @param {string} str
   * @return {string}
   *
   * @example:
   *   'The quick brown fox jumps over the lazy dog' => 'T'
   *   'abracadabra'  => 'c'
   *   'entente' => null
   */
  function findFirstSingleChar(str) {
    const res = [];
    str.split('').forEach((el) => {
      if (str.indexOf(el) === str.lastIndexOf(el)) {
        res.push(el);
      }
    });
    return res[0] || null;
  }
  
  /**
   * Returns the string representation of math interval,
   * specified by two points and include / exclude flags.
   * See the details: https://en.wikipedia.org/wiki/Interval_(mathematics)
   *
   * Please take attention, that the smaller number should be the first in the notation
   *
   * @param {number} a
   * @param {number} b
   * @param {bool} isStartIncluded
   * @param {bool} isEndIncluded
   * @return {string}
   *
   * @example
   *   0, 1, true, true   => '[0, 1]'
   *   0, 1, true, false  => '[0, 1)'
   *   0, 1, false, true  => '(0, 1]'
   *   0, 1, false, false => '(0, 1)'
   * Smaller number has to be first :
   *   5, 3, true, true   => '[3, 5]'
   *
   */
  function getIntervalString(a, b, isStartIncluded, isEndIncluded) {
    const f1 = isStartIncluded ? '[' : '(';
    const f4 = isEndIncluded ? ']' : ')';
    const f2 = a < b ? a : b;
    const f3 = a < b ? b : a;
    return `${f1}${f2}, ${f3}${f4}`;
  }
  
  /**
   * Reverse the specified string (put all chars in reverse order)
   *
   * @param {string} str
   * @return {string}
   *
   * @example:
   * 'The quick brown fox jumps over the lazy dog' => 'god yzal eht revo spmuj xof nworb kciuq ehT'
   * 'abracadabra' => 'arbadacarba'
   * 'rotator' => 'rotator'
   * 'noon' => 'noon'
   */
  function reverseString(str) {
    return str
      .split('')
      .reverse()
      .join('');
  }
  
  /**
   * Reverse the specified integer number (put all digits in reverse order)
   *
   * @param {number} num
   * @return {number}
   *
   * @example:
   *   12345 => 54321
   *   1111  => 1111
   *   87354 => 45378
   *   34143 => 34143
   */
  function reverseInteger(num) {
    return Number(
      num
        .toString()
        .split('')
        .reverse()
        .join(''),
    );
  }
  
  /**
   * Validates the CCN (credit card number) and return true if CCN is valid
   * and false otherwise.
   *
   * See algorithm here : https://en.wikipedia.org/wiki/Luhn_algorithm
   *
   * @param {number} cnn
   * @return {boolean}
   *
   * @example:
   *   79927398713      => true
   *   4012888888881881 => true
   *   5123456789012346 => true
   *   378282246310005  => true
   *   371449635398431  => true
   *
   *   4571234567890111 => false
   *   5436468789016589 => false
   *   4916123456789012 => false
   */
  function isCreditCardNumber(ccn) {
    const luhn = ccn
      .toString()
      .split('')
      .reverse()
      .map((el) => Number(el))
      .map((el, i) => (i % 2 ? el * 2 : el))
      .map((el) => (el > 9 ? (el % 10) + 1 : el))
      .reduce((acc, el) => acc + el);
    return luhn % 10 === 0;
  }
  
  /**
   * Returns the digital root of integer:
   *   step1 : find sum of all digits
   *   step2 : if sum > 9 then goto step1 otherwise return the sum
   *
   * @param {number} n
   * @return {number}
   *
   * @example:
   *   12345 ( 1+2+3+4+5 = 15, 1+5 = 6) => 6
   *   23456 ( 2+3+4+5+6 = 20, 2+0 = 2) => 2
   *   10000 ( 1+0+0+0+0 = 1 ) => 1
   *   165536 (1+6+5+5+3+6 = 26,  2+6 = 8) => 8
   */
  function getDigitalRoot(num) {
    let res = num;
    do {
      res = res
        .toString()
        .split('')
        .map((el) => Number(el))
        .reduce((acc, el) => acc + el);
    } while (res > 9);
    return res;
  }
  
  /**
   * Returns true if the specified string has the balanced brackets and false otherwise.
   * Balanced means that is, whether it consists entirely of pairs of opening/closing brackets
   * (in that order), none of which mis-nest.
   * Brackets include [],(),{},<>
   *
   * @param {string} str
   * @return {boolean}
   *
   * @example:
   *   '' => true
   *   '[]'  => true
   *   '{}'  => true
   *   '()   => true
   *   '[[]' => false
   *   ']['  => false
   *   '[[][][[]]]' => true
   *   '[[][]][' => false
   *   '{)' = false
   *   '{[(<{[]}>)]}' = true
   */
  function isBracketsBalanced(str) {
    const stack = [];
    const map = {
      '[': ']',
      '(': ')',
      '{': '}',
      '<': '>',
    };
    str.split('').forEach((el) => {
      const lastInStack = stack[stack.length - 1] || null;
      if (map[lastInStack] === el) {
        stack.pop();
      } else {
        stack.push(el);
      }
    });
    return !stack.length;
  }
  
  /**
   * Returns the string with n-ary (binary, ternary, etc, where n <= 10)
   * representation of specified number.
   * See more about
   * https://en.wikipedia.org/wiki/Binary_number
   * https://en.wikipedia.org/wiki/Ternary_numeral_system
   * https://en.wikipedia.org/wiki/Radix
   *
   * @param {number} num
   * @param {number} n, radix of the result
   * @return {string}
   *
   * @example:
   *   1024, 2  => '10000000000'
   *   6561, 3  => '100000000'
   *    365, 2  => '101101101'
   *    365, 3  => '111112'
   *    365, 4  => '11231'
   *    365, 10 => '365'
   */
  function toNaryString(num, n) {
    return num.toString(n);
  }
  
  /**
   * Returns the commom directory path for specified array of full filenames.
   *
   * @param {array} pathes
   * @return {string}
   *
   * @example:
   *   ['/web/images/image1.png', '/web/images/image2.png']  => '/web/images/'
   *   ['/web/assets/style.css', '/web/scripts/app.js',  'home/setting.conf'] => ''
   *   ['/web/assets/style.css', '/.bin/mocha',  '/read.me'] => '/'
   *   ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'] => '/'
   */
  function getCommonDirectoryPath(pathes) {
    const f1 = pathes.map((el) => {
      const arr = el.split('/');
      arr.pop();
      return arr.map((elem) => `${elem}/`);
    });
    let res = f1[0];
  
    for (let i = 1; i < f1.length; i += 1) {
      for (let j = 0; j < f1[i].length; j += 1) {
        if (f1[i][j] !== res[j]) {
          res = res.slice(0, j);
        }
      }
    }
    return res.join('');
  }
  
  /**
   * Returns the product of two specified matrixes.
   * See details: https://en.wikipedia.org/wiki/Matrix_multiplication
   *
   * @param {array} m1
   * @param {array} m2
   * @return {array}
   *
   * @example:
   *   [[ 1, 0, 0 ],       [[ 1, 2, 3 ],           [[ 1, 2, 3 ],
   *    [ 0, 1, 0 ],   X    [ 4, 5, 6 ],     =>     [ 4, 5, 6 ],
   *    [ 0, 0, 1 ]]        [ 7, 8, 9 ]]            [ 7, 8, 9 ]]
   *
   *                        [[ 4 ],
   *   [[ 1, 2, 3]]    X     [ 5 ],          =>     [[ 32 ]]
   *                         [ 6 ]]
   *
   */
  function getMatrixProduct(a, b) {
    const aRows = a.length;
    const aCols = a[0].length;
    const bCols = b[0].length;
    const m = new Array(aRows);
  
    for (let r = 0; r < aRows; r += 1) {
      m[r] = new Array(bCols);
      for (let c = 0; c < bCols; c += 1) {
        m[r][c] = 0;
        for (let i = 0; i < aCols; i += 1) {
          m[r][c] += a[r][i] * b[i][c];
        }
      }
    }
    return m;
  }
  
  /**
   * Returns the evaluation of the specified tic-tac-toe position.
   * See the details: https://en.wikipedia.org/wiki/Tic-tac-toe
   *
   * Position is provides as 3x3 array with the following values: 'X','0', undefined
   * Function should return who is winner in the current position according to the game rules.
   * The result can be: 'X','0',undefined
   *
   * @param {array} position
   * @return {string}
   *
   * @example
   *
   *   [[ 'X',   ,'0' ],
   *    [    ,'X','0' ],       =>  'X'
   *    [    ,   ,'X' ]]
   *
   *   [[ '0','0','0' ],
   *    [    ,'X',    ],       =>  '0'
   *    [ 'X',   ,'X' ]]
   *
   *   [[ '0','X','0' ],
   *    [    ,'X',    ],       =>  undefined
   *    [ 'X','0','X' ]]
   *
   *   [[    ,   ,    ],
   *    [    ,   ,    ],       =>  undefined
   *    [    ,   ,    ]]
   *
   */
  function evaluateTicTacToePosition(arr) {
    const iks = (el) => el === 'X';
    const zero = (el) => el === '0';
  
    const winArr = [];
    for (let i = 0; i < 3; i += 1) {
      const col = [];
      const raw = [];
      for (let j = 0; j < 3; j += 1) {
        col.push(arr[j][i]);
        raw.push(arr[i][j]);
      }
      winArr.push(col);
      winArr.push(raw);
    }
    winArr.push([arr[0][0], arr[1][1], arr[2][2]]);
    winArr.push([arr[0][2], arr[1][1], arr[2][0]]);
  
    for (let i = 0; i < winArr.length; i += 1) {
      if (winArr[i].every(iks) || winArr[i].every(zero)) {
        return winArr[i][0];
      }
    }
    return undefined;
  }
  
  module.exports = {
    getFizzBuzz,
    getFactorial,
    getSumBetweenNumbers,
    isTriangle,
    doRectanglesOverlap,
    isInsideCircle,
    findFirstSingleChar,
    getIntervalString,
    reverseString,
    reverseInteger,
    isCreditCardNumber,
    getDigitalRoot,
    isBracketsBalanced,
    toNaryString,
    getCommonDirectoryPath,
    getMatrixProduct,
    evaluateTicTacToePosition,
  };
  