
(function (root) {
  'use strict';
  if (root.InstinctorQR) return;

  var EXP = new Array(512);
  var LOG = new Array(256);
  (function () {
    var x = 1;
    for (var i = 0; i < 255; i++) {
      EXP[i] = x;
      LOG[x] = i;
      x <<= 1;
      if (x & 0x100) x ^= 0x11d;
    }
    for (var j = 255; j < 512; j++) EXP[j] = EXP[j - 255];
  })();
  function gfMul(a, b) { return (a === 0 || b === 0) ? 0 : EXP[LOG[a] + LOG[b]]; }

  
  
  var ECL = { L: 0, M: 1, Q: 2, H: 3 };
  var EC_BLOCKS = {
    1:  [[7,[[1,19]]],            [10,[[1,16]]],            [13,[[1,13]]],            [17,[[1,9]]]],
    2:  [[10,[[1,34]]],           [16,[[1,28]]],            [22,[[1,22]]],            [28,[[1,16]]]],
    3:  [[15,[[1,55]]],           [26,[[1,44]]],            [18,[[2,17]]],            [22,[[2,13]]]],
    4:  [[20,[[1,80]]],           [18,[[2,32]]],            [26,[[2,24]]],            [16,[[4,9]]]],
    5:  [[26,[[1,108]]],          [24,[[2,43]]],            [18,[[2,15],[2,16]]],     [22,[[2,11],[2,12]]]],
    6:  [[18,[[2,68]]],           [16,[[4,27]]],            [24,[[4,19]]],            [28,[[4,15]]]],
    7:  [[20,[[2,78]]],           [18,[[4,31]]],            [18,[[2,14],[4,15]]],     [26,[[4,13],[1,14]]]],
    8:  [[24,[[2,97]]],           [22,[[2,38],[2,39]]],     [22,[[4,18],[2,19]]],     [26,[[4,14],[2,15]]]],
    9:  [[30,[[2,116]]],          [22,[[3,36],[2,37]]],     [20,[[4,16],[4,17]]],     [24,[[4,12],[4,13]]]],
    10: [[18,[[2,68],[2,69]]],    [26,[[4,43],[1,44]]],     [24,[[6,19],[2,20]]],     [28,[[6,15],[2,16]]]],
    11: [[20,[[4,81]]],           [30,[[1,50],[4,51]]],     [28,[[4,22],[4,23]]],     [24,[[3,12],[8,13]]]],
    12: [[24,[[2,92],[2,93]]],    [22,[[6,36],[2,37]]],     [26,[[4,20],[6,21]]],     [28,[[7,14],[4,15]]]],
    13: [[26,[[4,107]]],          [22,[[8,37],[1,38]]],     [24,[[8,20],[4,21]]],     [22,[[12,11],[4,12]]]],
    14: [[30,[[3,115],[1,116]]],  [24,[[4,40],[5,41]]],     [20,[[11,16],[5,17]]],    [24,[[11,12],[5,13]]]],
    15: [[22,[[5,87],[1,88]]],    [24,[[5,41],[5,42]]],     [30,[[5,24],[7,25]]],     [24,[[11,12],[7,13]]]],
    16: [[24,[[5,98],[1,99]]],    [28,[[7,45],[3,46]]],     [24,[[15,19],[2,20]]],    [30,[[3,15],[13,16]]]],
    17: [[28,[[1,107],[5,108]]],  [28,[[10,46],[1,47]]],    [28,[[1,22],[15,23]]],    [28,[[2,14],[17,15]]]],
    18: [[30,[[5,120],[1,121]]],  [26,[[9,43],[4,44]]],     [28,[[17,22],[1,23]]],    [28,[[2,14],[19,15]]]],
    19: [[28,[[3,113],[4,114]]],  [26,[[3,44],[11,45]]],    [26,[[17,21],[4,22]]],    [26,[[9,13],[16,14]]]],
    20: [[28,[[3,107],[5,108]]],  [26,[[3,41],[13,42]]],    [30,[[15,24],[5,25]]],    [28,[[15,15],[10,16]]]]
  };

  var ALIGN = {
    1: [], 2: [6,18], 3: [6,22], 4: [6,26], 5: [6,30], 6: [6,34],
    7: [6,22,38], 8: [6,24,42], 9: [6,26,46], 10: [6,28,50],
    11: [6,30,54], 12: [6,32,58], 13: [6,34,62], 14: [6,26,46,66],
    15: [6,26,48,70], 16: [6,26,50,74], 17: [6,30,54,78], 18: [6,30,56,82],
    19: [6,30,58,86], 20: [6,34,62,90]
  };
  var MAX_VERSION = 20;

  function dataCodewords(version, level) {
    var groups = EC_BLOCKS[version][level][1], n = 0;
    for (var i = 0; i < groups.length; i++) n += groups[i][0] * groups[i][1];
    return n;
  }
  function charCountBits(version) { return version <= 9 ? 8 : 16; }

  function toUtf8Bytes(str) {
    var out = [], i, c;
    for (i = 0; i < str.length; i++) {
      c = str.charCodeAt(i);
      if (c < 0x80) { out.push(c); }
      else if (c < 0x800) { out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
      else if (c >= 0xd800 && c <= 0xdbff) { 
        var c2 = str.charCodeAt(++i);
        var cp = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff);
        out.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
      } else { out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
    }
    return out;
  }

  function rsEncode(data, ecLen) {

    var gen = [1];
    for (var i = 0; i < ecLen; i++) {
      var next = new Array(gen.length + 1).fill(0);
      for (var j = 0; j < gen.length; j++) {
        next[j] ^= gen[j];                    
        next[j + 1] ^= gfMul(gen[j], EXP[i]); 
      }
      gen = next;
    }
    
    var rem = data.slice().concat(new Array(ecLen).fill(0));
    for (var k = 0; k < data.length; k++) {
      var coeff = rem[k];
      if (coeff !== 0) {
        for (var l = 0; l < gen.length; l++) rem[k + l] ^= gfMul(gen[l], coeff);
      }
    }
    return rem.slice(data.length);
  }

  function BitBuffer() { this.bits = []; }
  BitBuffer.prototype.put = function (val, len) {
    for (var i = len - 1; i >= 0; i--) this.bits.push((val >> i) & 1);
  };

  function buildCodewords(text, level, minVersion) {
    var bytes = toUtf8Bytes(text);
    
    var version = -1;
    for (var v = Math.max(1, minVersion || 1); v <= MAX_VERSION; v++) {
      var cap = dataCodewords(v, level) * 8;
      var need = 4 + charCountBits(v) + bytes.length * 8;
      if (need <= cap) { version = v; break; }
    }
    if (version === -1) throw new Error('InstinctorQR: content too long for QR version ' + MAX_VERSION);

    var totalData = dataCodewords(version, level);
    var bb = new BitBuffer();
    bb.put(0x4, 4);                              
    bb.put(bytes.length, charCountBits(version)); 
    for (var i = 0; i < bytes.length; i++) bb.put(bytes[i], 8);
    
    var cap2 = totalData * 8;
    for (var t = 0; t < 4 && bb.bits.length < cap2; t++) bb.bits.push(0);
    
    while (bb.bits.length % 8 !== 0) bb.bits.push(0);
    
    var pad = [0xec, 0x11], pi = 0;
    while (bb.bits.length < cap2) { bb.put(pad[pi & 1], 8); pi++; }
    
    var dataCw = [];
    for (var b = 0; b < bb.bits.length; b += 8) {
      var byte = 0;
      for (var k = 0; k < 8; k++) byte = (byte << 1) | bb.bits[b + k];
      dataCw.push(byte);
    }

    var ecPerBlock = EC_BLOCKS[version][level][0];
    var groups = EC_BLOCKS[version][level][1];
    var dataBlocks = [], ecBlocks = [], offset = 0;
    for (var g = 0; g < groups.length; g++) {
      for (var nb = 0; nb < groups[g][0]; nb++) {
        var dcount = groups[g][1];
        var block = dataCw.slice(offset, offset + dcount);
        offset += dcount;
        dataBlocks.push(block);
        ecBlocks.push(rsEncode(block, ecPerBlock));
      }
    }

    var result = [], col, blk;
    var maxData = 0;
    for (blk = 0; blk < dataBlocks.length; blk++) maxData = Math.max(maxData, dataBlocks[blk].length);
    for (col = 0; col < maxData; col++) {
      for (blk = 0; blk < dataBlocks.length; blk++) {
        if (col < dataBlocks[blk].length) result.push(dataBlocks[blk][col]);
      }
    }
    for (col = 0; col < ecPerBlock; col++) {
      for (blk = 0; blk < ecBlocks.length; blk++) result.push(ecBlocks[blk][col]);
    }
    return { codewords: result, version: version };
  }

  function buildMatrix(codewords, version, level) {
    var size = version * 4 + 17;
    var m = [], reserved = [];
    for (var r = 0; r < size; r++) { m.push(new Array(size).fill(null)); reserved.push(new Array(size).fill(false)); }

    function setF(r, c, val) { m[r][c] = val ? 1 : 0; reserved[r][c] = true; }

    function finder(r, c) {
      for (var dr = -1; dr <= 7; dr++) {
        for (var dc = -1; dc <= 7; dc++) {
          var rr = r + dr, cc = c + dc;
          if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue;
          var inRing = (dr >= 0 && dr <= 6 && (dc === 0 || dc === 6)) ||
                       (dc >= 0 && dc <= 6 && (dr === 0 || dr === 6));
          var inCore = (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4);
          setF(rr, cc, inRing || inCore);
        }
      }
    }
    finder(0, 0); finder(0, size - 7); finder(size - 7, 0);

    for (var i = 8; i < size - 8; i++) {
      setF(6, i, i % 2 === 0);
      setF(i, 6, i % 2 === 0);
    }

    
    
    var pos = ALIGN[version];
    var last = pos.length - 1;
    for (var a = 0; a < pos.length; a++) {
      for (var b = 0; b < pos.length; b++) {
        if ((a === 0 && b === 0) || (a === 0 && b === last) || (a === last && b === 0)) continue;
        var ar = pos[a], ac = pos[b];
        for (var yy = -2; yy <= 2; yy++) {
          for (var xx = -2; xx <= 2; xx++) {
            var mag = Math.max(Math.abs(yy), Math.abs(xx));
            setF(ar + yy, ac + xx, mag !== 1);
          }
        }
      }
    }

    setF(size - 8, 8, true);

    function reserveFormat() {
      for (var k = 0; k <= 8; k++) {
        if (!reserved[8][k]) reserved[8][k] = true;
        if (!reserved[k][8]) reserved[k][8] = true;
      }
      for (var j = 0; j < 8; j++) {
        reserved[8][size - 1 - j] = true;
        reserved[size - 1 - j][8] = true;
      }
    }
    reserveFormat();

    if (version >= 7) {
      for (var vr = 0; vr < 6; vr++) {
        for (var vc = 0; vc < 3; vc++) {
          reserved[vr][size - 11 + vc] = true;
          reserved[size - 11 + vc][vr] = true;
        }
      }
    }

    var bitIdx = 0;
    var totalBits = codewords.length * 8;
    function nextBit() {
      if (bitIdx >= totalBits) return 0; 
      var byte = codewords[bitIdx >> 3];
      var bit = (byte >> (7 - (bitIdx & 7))) & 1;
      bitIdx++;
      return bit;
    }
    var upward = true;
    for (var colPair = size - 1; colPair > 0; colPair -= 2) {
      if (colPair === 6) colPair = 5; 
      for (var rowStep = 0; rowStep < size; rowStep++) {
        var row = upward ? (size - 1 - rowStep) : rowStep;
        for (var cSub = 0; cSub < 2; cSub++) {
          var col = colPair - cSub;
          if (reserved[row][col]) continue;
          m[row][col] = nextBit();
        }
      }
      upward = !upward;
    }

    return { m: m, reserved: reserved, size: size };
  }

  function maskFn(id, r, c) {
    switch (id) {
      case 0: return (r + c) % 2 === 0;
      case 1: return r % 2 === 0;
      case 2: return c % 3 === 0;
      case 3: return (r + c) % 3 === 0;
      case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
      case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
      case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
      case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
    }
    return false;
  }

  function applyMask(base, id) {
    var size = base.size;
    var out = [];
    for (var r = 0; r < size; r++) {
      out.push(base.m[r].slice());
      for (var c = 0; c < size; c++) {
        if (!base.reserved[r][c] && maskFn(id, r, c)) out[r][c] ^= 1;
      }
    }
    return out;
  }

  function formatBits(level, maskId) {
    var levelBits = { 1: 0, 0: 1, 3: 2, 2: 3 }[level]; 
    var data = (levelBits << 3) | maskId;
    var rem = data;
    for (var i = 0; i < 10; i++) {
      rem = (rem << 1);
      if (rem & (1 << 10)) rem ^= 0x537; 
    }
    var bits = ((data << 10) | rem) ^ 0x5412; 
    return bits & 0x7fff;
  }

  function versionBits(version) {
    var rem = version;
    for (var i = 0; i < 12; i++) {
      rem = (rem << 1);
      if (rem & (1 << 12)) rem ^= 0x1f25;
    }
    return ((version << 12) | rem) & 0x3ffff;
  }

  function placeFormatAndVersion(grid, size, level, maskId, version) {
    var fmt = formatBits(level, maskId);
    
    for (var i = 0; i < 15; i++) {
      var mod = (fmt >> i) & 1;
      
      if (i < 6) grid[i][8] = mod;
      else if (i < 8) grid[i + 1][8] = mod;
      else grid[size - 15 + i][8] = mod;
      
      if (i < 8) grid[8][size - i - 1] = mod;
      else if (i < 9) grid[8][15 - i - 1 + 1] = mod;
      else grid[8][15 - i - 1] = mod;
    }
    grid[size - 8][8] = 1; 

    if (version >= 7) {
      var vb = versionBits(version);
      for (var p = 0; p < 18; p++) {
        var b = (vb >> p) & 1;
        var rr = Math.floor(p / 3);
        var cc = p % 3;
        grid[rr][size - 11 + cc] = b;
        grid[size - 11 + cc][rr] = b;
      }
    }
  }

  function penalty(grid, size) {
    var score = 0, r, c, i;
    
    for (r = 0; r < size; r++) {
      var runC = 1, runR = 1;
      for (c = 1; c < size; c++) {
        if (grid[r][c] === grid[r][c - 1]) { runC++; if (runC === 5) score += 3; else if (runC > 5) score++; }
        else runC = 1;
        if (grid[c][r] === grid[c - 1][r]) { runR++; if (runR === 5) score += 3; else if (runR > 5) score++; }
        else runR = 1;
      }
    }
    
    for (r = 0; r < size - 1; r++) {
      for (c = 0; c < size - 1; c++) {
        var v = grid[r][c];
        if (v === grid[r][c + 1] && v === grid[r + 1][c] && v === grid[r + 1][c + 1]) score += 3;
      }
    }
    
    var pat1 = [1,0,1,1,1,0,1,0,0,0,0], pat2 = [0,0,0,0,1,0,1,1,1,0,1];
    function matchAt(line, idx) {
      var a = true, b = true;
      for (var q = 0; q < 11; q++) { if (line[idx + q] !== pat1[q]) a = false; if (line[idx + q] !== pat2[q]) b = false; }
      return a || b;
    }
    for (r = 0; r < size; r++) {
      var rowLine = grid[r];
      var colLine = [];
      for (i = 0; i < size; i++) colLine.push(grid[i][r]);
      for (c = 0; c <= size - 11; c++) {
        if (matchAt(rowLine, c)) score += 40;
        if (matchAt(colLine, c)) score += 40;
      }
    }
    
    var dark = 0;
    for (r = 0; r < size; r++) for (c = 0; c < size; c++) if (grid[r][c]) dark++;
    var percent = (dark * 100) / (size * size);
    var k5 = Math.floor(Math.abs(percent - 50) / 5);
    score += k5 * 10;
    return score;
  }

  function matrix(text, opts) {
    opts = opts || {};
    var level = ECL[(opts.ecc || 'M').toUpperCase()];
    if (level === undefined) level = ECL.M;
    var built = buildCodewords(String(text == null ? '' : text), level, opts.minVersion);
    var base = buildMatrix(built.codewords, built.version, level);

    var best = null, bestGrid = null, bestId = 0;
    for (var id = 0; id < 8; id++) {
      var grid = applyMask(base, id);
      placeFormatAndVersion(grid, base.size, level, id, built.version);
      var p = penalty(grid, base.size);
      if (best === null || p < best) { best = p; bestGrid = grid; bestId = id; }
    }
    var bool = [];
    for (var r = 0; r < base.size; r++) {
      bool.push([]);
      for (var c = 0; c < base.size; c++) bool[r].push(bestGrid[r][c] === 1);
    }
    return { size: base.size, modules: bool, version: built.version, ecc: (opts.ecc || 'M').toUpperCase(), mask: bestId };
  }

  function svg(text, opts) {
    opts = opts || {};
    var mtx = matrix(text, opts);
    var margin = opts.margin == null ? 4 : opts.margin;
    var moduleColor = opts.moduleColor || '#000000';
    var bgColor = opts.bgColor || '#ffffff';
    var count = mtx.size + margin * 2;
    var px = opts.size || 512;
    
    var d = '';
    for (var r = 0; r < mtx.size; r++) {
      for (var c = 0; c < mtx.size; c++) {
        if (mtx.modules[r][c]) d += 'M' + (c + margin) + ',' + (r + margin) + 'h1v1h-1z';
      }
    }
    var bg = bgColor === 'none' || bgColor === 'transparent'
      ? ''
      : '<rect width="' + count + '" height="' + count + '" fill="' + bgColor + '"/>';
    return '<svg xmlns="http://www.w3.org/2000/svg" width="' + px + '" height="' + px +
      '" viewBox="0 0 ' + count + ' ' + count + '" shape-rendering="crispEdges">' +
      bg + '<path d="' + d + '" fill="' + moduleColor + '"/></svg>';
  }

  function render(el, text, opts) {
    if (typeof el === 'string') el = root.document.getElementById(el);
    if (!el) return null;
    el.innerHTML = svg(text, opts);
    return el;
  }

  function pngDataUrl(text, opts) {
    opts = opts || {};
    var markup = svg(text, opts);
    var size = opts.size || 512;
    return new Promise(function (resolve, reject) {
      try {
        var img = new root.Image();
        var svgUrl = 'data:image/svg+xml;base64,' + root.btoa(unescape(encodeURIComponent(markup)));
        img.onload = function () {
          var canvas = root.document.createElement('canvas');
          canvas.width = size; canvas.height = size;
          var ctx = canvas.getContext('2d');
          if ((opts.bgColor || '#ffffff') !== 'none' && opts.bgColor !== 'transparent') {
            ctx.fillStyle = opts.bgColor || '#ffffff';
            ctx.fillRect(0, 0, size, size);
          }
          ctx.drawImage(img, 0, 0, size, size);
          resolve(canvas.toDataURL('image/png'));
        };
        img.onerror = reject;
        img.src = svgUrl;
      } catch (e) { reject(e); }
    });
  }

  root.InstinctorQR = {
    matrix: matrix,
    svg: svg,
    render: render,
    pngDataUrl: pngDataUrl,
    _formatBits: formatBits,
    _versionBits: versionBits
  };
})(typeof window !== 'undefined' ? window : this);
