text: 'use strict';
  
  var QRMath = require('./QRMath.js');
  var Polynomial = require('./Polynomial.js');
  
  /**
   * @module QRUtil
   * @author nuintun
   * @author Kazuhiko Arase
   */
  var N1 = 3;
  var N2 = 3;
  var N3 = 40;
  var N4 = 10;
  var ALIGNMENT_PATTERN_TABLE = [
      [],
      [6, 18],
      [6, 22],
      [6, 26],
      [6, 30],
      [6, 34],
      [6, 22, 38],
      [6, 24, 42],
      [6, 26, 46],
      [6, 28, 50],
      [6, 30, 54],
      [6, 32, 58],
      [6, 34, 62],
      [6, 26, 46, 66],
      [6, 26, 48, 70],
      [6, 26, 50, 74],
      [6, 30, 54, 78],
      [6, 30, 56, 82],
      [6, 30, 58, 86],
      [6, 34, 62, 90],
      [6, 28, 50, 72, 94],
      [6, 26, 50, 74, 98],
      [6, 30, 54, 78, 102],
      [6, 28, 54, 80, 106],
      [6, 32, 58, 84, 110],
      [6, 30, 58, 86, 114],
      [6, 34, 62, 90, 118],
      [6, 26, 50, 74, 98, 122],
      [6, 30, 54, 78, 102, 126],
      [6, 26, 52, 78, 104, 130],
      [6, 30, 56, 82, 108, 134],
      [6, 34, 60, 86, 112, 138],
      [6, 30, 58, 86, 114, 142],
      [6, 34, 62, 90, 118, 146],
      [6, 30, 54, 78, 102, 126, 150],
      [6, 24, 50, 76, 102, 128, 154],
      [6, 28, 54, 80, 106, 132, 158],
      [6, 32, 58, 84, 110, 136, 162],
      [6, 26, 54, 82, 110, 138, 166],
      [6, 30, 58, 86, 114, 142, 170]
  ];
  var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
  var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
  var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
  function getAlignmentPattern(version) {
      return ALIGNMENT_PATTERN_TABLE[version - 1];
  }
  function getErrorCorrectionPolynomial(errorCorrectionLength) {
      var e = new Polynomial.Polynomial([1]);
      for (var i = 0; i < errorCorrectionLength; i++) {
          e = e.multiply(new Polynomial.Polynomial([1, QRMath.gexp(i)]));
      }
      return e;
  }
  function getBCHDigit(data) {
      var digit = 0;
      while (data !== 0) {
          digit++;
          data >>>= 1;
      }
      return digit;
  }
  var G18_BCH = getBCHDigit(G18);
  function getBCHVersion(data) {
      var offset = data << 12;
      while (getBCHDigit(offset) - G18_BCH >= 0) {
          offset ^= G18 << (getBCHDigit(offset) - G18_BCH);
      }
      return (data << 12) | offset;
  }
  var G15_BCH = getBCHDigit(G15);
  function getBCHVersionInfo(data) {
      var offset = data << 10;
      while (getBCHDigit(offset) - G15_BCH >= 0) {
          offset ^= G15 << (getBCHDigit(offset) - G15_BCH);
      }
      return ((data << 10) | offset) ^ G15_MASK;
  }
  function applyMaskPenaltyRule1Internal(qrcode, isHorizontal) {
      var matrixSize = qrcode.getMatrixSize();
      var penalty = 0;
      for (var i = 0; i < matrixSize; i++) {
          var prevBit = null;
          var numSameBitCells = 0;
          for (var j = 0; j < matrixSize; j++) {
              var bit = isHorizontal ? qrcode.isDark(i, j) : qrcode.isDark(j, i);
              if (bit === prevBit) {
                  numSameBitCells++;
                  if (numSameBitCells === 5) {
                      penalty += N1;
                  }
                  else if (numSameBitCells > 5) {
                      penalty++;
                  }
              }
              else {
                  // set prev bit
                  prevBit = bit;
                  // include the cell itself
                  numSameBitCells = 1;
              }
          }
      }
      return penalty;
  }
  function applyMaskPenaltyRule1(qrcode) {
      return applyMaskPenaltyRule1Internal(qrcode, true) + applyMaskPenaltyRule1Internal(qrcode, false);
  }
  function applyMaskPenaltyRule2(qrcode) {
      var matrixSize = qrcode.getMatrixSize();
      var penalty = 0;
      for (var y = 0; y < matrixSize - 1; y++) {
          for (var x = 0; x < matrixSize - 1; x++) {
              var value = qrcode.isDark(y, x);
              if (value === qrcode.isDark(y, x + 1) && value === qrcode.isDark(y + 1, x) && value === qrcode.isDark(y + 1, x + 1)) {
                  penalty += N2;
              }
          }
      }
      return penalty;
  }
  function isFourWhite(qrcode, rangeIndex, from, to, isHorizontal) {
      from = Math.max(from, 0);
      to = Math.min(to, qrcode.getMatrixSize());
      for (var i = from; i < to; i++) {
          var value = isHorizontal ? qrcode.isDark(rangeIndex, i) : qrcode.isDark(i, rangeIndex);
          if (value) {
              return false;
          }
      }
      return true;
  }
  function applyMaskPenaltyRule3(qrcode) {
      var matrixSize = qrcode.getMatrixSize();
      var penalty = 0;
      for (var y = 0; y < matrixSize; y++) {
          for (var x = 0; x < matrixSize; x++) {
              if (x + 6 < matrixSize &&
                  qrcode.isDark(y, x) &&
                  !qrcode.isDark(y, x + 1) &&
                  qrcode.isDark(y, x + 2) &&
                  qrcode.isDark(y, x + 3) &&
                  qrcode.isDark(y, x + 4) &&
                  !qrcode.isDark(y, x + 5) &&
                  qrcode.isDark(y, x + 6) &&
                  (isFourWhite(qrcode, y, x - 4, x, true) || isFourWhite(qrcode, y, x + 7, x + 11, true))) {
                  penalty += N3;
              }
              if (y + 6 < matrixSize &&
                  qrcode.isDark(y, x) &&
                  !qrcode.isDark(y + 1, x) &&
                  qrcode.isDark(y + 2, x) &&
                  qrcode.isDark(y + 3, x) &&
                  qrcode.isDark(y + 4, x) &&
                  !qrcode.isDark(y + 5, x) &&
                  qrcode.isDark(y + 6, x) &&
                  (isFourWhite(qrcode, x, y - 4, y, false) || isFourWhite(qrcode, x, y + 7, y + 11, false))) {
                  penalty += N3;
              }
          }
      }
      return penalty;
  }
  function applyMaskPenaltyRule4(qrcode) {
      var matrixSize = qrcode.getMatrixSize();
      var numDarkCells = 0;
      for (var y = 0; y < matrixSize; y++) {
          for (var x = 0; x < matrixSize; x++) {
              if (qrcode.isDark(y, x)) {
                  numDarkCells++;
              }
          }
      }
      var numTotalCells = matrixSize * matrixSize;
      var fivePercentVariances = Math.floor(Math.abs(numDarkCells * 20 - numTotalCells * 10) / numTotalCells);
      return fivePercentVariances * N4;
  }
  /**
   * @function calculateMaskPenalty
   * @param {Encoder} qrcode
   * @see https://www.thonky.com/qr-code-tutorial/data-masking
   * @see https://github.com/zxing/zxing/blob/master/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
   */
  function calculateMaskPenalty(qrcode) {
      return (applyMaskPenaltyRule1(qrcode) +
          applyMaskPenaltyRule2(qrcode) +
          applyMaskPenaltyRule3(qrcode) +
          applyMaskPenaltyRule4(qrcode));
  }
  
  exports.calculateMaskPenalty = calculateMaskPenalty;
  exports.getAlignmentPattern = getAlignmentPattern;
  exports.getBCHVersion = getBCHVersion;
  exports.getBCHVersionInfo = getBCHVersionInfo;
  exports.getErrorCorrectionPolynomial = getErrorCorrectionPolynomial;
  