#!/usr/bin/python3.11
# coding=utf-8
#
# Copyright (C) 2009 Kazuhiko Arase (http://www.d-project.com/)
#               2010 Bulia Byak <buliabyak@gmail.com>
#               2018 Kirill Okhotnikov <kirill.okhotnikov@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA.
#
"""
Provide the QR Code rendering.
"""

from __future__ import print_function

from itertools import product
from collections import defaultdict

import numpy as np

import inkex
from inkex import Group, Rectangle, Use, PathElement
from inkex.paths import Move, zoneClose, Line, Curve
from inkex.localization import inkex_gettext as _
from inkex.utils import circular_pairwise


class QRLengthError(Exception):
    def __init__(self, message):
        self.message = message


class QRCode(object):
    PAD0 = 0xEC
    PAD1 = 0x11

    def __init__(self, correction):
        self.typeNumber = 1
        self.errorCorrectLevel = correction
        self.qrDataList = []
        self.modules = []
        self.moduleCount = 0

    def getTypeNumber(self):
        return self.typeNumber

    def setTypeNumber(self, typeNumber):
        self.typeNumber = typeNumber

    def clearData(self):
        self.qrDataList = []

    def addData(self, data):
        self.qrDataList.append(data)

    def getDataCount(self):
        return len(self.qrDataList)

    def getData(self, index):
        return self.qrDataList[index]

    def isDark(self, row, col):
        return self.modules[row][col] if self.modules[row][col] is not None else False

    def getModuleCount(self):
        return self.moduleCount

    def make(self):
        self._make(False, self._getBestMaskPattern())

    def _getBestMaskPattern(self):
        minLostPoint = 0
        pattern = 0
        for i in range(8):
            self._make(True, i)
            lostPoint = QRUtil.getLostPoint(self)
            if i == 0 or minLostPoint > lostPoint:
                minLostPoint = lostPoint
                pattern = i
        return pattern

    def _make(self, test, maskPattern):
        self.moduleCount = self.typeNumber * 4 + 17
        self.modules = np.array(
            [[None] * self.moduleCount for i in range(self.moduleCount)]
        )

        self._setupPositionProbePattern(0, 0)
        self._setupPositionProbePattern(self.moduleCount - 7, 0)
        self._setupPositionProbePattern(0, self.moduleCount - 7)

        self._setupPositionAdjustPattern()
        self._setupTimingPattern()

        self._setupTypeInfo(test, maskPattern)

        if self.typeNumber >= 7:
            self._setupTypeNumber(test)

        data = QRCode._createData(
            self.typeNumber, self.errorCorrectLevel, self.qrDataList
        )

        self._mapData(data, maskPattern)

    def _mapData(self, data, maskPattern):
        rows = list(range(self.moduleCount))
        cols = [
            col - 1 if col <= 6 else col for col in range(self.moduleCount - 1, 0, -2)
        ]
        maskFunc = QRUtil.getMaskFunction(maskPattern)

        byteIndex = 0
        bitIndex = 7

        for col in cols:
            rows.reverse()
            for row in rows:
                for c in range(2):
                    if self.modules[row][col - c] is None:
                        dark = False
                        if byteIndex < len(data):
                            dark = ((data[byteIndex] >> bitIndex) & 1) == 1
                        if maskFunc(row, col - c):
                            dark = not dark
                        self.modules[row][col - c] = dark

                        bitIndex -= 1
                        if bitIndex == -1:
                            byteIndex += 1
                            bitIndex = 7

    def _setupPositionAdjustPattern(self):
        pos = QRUtil.getPatternPosition(self.typeNumber)
        for row in pos:
            for col in pos:
                if self.modules[row][col] is not None:
                    continue
                for r in range(-2, 3):
                    for c in range(-2, 3):
                        self.modules[row + r][col + c] = (
                            r == -2
                            or r == 2
                            or c == -2
                            or c == 2
                            or (r == 0 and c == 0)
                        )

    def _setupPositionProbePattern(self, row, col):
        for r in range(-1, 8):
            for c in range(-1, 8):
                if (
                    row + r <= -1
                    or self.moduleCount <= row + r
                    or col + c <= -1
                    or self.moduleCount <= col + c
                ):
                    continue
                self.modules[row + r][col + c] = (
                    (0 <= r <= 6 and (c == 0 or c == 6))
                    or (0 <= c <= 6 and (r == 0 or r == 6))
                    or (2 <= r <= 4 and 2 <= c <= 4)
                )

    def _setupTimingPattern(self):
        for r in range(8, self.moduleCount - 8):
            if self.modules[r][6] is not None:
                continue
            self.modules[r][6] = r % 2 == 0
        for c in range(8, self.moduleCount - 8):
            if self.modules[6][c] is not None:
                continue
            self.modules[6][c] = c % 2 == 0

    def _setupTypeNumber(self, test):
        bits = QRUtil.getBCHTypeNumber(self.typeNumber)
        for i in range(18):
            self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = (
                not test and ((bits >> i) & 1) == 1
            )
        for i in range(18):
            self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = (
                not test and ((bits >> i) & 1) == 1
            )

    def _setupTypeInfo(self, test, maskPattern):
        data = (self.errorCorrectLevel << 3) | maskPattern
        bits = QRUtil.getBCHTypeInfo(data)

        # vertical
        for i in range(15):
            mod = not test and ((bits >> i) & 1) == 1
            if i < 6:
                self.modules[i][8] = mod
            elif i < 8:
                self.modules[i + 1][8] = mod
            else:
                self.modules[self.moduleCount - 15 + i][8] = mod

        # horizontal
        for i in range(15):
            mod = not test and ((bits >> i) & 1) == 1
            if i < 8:
                self.modules[8][self.moduleCount - i - 1] = mod
            elif i < 9:
                self.modules[8][15 - i - 1 + 1] = mod
            else:
                self.modules[8][15 - i - 1] = mod

        # fixed
        self.modules[self.moduleCount - 8][8] = not test

    @staticmethod
    def _createData(typeNumber, errorCorrectLevel, dataArray):
        rsBlocks = RSBlock.getRSBlocks(typeNumber, errorCorrectLevel)

        buffer = BitBuffer()

        for data in dataArray:
            buffer.put(data.getMode(), 4)
            buffer.put(data.getLength(), data.getLengthInBits(typeNumber))
            data.write(buffer)

        totalDataCount = sum(rsBlock.getDataCount() for rsBlock in rsBlocks)

        if buffer.getLengthInBits() > totalDataCount * 8:
            raise QRLengthError(
                "code length overflow. (%s>%s)"
                % (buffer.getLengthInBits(), totalDataCount * 8)
            )

        # end code
        if buffer.getLengthInBits() + 4 <= totalDataCount * 8:
            buffer.put(0, 4)

        # padding
        while buffer.getLengthInBits() % 8 != 0:
            buffer.put(False, 1)

        # padding
        while True:
            if buffer.getLengthInBits() >= totalDataCount * 8:
                break
            buffer.put(QRCode.PAD0, 8)
            if buffer.getLengthInBits() >= totalDataCount * 8:
                break
            buffer.put(QRCode.PAD1, 8)

        return QRCode._createBytes(buffer, rsBlocks)

    @staticmethod
    def _createBytes(buffer, rsBlocks):
        offset = 0

        maxDcCount = 0
        maxEcCount = 0

        dcdata = [None] * len(rsBlocks)
        ecdata = [None] * len(rsBlocks)

        for r, b in enumerate(rsBlocks):
            dcCount = b.getDataCount()
            ecCount = b.getTotalCount() - dcCount

            maxDcCount = max(maxDcCount, dcCount)
            maxEcCount = max(maxEcCount, ecCount)

            dcdata[r] = [0] * dcCount
            for i in range(len(dcdata[r])):
                dcdata[r][i] = 0xFF & buffer.getBuffer()[i + offset]
            offset += dcCount

            rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount)
            rawPoly = Polynomial(dcdata[r], rsPoly.getLength() - 1)

            modPoly = rawPoly.mod(rsPoly)
            ecdata[r] = [0] * (rsPoly.getLength() - 1)
            for i in range(len(ecdata[r])):
                modIndex = i + modPoly.getLength() - len(ecdata[r])
                ecdata[r][i] = modPoly.get(modIndex) if modIndex >= 0 else 0

        totalCodeCount = sum(rsBlock.getTotalCount() for rsBlock in rsBlocks)

        data = [0] * totalCodeCount

        index = 0

        for i in range(maxDcCount):
            for r in range(len(rsBlocks)):
                if i < len(dcdata[r]):
                    data[index] = dcdata[r][i]
                    index += 1

        for i in range(maxEcCount):
            for r in range(len(rsBlocks)):
                if i < len(ecdata[r]):
                    data[index] = ecdata[r][i]
                    index += 1

        return data

    @staticmethod
    def getMinimumQRCode(data, errorCorrectLevel):
        qr = QRCode(correction=errorCorrectLevel)
        qr.addData(data)
        lv = 1
        rv = 40
        while rv - lv > 0:
            mid = (3 * lv + rv) // 4
            qr.setTypeNumber(mid)
            try:
                qr.make()
            except QRLengthError:
                if mid == 40:
                    raise inkex.AbortExtension(
                        _("The string is too large to represent as QR code")
                    )
                lv = mid + 1
            else:
                rv = mid

        qr.setTypeNumber(rv)
        qr.make()
        return qr


