(function (window, document) {
    'use strict';

    var EXP = new Array(512);
    var LOG = new Array(256);
    (function initGalois() {
        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) {
        if (a === 0 || b === 0) {
            return 0;
        }
        return EXP[LOG[a] + LOG[b]];
    }

    function rsGenerator(degree) {
        var poly = [1];
        for (var i = 0; i < degree; i++) {
            var next = new Array(poly.length + 1).fill(0);
            for (var j = 0; j < poly.length; j++) {
                next[j] ^= poly[j];
                next[j + 1] ^= gfMul(poly[j], EXP[i]);
            }
            poly = next;
        }
        return poly;
    }

    function rsRemainder(data, degree) {
        var gen = rsGenerator(degree);
        var result = data.slice();
        for (var i = 0; i < degree; i++) {
            result.push(0);
        }
        for (var j = 0; j < data.length; j++) {
            var coef = result[j];
            if (coef !== 0) {
                for (var k = 0; k < gen.length; k++) {
                    result[j + k] ^= gfMul(gen[k], coef);
                }
            }
        }
        return result.slice(result.length - degree);
    }

    function utf8Bytes(text) {
        var encoded = unescape(encodeURIComponent(String(text)));
        var out = [];
        for (var i = 0; i < encoded.length; i++) {
            out.push(encoded.charCodeAt(i));
        }
        return out;
    }

    function pushBits(bits, value, length) {
        for (var i = length - 1; i >= 0; i--) {
            bits.push((value >>> i) & 1);
        }
    }

    function buildData(text) {
        var bytes = utf8Bytes(text);
        if (bytes.length > 106) {
            bytes = bytes.slice(0, 106);
        }
        var bits = [];
        pushBits(bits, 0x4, 4);
        pushBits(bits, bytes.length, 8);
        for (var i = 0; i < bytes.length; i++) {
            pushBits(bits, bytes[i], 8);
        }
        var capacityBits = 108 * 8;
        var terminator = Math.min(4, capacityBits - bits.length);
        pushBits(bits, 0, terminator);
        while (bits.length % 8 !== 0) {
            bits.push(0);
        }
        var data = [];
        for (var b = 0; b < bits.length; b += 8) {
            var val = 0;
            for (var n = 0; n < 8; n++) {
                val = (val << 1) | bits[b + n];
            }
            data.push(val);
        }
        var pad = [0xec, 0x11];
        var p = 0;
        while (data.length < 108) {
            data.push(pad[p % 2]);
            p++;
        }
        return data;
    }

    function makeMatrix(size) {
        var matrix = [];
        var reserved = [];
        for (var y = 0; y < size; y++) {
            matrix[y] = [];
            reserved[y] = [];
            for (var x = 0; x < size; x++) {
                matrix[y][x] = false;
                reserved[y][x] = false;
            }
        }
        return {matrix: matrix, reserved: reserved};
    }

    function setModule(ctx, x, y, dark, reserve) {
        if (x < 0 || y < 0 || y >= ctx.matrix.length || x >= ctx.matrix.length) {
            return;
        }
        ctx.matrix[y][x] = !!dark;
        if (reserve) {
            ctx.reserved[y][x] = true;
        }
    }

    function drawFinder(ctx, x, y) {
        for (var dy = -1; dy <= 7; dy++) {
            for (var dx = -1; dx <= 7; dx++) {
                var xx = x + dx;
                var yy = y + dy;
                var dark = dx >= 0 && dx <= 6 && dy >= 0 && dy <= 6 && (dx === 0 || dx === 6 || dy === 0 || dy === 6 || (dx >= 2 && dx <= 4 && dy >= 2 && dy <= 4));
                setModule(ctx, xx, yy, dark, true);
            }
        }
    }

    function drawAlignment(ctx, cx, cy) {
        for (var dy = -2; dy <= 2; dy++) {
            for (var dx = -2; dx <= 2; dx++) {
                var dist = Math.max(Math.abs(dx), Math.abs(dy));
                setModule(ctx, cx + dx, cy + dy, dist !== 1, true);
            }
        }
    }

    function drawPatterns(ctx) {
        var size = ctx.matrix.length;
        drawFinder(ctx, 0, 0);
        drawFinder(ctx, size - 7, 0);
        drawFinder(ctx, 0, size - 7);
        for (var i = 8; i < size - 8; i++) {
            setModule(ctx, i, 6, i % 2 === 0, true);
            setModule(ctx, 6, i, i % 2 === 0, true);
        }
        drawAlignment(ctx, 30, 30);
        setModule(ctx, 8, 29, true, true);
        reserveFormat(ctx);
    }

    function reserveFormat(ctx) {
        var size = ctx.matrix.length;
        for (var i = 0; i < 9; i++) {
            if (i !== 6) {
                ctx.reserved[8][i] = true;
                ctx.reserved[i][8] = true;
            }
        }
        for (var x = size - 8; x < size; x++) {
            ctx.reserved[8][x] = true;
        }
        for (var y = size - 7; y < size; y++) {
            ctx.reserved[y][8] = true;
        }
    }

    function maskBit(mask, x, y) {
        switch (mask) {
            case 0: return (x + y) % 2 === 0;
            case 1: return y % 2 === 0;
            case 2: return x % 3 === 0;
            case 3: return (x + y) % 3 === 0;
            case 4: return (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0;
            default: return (x * y) % 2 + (x * y) % 3 === 0;
        }
    }

    function drawData(ctx, bits, mask) {
        var size = ctx.matrix.length;
        var bitIndex = 0;
        var upward = true;
        for (var x = size - 1; x >= 1; x -= 2) {
            if (x === 6) {
                x--;
            }
            for (var count = 0; count < size; count++) {
                var y = upward ? size - 1 - count : count;
                for (var col = 0; col < 2; col++) {
                    var xx = x - col;
                    if (ctx.reserved[y][xx]) {
                        continue;
                    }
                    var bit = bitIndex < bits.length ? bits[bitIndex++] === 1 : false;
                    setModule(ctx, xx, y, bit !== maskBit(mask, xx, y), false);
                }
            }
            upward = !upward;
        }
    }

    function bchFormat(data) {
        var value = data << 10;
        var generator = 0x537;
        for (var i = 14; i >= 10; i--) {
            if ((value >>> i) & 1) {
                value ^= generator << (i - 10);
            }
        }
        return ((data << 10) | value) ^ 0x5412;
    }

    function drawFormat(ctx, mask) {
        var size = ctx.matrix.length;
        var format = bchFormat((1 << 3) | mask); // EC level L.
        var positionsA = [[8,0],[8,1],[8,2],[8,3],[8,4],[8,5],[8,7],[8,8],[7,8],[5,8],[4,8],[3,8],[2,8],[1,8],[0,8]];
        var positionsB = [[size-1,8],[size-2,8],[size-3,8],[size-4,8],[size-5,8],[size-6,8],[size-7,8],[8,size-8],[8,size-7],[8,size-6],[8,size-5],[8,size-4],[8,size-3],[8,size-2],[8,size-1]];
        for (var i = 0; i < 15; i++) {
            var dark = ((format >>> i) & 1) === 1;
            setModule(ctx, positionsA[i][0], positionsA[i][1], dark, true);
            setModule(ctx, positionsB[i][0], positionsB[i][1], dark, true);
        }
    }

    function buildMatrix(text) {
        var data = buildData(text);
        var ec = rsRemainder(data, 26);
        var codewords = data.concat(ec);
        var bits = [];
        for (var i = 0; i < codewords.length; i++) {
            pushBits(bits, codewords[i], 8);
        }
        for (var r = 0; r < 7; r++) {
            bits.push(0);
        }
        var ctx = makeMatrix(37);
        drawPatterns(ctx);
        var mask = 0;
        drawData(ctx, bits, mask);
        drawFormat(ctx, mask);
        return ctx.matrix;
    }

    function renderToElement(element, text, options) {
        if (!element || !text) {
            return;
        }
        var matrix = buildMatrix(text);
        var requestedSize = options && options.size ? parseInt(options.size, 10) : 112;
        if (!requestedSize || requestedSize < 180) {
            requestedSize = 180;
        }
        var quiet = 4;
        var moduleCount = matrix.length + quiet * 2;
        var unit = Math.max(2, Math.ceil(requestedSize / moduleCount));
        var size = moduleCount * unit;
        var canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;
        canvas.style.width = size + 'px';
        canvas.style.height = size + 'px';
        var ctx = canvas.getContext('2d');
        ctx.fillStyle = options && options.background ? options.background : '#ffffff';
        ctx.fillRect(0, 0, size, size);
        ctx.fillStyle = options && options.color ? options.color : '#111827';
        for (var y = 0; y < matrix.length; y++) {
            for (var x = 0; x < matrix.length; x++) {
                if (matrix[y][x]) {
                    ctx.fillRect((x + quiet) * unit, (y + quiet) * unit, unit, unit);
                }
            }
        }
        element.innerHTML = '';
        element.appendChild(canvas);
    }

    window.noesiaCommerceSuiteQr = {
        render: renderToElement
    };
}(window, document));
