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) {
    let result = '';
  
    if (num % 3 === 0) {
      result = 'Fizz';
    }
  
    if (num % 5 === 0) {
      result += 'Buzz';
    }
  
    return result === '' ? num : result;
  }
  
  
  /**
   * Returns the factorial of the specified integer n.
   *
   * @param {number} n
   * @return {number}
   *
   * @example:
   *   1  => 1
   *   5  => 120
   *   10 => 3628800
   */
  function getFactorial(n) {
    if (n !== 1) {
      return n * getFactorial(n - 1);
    }
  
    return 1;
  }
  
  
  /**
   * 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) {
    if (n2 > n1) {
      return n2 + getSumBetweenNumbers(n1, n2 - 1);
    }
  
    return n1;
  }
  
  
  /**
   * 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) {
    const sides = [a, b, c].sort();
    const maxSide = sides.pop();
  
    return sides.reduce((sum, item) => sum + item, 0) > maxSide;
  }
  
  
  /**
   * 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) {
    const rectangleCorners = ({
      top, left, width, height,
    }) => {
      const x2 = left + width;
      const y2 = top + height;
  
      return [
        { x: left, y: top },
        { x: x2, y: top },
        { x: left, y: y2 },
        { x: x2, y: y2 },
      ];
    };
  
    const rectCoords = ({
      top, left, width, height,
    }) => ({
      x1: left,
      x2: left + width,
      y1: top,
      y2: top + height,
    });
  
    const isInRange = ({
      x, y,
    }, {
      x1, x2, y1, y2,
    }) => x >= x1 && x <= x2 && y > y1 && y < y2;
  
    const rect1Coords = rectCoords(rect1);
    const rect2Coords = rectCoords(rect2);
  
    const isFirstInSecond = rectangleCorners(rect1).some((corner) => isInRange(corner, rect2Coords));
    const isSecondInFirst = rectangleCorners(rect2).some((corner) => isInRange(corner, rect1Coords));
  
    return isFirstInSecond || isSecondInFirst;
  }
  
  
  /**
   * 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 { center, radius } = circle;
    const { x, y } = point;
  
    const side = (pointA, pointB) => Math.abs(pointA - pointB);
    const length = Math.sqrt(side(center.x, x) ** 2 + side(center.y, y) ** 2);
  
    return length < radius;
  }
  
  
  /**
   * 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 charList = str.split('');
    const charSet = [...new Set(charList)];
  
    const isAlone = (checkChar) => charList.filter((char) => char === checkChar).length === 1;
  
    return charSet.find(isAlone);
  }
  
  
  /**
   * 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 startBracket = isStartIncluded ? '[' : '(';
    const endBracket = isEndIncluded ? ']' : ')';
    const orederPoint = a > b ? [b, a] : [a, b];
  
    return startBracket + orederPoint.join(', ') + endBracket;
  }
  
  
  /**
   * 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].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 [...num.toString()].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 checkArr = [...ccn.toString()].reverse();
    const sumCheckArr = checkArr.map((item, ind) => {
      if (ind % 2 !== 0) {
        const double = Number(item) * 2;
        return double > 9 ? 1 + (double % 10) : double;
      }
      return Number(item);
    });
    const checkDigit = sumCheckArr.reduce((acc, sum) => acc + sum);
  
    return checkDigit % 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) {
    const root = [...num.toString()].reduce((acc, digit) => acc + Number(digit), 0);
  
    if (root > 9) {
      return getDigitalRoot(root);
    }
    return root;
  }
  
  
  /**
   * 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 pairs = {
      '[': ']',
      '{': '}',
      '(': ')',
      '<': '>',
    };
    const openBrackets = Object.keys(pairs);
    const closeBrackets = Object.values(pairs);
  
    const bracketsStack = [];
    let isPairsOk = true;
  
    [...str].forEach((char) => {
      if (isPairsOk) {
        if (openBrackets.includes(char)) {
          bracketsStack.push(char);
        }
  
        if (closeBrackets.includes(char)) {
          if (pairs[bracketsStack.pop()] !== char) {
            isPairsOk = false;
          }
        }
      }
    });
  
    return isPairsOk && bracketsStack.length === 0;
  }
  
  
  /**
   * 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, step = 0) {
    if (num > 0 || (num === 0 && step === 0)) {
      const div = Math.floor(num / n);
      const mod = num % n;
      return toNaryString(div, n, step + 1).concat(mod);
    }
  
    return '';
  }
  
  
  /**
   * 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 parsePathList = pathes.map((path) => path.match(/(\/)|([a-z0-9.]+)/g));
  
    let resultString = '';
    let isPairsOk = true;
  
    parsePathList[0].forEach((pointOfPath, index) => {
      if (isPairsOk) {
        const allListVariants = parsePathList.map((path) => path[index]);
  
        if (allListVariants.every((partPath) => partPath === pointOfPath)) {
          resultString += pointOfPath;
        } else {
          isPairsOk = false;
        }
      }
    });
  
    return resultString;
  }
  
  
  /**
   * 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(m1, m2) {
    const m = m1.length;
    const p = m2[0].length;
    const n = m1[0].length;
    const matrixC = [...new Array(m)].map(() => new Array(p).fill(0));
  
    return matrixC.map((mString, iInd) => mString.map((cPoint, jInd) => {
      let pointCount = 0;
      for (let k = 0; k < n; k += 1) {
        pointCount += (m1[iInd][k] * m2[k][jInd]);
      }
      return pointCount;
    }));
  }
  
  
  /**
   * 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(position) {
    const m = position.length;
    const n = position[0].length;
  
    position.forEach((line) => {
      line.push('');
      line.unshift('');
    });
  
    position.unshift(['', '', '', '', '']);
    position.push(['', '', '', '', '']);
  
    const isLine = (x, y) => {
      const inPoint = position[x][y];
  
      if (position[x - 1][y] === inPoint && position[x + 1][y] === inPoint) {
        return true;
      }
  
      if (position[x][y - 1] === inPoint && position[x][y + 1] === inPoint) {
        return true;
      }
  
      if (position[x - 1][y - 1] === inPoint && position[x + 1][y + 1] === inPoint) {
        return true;
      }
  
      if (position[x + 1][y - 1] === inPoint && position[x - 1][y + 1] === inPoint) {
        return true;
      }
  
      return false;
    };
  
    let result;
  
    for (let i = 1; i <= m; i += 1) {
      for (let j = 1; j <= n; j += 1) {
        const point = position[i][j];
        if (point) {
          if (isLine(i, j)) {
            result = point;
            break;
          }
        }
      }
    }
  
    return result;
  }
  
  
  module.exports = {
    getFizzBuzz,
    getFactorial,
    getSumBetweenNumbers,
    isTriangle,
    doRectanglesOverlap,
    isInsideCircle,
    findFirstSingleChar,
    getIntervalString,
    reverseString,
    reverseInteger,
    isCreditCardNumber,
    getDigitalRoot,
    isBracketsBalanced,
    toNaryString,
    getCommonDirectoryPath,
    getMatrixProduct,
    evaluateTicTacToePosition,
  };
  