class Mode(object):
    MODE_NUMBER = 1 << 0
    MODE_ALPHA_NUM = 1 << 1
    MODE_8BIT_BYTE = 1 << 2
    MODE_KANJI = 1 << 3


class MaskPattern(object):
    PATTERN000 = 0
    PATTERN001 = 1
    PATTERN010 = 2
    PATTERN011 = 3
    PATTERN100 = 4
    PATTERN101 = 5
    PATTERN110 = 6
    PATTERN111 = 7


class QRUtil(object):
    @staticmethod
    def getPatternPosition(typeNumber):
        return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]

    PATTERN_POSITION_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],
    ]

    @staticmethod
    def getErrorCorrectPolynomial(errorCorrectLength):
        a = Polynomial([1])
        for i in range(errorCorrectLength):
            a = a.multiply(Polynomial([1, QRMath.gexp(i)]))
        return a

    @staticmethod
    def getMaskFunction(maskPattern):
        return {
            MaskPattern.PATTERN000: lambda i, j: (i + j) % 2 == 0,
            MaskPattern.PATTERN001: lambda i, j: i % 2 == 0,
            MaskPattern.PATTERN010: lambda i, j: j % 3 == 0,
            MaskPattern.PATTERN011: lambda i, j: (i + j) % 3 == 0,
            MaskPattern.PATTERN100: lambda i, j: (i // 2 + j // 3) % 2 == 0,
            MaskPattern.PATTERN101: lambda i, j: (i * j) % 2 + (i * j) % 3 == 0,
            MaskPattern.PATTERN110: lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0,
            MaskPattern.PATTERN111: lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0,
        }[maskPattern]

    @staticmethod
    def getLostPoint(qrcode: QRCode):
        moduleCount = qrcode.getModuleCount()
        lostPoint = 0

        other = qrcode.modules.astype("int")

        # LEVEL1
        same = np.zeros_like(other)
        same[:-1, :] += other[:-1, :] == other[1:, :]  # east
        same[:-1, :-1] += other[:-1, :-1] == other[1:, 1:]  # north east
        same[:, :-1] += other[:, :-1] == other[:, 1:]  # north
        same[1:, :-1] += other[1:, :-1] == other[:-1, 1:]  # north west
        same[1:, :] += other[1:, :] == other[:-1, :]  # west
        same[1:, 1:] += other[1:, 1:] == other[:-1, :-1]  # south west
        same[:, 1:] += other[:, 1:] == other[:, :-1]  # south
        same[:-1, 1:] += other[:-1, 1:] == other[1:, :-1]  # south east
        lostPoint = np.sum(np.where(same > 5, 3 + same - 5, 0))

        # LEVEL2
        lostPoint += 3 * np.sum(
            np.mod(other[:-1, :-1] + other[1:, :-1] + other[:-1, 1:] + other[1:, 1:], 4)
            == 0
        )

        # LEVEL3
        lostPoint += 40 * np.sum(
            (other[:, 0:-6] == 1)
            & (other[:, 1:-5] == 0)
            & (other[:, 2:-4] == 1)
            & (other[:, 3:-3] == 1)
            & (other[:, 4:-2] == 1)
            & (other[:, 5:-1] == 0)
            & (other[:, 6:] == 1)
        )

        lostPoint += 40 * np.sum(
            (other[0:-6] == 1)
            & (other[1:-5, :] == 0)
            & (other[2:-4, :] == 1)
            & (other[3:-3, :] == 1)
            & (other[4:-2, :] == 1)
            & (other[5:-1, :] == 0)
            & (other[6:, :] == 1)
        )

        # LEVEL4
        darkCount = np.sum(other)
        ratio = abs(100 * darkCount // moduleCount // moduleCount - 50) // 5
        lostPoint += ratio * 10

        return lostPoint

    G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0)
    G18 = (
        (1 << 12)
        | (1 << 11)
        | (1 << 10)
        | (1 << 9)
        | (1 << 8)
        | (1 << 5)
        | (1 << 2)
        | (1 << 0)
    )
    G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1)

    @staticmethod
    def getBCHTypeInfo(data):
        d = data << 10
        while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0:
            d ^= QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15))
        return ((data << 10) | d) ^ QRUtil.G15_MASK

    @staticmethod
    def getBCHTypeNumber(data):
        d = data << 12
        while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0:
            d ^= QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18))
        return (data << 12) | d

    @staticmethod
    def getBCHDigit(data):
        digit = 0
        while data != 0:
            digit += 1
            data >>= 1
        return digit


class QRData(object):
    def __init__(self, mode, data):
        self._mode = mode
        self.data = data

    def getMode(self):
        return self._mode

    def getData(self):
        return self.data

    def getLength(self):
        return len(self.data)

    #    @abstractmethod
    def write(self, buffer):
        pass

    def getLengthInBits(self, type):
        if 1 <= type < 10:  # 1 - 9
            return {
                Mode.MODE_NUMBER: 10,
                Mode.MODE_ALPHA_NUM: 9,
                Mode.MODE_8BIT_BYTE: 8,
                Mode.MODE_KANJI: 8,
            }[self._mode]

        elif type < 27:  # 10 - 26
            return {
                Mode.MODE_NUMBER: 12,
                Mode.MODE_ALPHA_NUM: 11,
                Mode.MODE_8BIT_BYTE: 16,
                Mode.MODE_KANJI: 10,
            }[self._mode]

        elif type < 41:  # 27 - 40
            return {
                Mode.MODE_NUMBER: 14,
                Mode.MODE_ALPHA_NUM: 13,
                Mode.MODE_8BIT_BYTE: 16,
                Mode.MODE_KANJI: 12,
            }[self._mode]

        else:
            raise Exception("type:%s" % type)


class QR8BitByte(QRData):
    def __init__(self, data):
        super(QR8BitByte, self).__init__(Mode.MODE_8BIT_BYTE, data)
        if isinstance(data, str):
            data = data.encode("ascii", "ignore")
        if not isinstance(data, bytes):
            raise ValueError("Data must be in bytes!")

    def write(self, buffer):
        for d in self.data:
            buffer.put(ord(d), 8)


class QRAlphaNum(QRData):
    def __init__(self, data):
        super(QRAlphaNum, self).__init__(Mode.MODE_ALPHA_NUM, data)

    def write(self, buffer):
        i = 0
        while i + 1 < len(self.data):
            buffer.put(
                QRAlphaNum._getCode(self.data[i]) * 45
                + QRAlphaNum._getCode(self.data[i + 1]),
                11,
            )
            i += 2
        if i < len(self.data):
            buffer.put(QRAlphaNum._getCode(self.data[i]), 6)

    @staticmethod
    def _getCode(c):
        if "0" <= c and c <= "9":
            return ord(c) - ord("0")
        elif "A" <= c and c <= "Z":
            return ord(c) - ord("A") + 10
        else:
            dct = {
                " ": 36,
                "$": 37,
                "%": 38,
                "*": 39,
                "+": 40,
                "-": 41,
                ".": 42,
                "/": 43,
                ":": 44,
            }
            if c in dct.keys():
                return dct[c]
            else:
                raise inkex.AbortExtension(
                    _(
                        "Wrong symbol '{}' in alphanumeric representation: Should be [A-Z, 0-9] or {}"
                    ).format(c, dct.keys())
                )


class QRNumber(QRData):
    def __init__(self, data):
        super(QRNumber, self).__init__(Mode.MODE_NUMBER, data)

    def write(self, buffer):
        i = 0
        try:
            while i + 2 < len(self.data):
                num = int(self.data[i : i + 3])
                buffer.put(num, 10)
                i += 3

            if i < len(self.data):
                ln = 4 if len(self.data) - i == 1 else 7
                buffer.put(int(self.data[i:]), ln)
        except:
            raise ValueError("The string '{}' is not a valid number".format(self.data))


class QRKanji(QRData):
    def __init__(self, data):
        super(QRKanji, self).__init__(Mode.MODE_KANJI, data)
        raise RuntimeError("Class QRKanji is not implemented")


class QRMath(object):
    EXP_TABLE = None
    LOG_TABLE = None

    @staticmethod
    def _init():
        QRMath.EXP_TABLE = [0] * 256
        for i in range(256):
            QRMath.EXP_TABLE[i] = (
                1 << i
                if i < 8
                else QRMath.EXP_TABLE[i - 4]
                ^ QRMath.EXP_TABLE[i - 5]
                ^ QRMath.EXP_TABLE[i - 6]
                ^ QRMath.EXP_TABLE[i - 8]
            )

        QRMath.LOG_TABLE = [0] * 256
        for i in range(255):
            QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i

    @staticmethod
    def glog(n):
        return QRMath.LOG_TABLE[n]

    @staticmethod
    def gexp(n):
        return QRMath.EXP_TABLE[n % 255]


# initialize statics
QRMath._init()


class Polynomial(object):
    def __init__(self, num, shift=0):
        offset = 0
        length = len(num)
        while offset < length and num[offset] == 0:
            offset += 1
        self.num = num[offset:] + [0] * shift

    def get(self, index):
        return self.num[index]

    def getLength(self):
        return len(self.num)

    def __repr__(self):
        return ",".join([str(self.get(i)) for i in range(self.getLength())])

    def toLogString(self):
        return ",".join(
            [str(QRMath.glog(self.get(i))) for i in range(self.getLength())]
        )

    def multiply(self, e):
        num = [0] * (self.getLength() + e.getLength() - 1)
        for i in range(self.getLength()):
            for j in range(e.getLength()):
                num[i + j] ^= QRMath.gexp(
                    QRMath.glog(self.get(i)) + QRMath.glog(e.get(j))
                )
        return Polynomial(num)

    def mod(self, e):
        if self.getLength() - e.getLength() < 0:
            return self
        ratio = QRMath.glog(self.get(0)) - QRMath.glog(e.get(0))
        num = self.num[:]
        for i in range(e.getLength()):
            num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio)
        return Polynomial(num).mod(e)


class RSBlock(object):
    RS_BLOCK_TABLE = [
        # L
        # M
        # Q
        # H
        # 1
        [1, 26, 19],
        [1, 26, 16],
        [1, 26, 13],
        [1, 26, 9],
        # 2
        [1, 44, 34],
        [1, 44, 28],
        [1, 44, 22],
        [1, 44, 16],
        # 3
        [1, 70, 55],
        [1, 70, 44],
        [2, 35, 17],
        [2, 35, 13],
        # 4
        [1, 100, 80],
        [2, 50, 32],
        [2, 50, 24],
        [4, 25, 9],
        # 5
        [1, 134, 108],
        [2, 67, 43],
        [2, 33, 15, 2, 34, 16],
        [2, 33, 11, 2, 34, 12],
        # 6
        [2, 86, 68],
        [4, 43, 27],
        [4, 43, 19],
        [4, 43, 15],
        # 7
        [2, 98, 78],
        [4, 49, 31],
        [2, 32, 14, 4, 33, 15],
        [4, 39, 13, 1, 40, 14],
        # 8
        [2, 121, 97],
        [2, 60, 38, 2, 61, 39],
        [4, 40, 18, 2, 41, 19],
        [4, 40, 14, 2, 41, 15],
        # 9
        [2, 146, 116],
        [3, 58, 36, 2, 59, 37],
        [4, 36, 16, 4, 37, 17],
        [4, 36, 12, 4, 37, 13],
        # 10
        [2, 86, 68, 2, 87, 69],
        [4, 69, 43, 1, 70, 44],
        [6, 43, 19, 2, 44, 20],
        [6, 43, 15, 2, 44, 16],
        # 11
        [4, 101, 81],
        [1, 80, 50, 4, 81, 51],
        [4, 50, 22, 4, 51, 23],
        [3, 36, 12, 8, 37, 13],
        # 12
        [2, 116, 92, 2, 117, 93],
        [6, 58, 36, 2, 59, 37],
        [4, 46, 20, 6, 47, 21],
        [7, 42, 14, 4, 43, 15],
        # 13
        [4, 133, 107],
        [8, 59, 37, 1, 60, 38],
        [8, 44, 20, 4, 45, 21],
        [12, 33, 11, 4, 34, 12],
        # 14
        [3, 145, 115, 1, 146, 116],
        [4, 64, 40, 5, 65, 41],
        [11, 36, 16, 5, 37, 17],
        [11, 36, 12, 5, 37, 13],
        # 15
        [5, 109, 87, 1, 110, 88],
        [5, 65, 41, 5, 66, 42],
        [5, 54, 24, 7, 55, 25],
        [11, 36, 12, 7, 37, 13],
        # 16
        [5, 122, 98, 1, 123, 99],
        [7, 73, 45, 3, 74, 46],
        [15, 43, 19, 2, 44, 20],
        [3, 45, 15, 13, 46, 16],
        # 17
        [1, 135, 107, 5, 136, 108],
        [10, 74, 46, 1, 75, 47],
        [1, 50, 22, 15, 51, 23],
        [2, 42, 14, 17, 43, 15],
        # 18
        [5, 150, 120, 1, 151, 121],
        [9, 69, 43, 4, 70, 44],
        [17, 50, 22, 1, 51, 23],
        [2, 42, 14, 19, 43, 15],
        # 19
        [3, 141, 113, 4, 142, 114],
        [3, 70, 44, 11, 71, 45],
        [17, 47, 21, 4, 48, 22],
        [9, 39, 13, 16, 40, 14],
        # 20
        [3, 135, 107, 5, 136, 108],
        [3, 67, 41, 13, 68, 42],
        [15, 54, 24, 5, 55, 25],
        [15, 43, 15, 10, 44, 16],
        # 21
        [4, 144, 116, 4, 145, 117],
        [17, 68, 42],
        [17, 50, 22, 6, 51, 23],
        [19, 46, 16, 6, 47, 17],
        # 22
        [2, 139, 111, 7, 140, 112],
        [17, 74, 46],
        [7, 54, 24, 16, 55, 25],
        [34, 37, 13],
        # 23
        [4, 151, 121, 5, 152, 122],
        [4, 75, 47, 14, 76, 48],
        [11, 54, 24, 14, 55, 25],
        [16, 45, 15, 14, 46, 16],
        # 24
        [6, 147, 117, 4, 148, 118],
        [6, 73, 45, 14, 74, 46],
        [11, 54, 24, 16, 55, 25],
        [30, 46, 16, 2, 47, 17],
        # 25
        [8, 132, 106, 4, 133, 107],
        [8, 75, 47, 13, 76, 48],
        [7, 54, 24, 22, 55, 25],
        [22, 45, 15, 13, 46, 16],
        # 26
        [10, 142, 114, 2, 143, 115],
        [19, 74, 46, 4, 75, 47],
        [28, 50, 22, 6, 51, 23],
        [33, 46, 16, 4, 47, 17],
        # 27
        [8, 152, 122, 4, 153, 123],
        [22, 73, 45, 3, 74, 46],
        [8, 53, 23, 26, 54, 24],
        [12, 45, 15, 28, 46, 16],
        # 28
        [3, 147, 117, 10, 148, 118],
        [3, 73, 45, 23, 74, 46],
        [4, 54, 24, 31, 55, 25],
        [11, 45, 15, 31, 46, 16],
        # 29
        [7, 146, 116, 7, 147, 117],
        [21, 73, 45, 7, 74, 46],
        [1, 53, 23, 37, 54, 24],
        [19, 45, 15, 26, 46, 16],
        # 30
        [5, 145, 115, 10, 146, 116],
        [19, 75, 47, 10, 76, 48],
        [15, 54, 24, 25, 55, 25],
        [23, 45, 15, 25, 46, 16],
        # 31
        [13, 145, 115, 3, 146, 116],
        [2, 74, 46, 29, 75, 47],
        [42, 54, 24, 1, 55, 25],
        [23, 45, 15, 28, 46, 16],
        # 32
        [17, 145, 115],
        [10, 74, 46, 23, 75, 47],
        [10, 54, 24, 35, 55, 25],
        [19, 45, 15, 35, 46, 16],
        # 33
        [17, 145, 115, 1, 146, 116],
        [14, 74, 46, 21, 75, 47],
        [29, 54, 24, 19, 55, 25],
        [11, 45, 15, 46, 46, 16],
        # 34
        [13, 145, 115, 6, 146, 116],
        [14, 74, 46, 23, 75, 47],
        [44, 54, 24, 7, 55, 25],
        [59, 46, 16, 1, 47, 17],
        # 35
        [12, 151, 121, 7, 152, 122],
        [12, 75, 47, 26, 76, 48],
        [39, 54, 24, 14, 55, 25],
        [22, 45, 15, 41, 46, 16],
        # 36
        [6, 151, 121, 14, 152, 122],
        [6, 75, 47, 34, 76, 48],
        [46, 54, 24, 10, 55, 25],
        [2, 45, 15, 64, 46, 16],
        # 37
        [17, 152, 122, 4, 153, 123],
        [29, 74, 46, 14, 75, 47],
        [49, 54, 24, 10, 55, 25],
        [24, 45, 15, 46, 46, 16],
        # 38
        [4, 152, 122, 18, 153, 123],
        [13, 74, 46, 32, 75, 47],
        [48, 54, 24, 14, 55, 25],
        [42, 45, 15, 32, 46, 16],
        # 39
        [20, 147, 117, 4, 148, 118],
        [40, 75, 47, 7, 76, 48],
        [43, 54, 24, 22, 55, 25],
        [10, 45, 15, 67, 46, 16],
        # 40
        [19, 148, 118, 6, 149, 119],
        [18, 75, 47, 31, 76, 48],
        [34, 54, 24, 34, 55, 25],
        [20, 45, 15, 61, 46, 16],
    ]

    def __init__(self, totalCount, dataCount):
        self.totalCount = totalCount
        self.dataCount = dataCount

    def getDataCount(self):
        return self.dataCount

    def getTotalCount(self):
        return self.totalCount

    def __repr__(self):
        return "(total=%s,data=%s)" % (self.totalCount, self.dataCount)

    @staticmethod
    def getRSBlocks(typeNumber, errorCorrectLevel):
        rsBlock = RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel)
        length = len(rsBlock) // 3
        list = []
        for i in range(length):
            count = rsBlock[i * 3 + 0]
            totalCount = rsBlock[i * 3 + 1]
            dataCount = rsBlock[i * 3 + 2]
            list += [RSBlock(totalCount, dataCount)] * count
        return list

    @staticmethod
    def getRsBlockTable(typeNumber, errorCorrectLevel):
        return {
            1: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0],
            0: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1],
            3: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2],
            2: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3],
        }[errorCorrectLevel]


class BitBuffer(object):
    def __init__(self, inclements=32):
        self.inclements = inclements
        self.buffer = [0] * self.inclements
        self.length = 0

    def getBuffer(self):
        return self.buffer

    def getLengthInBits(self):
        return self.length

    def get(self, index):
        return ((self.buffer[index // 8] >> (7 - index % 8)) & 1) == 1

    def putBit(self, bit):
        if self.length == len(self.buffer) * 8:
            self.buffer += [0] * self.inclements
        if bit:
            self.buffer[self.length // 8] |= 0x80 >> (self.length % 8)
        self.length += 1

    def put(self, num, length):
        for i in range(length):
            self.putBit(((num >> (length - i - 1)) & 1) == 1)

    def __repr__(self):
        return "".join(
            "1" if self.get(i) else "0" for i in range(self.getLengthInBits())
        )


class GridDrawer(object):
    """Mechanism to draw grids of boxes"""

    def __init__(self, invert_code, smooth_factor):
        self.invert_code = invert_code
        self.smoothFactor = smooth_factor
        self.grid = None
        self.row_count = 0
        self.col_count = 0

    def set_grid(self, grid):
        if len({len(g) for g in grid}) != 1:
            raise Exception("The array is not rectangular")
        else:
            self.grid = grid
            self.row_count = len(self.grid)
            self.col_count = len(self.grid[0])

    def isDark(self, col, row):
        inside = 0 <= col < self.col_count and 0 <= row < self.row_count
        return False if not inside else self.grid[row][col] != self.invert_code

    dm = {0: (1, 0), 1: (0, -1), 2: (-1, 0), 3: (0, 1)}

    @staticmethod
    def moveByDirection(xyd):
        return xyd[0] + GridDrawer.dm[xyd[2]][0], xyd[1] + GridDrawer.dm[xyd[2]][1]

    @staticmethod
    def makeDirectionsTable():
        result = []
        for cfg in product(range(2), repeat=4):
            result.append([])
            for d in range(4):
                if cfg[3 - d] == 0 and cfg[3 - (d - 1) % 4] != 0:
                    result[-1].append(d)
        return result

    def createVertexesForAdvDrawer(self):
        dirTable = self.makeDirectionsTable()
        result = []
        # Create vertex
        for row in range(self.row_count + 1):
            for col in range(self.col_count + 1):
                indx = (
                    (2**0 if self.isDark(col - 0, row - 1) else 0)
                    + (2**1 if self.isDark(col - 1, row - 1) else 0)
                    + (2**2 if self.isDark(col - 1, row - 0) else 0)
                    + (2**3 if self.isDark(col - 0, row - 0) else 0)
                )

                for d in dirTable[indx]:
                    result.append((col, row, d, len(dirTable[indx]) > 1))

        return result

    def getSmoothPosition(self, v, extraSmoothFactor=1.0):
        vn = self.moveByDirection(v)
        sc = extraSmoothFactor * self.smoothFactor / 2.0
        sc1 = 1.0 - sc
        return (v[0] * sc1 + vn[0] * sc, v[1] * sc1 + vn[1] * sc), (
            v[0] * sc + vn[0] * sc1,
            v[1] * sc + vn[1] * sc1,
        )


class QrCode(inkex.GenerateExtension):
    """Generate QR Code Extension"""

    def add_arguments(self, pars):
        pars.add_argument("--text", default="https://inkscape.org")
        pars.add_argument("--typenumber", type=int, default=0)
        pars.add_argument("--correctionlevel", type=int, default=0)
        pars.add_argument("--qrmode", type=int, default=0)
        pars.add_argument("--encoding", default="latin_1")
        pars.add_argument("--modulesize", type=float, default=4.0)
        pars.add_argument("--invert", type=inkex.Boolean, default="false")
        pars.add_argument(
            "--drawtype",
            default="smooth",
            choices=["smooth", "pathpreset", "selection", "symbol"],
        )
        pars.add_argument(
            "--smoothness", default="neutral", choices=["neutral", "greedy", "proud"]
        )
        pars.add_argument("--pathtype", default="simple", choices=["simple", "circle"])
        pars.add_argument("--smoothval", type=float, default=0.2)
        pars.add_argument("--symbolid", default="")
        pars.add_argument("--groupid", default="")

    def generate(self):
        scale = self.svg.unittouu("1px")  # convert to document units
        opt = self.options

        if not opt.text:
            raise inkex.AbortExtension(_("Please enter an input text"))
        elif opt.drawtype == "symbol" and opt.symbolid == "":
            raise inkex.AbortExtension(_("Please enter symbol id"))

        # for Python 3 ugly hack to represent bytes as str for Python2 compatibility
        text_str = str(opt.text)
        cmode = [QR8BitByte, QRNumber, QRAlphaNum, QRKanji][opt.qrmode]
        text_data = cmode(bytes(opt.text, opt.encoding).decode("latin_1"))

        grp = Group()
        grp.set("inkscape:label", "QR Code: " + text_str)
        if opt.groupid:
            grp.set("id", opt.groupid)
        pos_x, pos_y = self.svg.namedview.center
        grp.transform.add_translate(pos_x, pos_y)
        if scale:
            grp.transform.add_scale(scale)

        # GENERATE THE QRCODE
        if opt.typenumber == 0:
            # Automatic QR code size`
            code = QRCode.getMinimumQRCode(text_data, opt.correctionlevel)
        else:
            # Manual QR code size
            code = QRCode(correction=opt.correctionlevel)
            code.setTypeNumber(int(opt.typenumber))
            code.addData(text_data)
            code.make()

        self.boxsize = opt.modulesize
        self.invert_code = opt.invert
        self.margin = 4
        self.draw = GridDrawer(opt.invert, opt.smoothval)
        self.draw.set_grid(code.modules)
        self.render_svg(grp, opt.drawtype)
        return grp

    def ring_iterator(self, vertices, greedy):
        visited = set()
        lut = defaultdict(list)
        for i, v in enumerate(vertices):
            lut[v[0:2]].append(i)

        def getnext(vertex):
            nextPos = self.draw.moveByDirection(vertex)
            nextIndexes = lut[nextPos[0:2]]
            if len(nextIndexes) == 0 or len(nextIndexes) > 2:
                raise Exception("Vertex " + str(next_c) + " has no connections")
            elif len(nextIndexes) == 1:
                vertsIndexNext = nextIndexes[0]
            else:
                if {vertices[nextIndexes[0]][2], vertices[nextIndexes[1]][2]} != {
                    (vertex[2] - 1) % 4,
                    (vertex[2] + 1) % 4,
                }:
                    raise Exception(
                        "Bad next vertex directions "
                        + str(vertices[nextIndexes[0]])
                        + str(vertices[nextIndexes[1]])
                    )

                # Greedy - CCW turn, proud and neutral CW turn
                vertsIndexNext = (
                    nextIndexes[0]
                    if (greedy == "g")
                    == (vertices[nextIndexes[0]][2] == (vertex[2] + 1) % 4)
                    else nextIndexes[1]
                )
            return vertices[vertsIndexNext]

        for start_vertex in reversed(vertices):
            if start_vertex in visited:
                continue

            path = [start_vertex]
            next_vertex = getnext(start_vertex)

            while start_vertex != next_vertex:
                path.append(next_vertex)
                next_vertex = getnext(next_vertex)
            else:
                yield path
                visited |= set(path)

    def render_adv(self, greedy):
        verts = self.draw.createVertexesForAdvDrawer()
        it = self.ring_iterator(verts, greedy)
        qrPath = inkex.Path()
        for ringVertices in it:
            posStart, _ = self.draw.getSmoothPosition(ringVertices[0])
            qrPath.append(Move(*self.get_svg_pos(posStart[0], posStart[1])))
            for vc, vn in circular_pairwise(ringVertices):
                if vn[2] != vc[2]:
                    if (greedy != "n") or not vn[3]:
                        # Add bezier
                        # Opt length http://spencermortensen.com/articles/bezier-circle/
                        # c = 0.552284749
                        ex = 1 - 0.552284749
                        _, bs = self.draw.getSmoothPosition(vc)
                        _, bp1 = self.draw.getSmoothPosition(vc, ex)
                        bp2, _ = self.draw.getSmoothPosition(vn, ex)
                        bf, _ = self.draw.getSmoothPosition(vn)
                        qrPath.append(Line(*self.get_svg_pos(bs[0], bs[1])))
                        qrPath.append(
                            Curve(
                                *self.get_svg_pos(bp1[0], bp1[1]),
                                *self.get_svg_pos(bp2[0], bp2[1]),
                                *self.get_svg_pos(bf[0], bf[1]),
                            )
                        )
                    else:
                        # Add straight
                        qrPath.append(Line(*self.get_svg_pos(vn[0], vn[1])))

            qrPath.append(zoneClose())

        return PathElement.new(path=qrPath)

    def render_obsolete(self):
        for row in range(self.draw.row_count):
            for col in range(self.draw.col_count):
                if self.draw.isDark(col, row):
                    x, y = self.get_svg_pos(col, row)
                    return Rectangle.new(x, y, self.boxsize, self.boxsize)

    def render_path(self, pointStr):
        singlePath = inkex.Path(self.get_icon_path_str(pointStr))
        path = inkex.Path()
        for row in range(self.draw.row_count):
            for col in range(self.draw.col_count):
                if self.draw.isDark(col, row):
                    x, y = self.get_svg_pos(col, row)
                    path.append(Move(x, y))
                    path += singlePath
                    path.append(zoneClose())
        return PathElement.new(path=path)

    def render_selection(self):
        if len(self.svg.selection) > 0:
            self.options.symbolid = self.svg.selection.first().get_id()
        else:
            raise inkex.AbortExtension(_("Please select an element to clone"))
        return self.render_symbol()

    def render_symbol(self):
        symbol = self.svg.getElementById(self.options.symbolid)
        if symbol is None:
            raise inkex.AbortExtension(
                _("Can't find symbol {}").format(self.options.symbolid)
            )
        bbox = symbol.path.bounding_box()
        transform = inkex.Transform(
            scale=(
                float(self.boxsize) / bbox.width,
                float(self.boxsize) / bbox.height,
            )
        )
        result = Group()
        for row in range(self.draw.row_count):
            for col in range(self.draw.col_count):
                if self.draw.isDark(col, row):
                    x, y = self.get_svg_pos(col, row)
                    # Inkscape doesn't support width/height on use tags
                    result.append(
                        Use.new(
                            symbol,
                            x / transform.a,
                            y / transform.d,
                            transform=transform,
                        )
                    )
        return result

    def render_pathpreset(self):
        if self.options.pathtype == "simple":
            return self.render_path("h 1 v 1 h -1")
        else:
            s = (
                "m 0.5,0.5 "
                "c 0.2761423745,0 0.5,0.2238576255 0.5,0.5 "
                "c 0,0.2761423745 -0.2238576255,0.5 -0.5,0.5 "
                "c -0.2761423745,0 -0.5,-0.2238576255 -0.5,-0.5 "
                "c 0,-0.2761423745 0.2238576255,-0.5 0.5,-0.5"
            )
            return self.render_path(s)

    render_smooth = lambda self: self.render_adv(self.options.smoothness[0])

    def render_svg(self, grp, drawtype):
        """Render to svg"""
        drawer = getattr(self, f"render_{drawtype}", self.render_obsolete)
        if drawer is None:
            raise Exception("Unknown draw type: " + drawtype)

        canvas_width = (self.draw.col_count + 2 * self.margin) * self.boxsize
        canvas_height = (self.draw.row_count + 2 * self.margin) * self.boxsize

        # white background providing margin:
        rect = grp.add(Rectangle.new(0, 0, canvas_width, canvas_height))
        rect.style["stroke"] = "none"
        rect.style["fill"] = "black" if self.invert_code else "white"

        qrg = grp.add(Group())
        qrg.style["stroke"] = "none"
        qrg.style["fill"] = "white" if self.invert_code else "black"
        qrg.add(drawer())

    def get_svg_pos(self, col, row):
        return (col + self.margin) * self.boxsize, (row + self.margin) * self.boxsize

    def get_icon_path_str(self, pointStr):
        result = ""
        digBuffer = ""
        for c in pointStr:
            if c.isdigit() or c == "-" or c == ".":
                digBuffer += c
            else:
                if len(digBuffer) > 0:
                    result += str(float(digBuffer) * self.boxsize)
                    digBuffer = ""
                result += c

        if len(digBuffer) > 0:
            result += str(float(digBuffer) * self.boxsize)

        return result


if __name__ == "__main__":
    QrCode().run()
