extends RefCounted

const BitStream := preload("res://addons/kenyoni/qr_code/bit_stream.gd")
const ReedSolomon := preload("res://addons/kenyoni/qr_code/reed_solomon.gd")
const ShiftJIS := preload("res://addons/kenyoni/qr_code/shift_jis.gd")

## Encoding Mode
enum Mode {
	## 0001
	NUMERIC = 1,
	## 0010
	ALPHANUMERIC = 2,
	## 0100
	BYTE = 4,
	## 1000
	KANJI = 8
}

## Error Correction
enum ErrorCorrection {
	LOW = 1,
	MEDIUM = 0,
	QUARTILE = 3,
	HIGH = 2
}

## Extended Channel Interpretation
enum ECI {
	CODE_PAGE_437 = 2,
	ISO_8859_1 = 3,
	ISO_8859_2 = 4,
	ISO_8859_3 = 5,
	ISO_8859_4 = 6,
	ISO_8859_5 = 7,
	ISO_8859_6 = 8,
	ISO_8859_7 = 9,
	ISO_8859_8 = 10,
	ISO_8859_9 = 11,
	ISO_8859_10 = 12,
	ISO_8859_11 = 13,
	ISO_8859_12 = 14,
	ISO_8859_13 = 15,
	ISO_8859_14 = 16,
	ISO_8859_15 = 17,
	ISO_8859_16 = 18,
	SHIFT_JIS = 20,
	WINDOWS_1250 = 21,
	WINDOWS_1251 = 22,
	WINDOWS_1252 = 23,
	WINDOWS_1256 = 24,
	UTF_16 = 25,
	UTF_8 = 26,
	US_ASCII = 27,
	BIG_5 = 28,
	GB_18030 = 29,
	EUC_KR = 30
}

const _DATA_CAPACITY: Array[Dictionary] = [
	# 1
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 41, Mode.ALPHANUMERIC: 25, Mode.BYTE: 17, Mode.KANJI: 10},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 34, Mode.ALPHANUMERIC: 20, Mode.BYTE: 14, Mode.KANJI: 8},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 27, Mode.ALPHANUMERIC: 16, Mode.BYTE: 11, Mode.KANJI: 7},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 17, Mode.ALPHANUMERIC: 10, Mode.BYTE: 7, Mode.KANJI: 4},
	},
	# 2
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 77, Mode.ALPHANUMERIC: 47, Mode.BYTE: 32, Mode.KANJI: 20},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 63, Mode.ALPHANUMERIC: 38, Mode.BYTE: 26, Mode.KANJI: 16},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 48, Mode.ALPHANUMERIC: 29, Mode.BYTE: 20, Mode.KANJI: 12},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 34, Mode.ALPHANUMERIC: 20, Mode.BYTE: 14, Mode.KANJI: 8},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 127, Mode.ALPHANUMERIC: 77, Mode.BYTE: 53, Mode.KANJI: 32},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 101, Mode.ALPHANUMERIC: 61, Mode.BYTE: 42, Mode.KANJI: 26},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 77, Mode.ALPHANUMERIC: 47, Mode.BYTE: 32, Mode.KANJI: 20},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 58, Mode.ALPHANUMERIC: 35, Mode.BYTE: 24, Mode.KANJI: 15},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 187, Mode.ALPHANUMERIC: 114, Mode.BYTE: 78, Mode.KANJI: 48},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 149, Mode.ALPHANUMERIC: 90, Mode.BYTE: 62, Mode.KANJI: 38},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 111, Mode.ALPHANUMERIC: 67, Mode.BYTE: 46, Mode.KANJI: 28},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 82, Mode.ALPHANUMERIC: 50, Mode.BYTE: 34, Mode.KANJI: 21},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 255, Mode.ALPHANUMERIC: 154, Mode.BYTE: 106, Mode.KANJI: 65},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 202, Mode.ALPHANUMERIC: 122, Mode.BYTE: 84, Mode.KANJI: 52},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 144, Mode.ALPHANUMERIC: 87, Mode.BYTE: 60, Mode.KANJI: 37},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 106, Mode.ALPHANUMERIC: 64, Mode.BYTE: 44, Mode.KANJI: 27},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 322, Mode.ALPHANUMERIC: 195, Mode.BYTE: 134, Mode.KANJI: 82},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 255, Mode.ALPHANUMERIC: 154, Mode.BYTE: 106, Mode.KANJI: 65},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 178, Mode.ALPHANUMERIC: 108, Mode.BYTE: 74, Mode.KANJI: 45},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 139, Mode.ALPHANUMERIC: 84, Mode.BYTE: 58, Mode.KANJI: 36},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 370, Mode.ALPHANUMERIC: 224, Mode.BYTE: 154, Mode.KANJI: 95},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 293, Mode.ALPHANUMERIC: 178, Mode.BYTE: 122, Mode.KANJI: 75},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 207, Mode.ALPHANUMERIC: 125, Mode.BYTE: 86, Mode.KANJI: 53},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 154, Mode.ALPHANUMERIC: 93, Mode.BYTE: 64, Mode.KANJI: 39},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 461, Mode.ALPHANUMERIC: 279, Mode.BYTE: 192, Mode.KANJI: 118},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 365, Mode.ALPHANUMERIC: 221, Mode.BYTE: 152, Mode.KANJI: 93},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 259, Mode.ALPHANUMERIC: 157, Mode.BYTE: 108, Mode.KANJI: 66},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 202, Mode.ALPHANUMERIC: 122, Mode.BYTE: 84, Mode.KANJI: 52},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 552, Mode.ALPHANUMERIC: 335, Mode.BYTE: 230, Mode.KANJI: 141},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 432, Mode.ALPHANUMERIC: 262, Mode.BYTE: 180, Mode.KANJI: 111},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 312, Mode.ALPHANUMERIC: 189, Mode.BYTE: 130, Mode.KANJI: 80},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 235, Mode.ALPHANUMERIC: 143, Mode.BYTE: 98, Mode.KANJI: 60},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 652, Mode.ALPHANUMERIC: 395, Mode.BYTE: 271, Mode.KANJI: 167},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 513, Mode.ALPHANUMERIC: 311, Mode.BYTE: 213, Mode.KANJI: 131},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 364, Mode.ALPHANUMERIC: 221, Mode.BYTE: 151, Mode.KANJI: 93},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 288, Mode.ALPHANUMERIC: 174, Mode.BYTE: 119, Mode.KANJI: 74},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 772, Mode.ALPHANUMERIC: 468, Mode.BYTE: 321, Mode.KANJI: 198},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 604, Mode.ALPHANUMERIC: 366, Mode.BYTE: 251, Mode.KANJI: 155},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 427, Mode.ALPHANUMERIC: 259, Mode.BYTE: 177, Mode.KANJI: 109},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 331, Mode.ALPHANUMERIC: 200, Mode.BYTE: 137, Mode.KANJI: 85},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 883, Mode.ALPHANUMERIC: 535, Mode.BYTE: 367, Mode.KANJI: 226},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 691, Mode.ALPHANUMERIC: 419, Mode.BYTE: 287, Mode.KANJI: 177},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 489, Mode.ALPHANUMERIC: 296, Mode.BYTE: 203, Mode.KANJI: 125},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 374, Mode.ALPHANUMERIC: 227, Mode.BYTE: 155, Mode.KANJI: 96},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1022, Mode.ALPHANUMERIC: 619, Mode.BYTE: 425, Mode.KANJI: 262},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 796, Mode.ALPHANUMERIC: 483, Mode.BYTE: 331, Mode.KANJI: 204},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 580, Mode.ALPHANUMERIC: 352, Mode.BYTE: 241, Mode.KANJI: 149},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 427, Mode.ALPHANUMERIC: 259, Mode.BYTE: 177, Mode.KANJI: 109},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1101, Mode.ALPHANUMERIC: 667, Mode.BYTE: 458, Mode.KANJI: 282},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 871, Mode.ALPHANUMERIC: 528, Mode.BYTE: 362, Mode.KANJI: 223},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 621, Mode.ALPHANUMERIC: 376, Mode.BYTE: 258, Mode.KANJI: 159},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 468, Mode.ALPHANUMERIC: 283, Mode.BYTE: 194, Mode.KANJI: 120},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1250, Mode.ALPHANUMERIC: 758, Mode.BYTE: 520, Mode.KANJI: 320},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 991, Mode.ALPHANUMERIC: 600, Mode.BYTE: 412, Mode.KANJI: 254},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 703, Mode.ALPHANUMERIC: 426, Mode.BYTE: 292, Mode.KANJI: 180},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 530, Mode.ALPHANUMERIC: 321, Mode.BYTE: 220, Mode.KANJI: 136},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1408, Mode.ALPHANUMERIC: 854, Mode.BYTE: 586, Mode.KANJI: 361},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1082, Mode.ALPHANUMERIC: 656, Mode.BYTE: 450, Mode.KANJI: 277},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 775, Mode.ALPHANUMERIC: 470, Mode.BYTE: 322, Mode.KANJI: 198},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 602, Mode.ALPHANUMERIC: 365, Mode.BYTE: 250, Mode.KANJI: 154},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1548, Mode.ALPHANUMERIC: 938, Mode.BYTE: 644, Mode.KANJI: 397},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1212, Mode.ALPHANUMERIC: 734, Mode.BYTE: 504, Mode.KANJI: 310},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 876, Mode.ALPHANUMERIC: 531, Mode.BYTE: 364, Mode.KANJI: 224},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 674, Mode.ALPHANUMERIC: 408, Mode.BYTE: 280, Mode.KANJI: 173},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1725, Mode.ALPHANUMERIC: 1046, Mode.BYTE: 718, Mode.KANJI: 442},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1346, Mode.ALPHANUMERIC: 816, Mode.BYTE: 560, Mode.KANJI: 345},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 948, Mode.ALPHANUMERIC: 574, Mode.BYTE: 394, Mode.KANJI: 243},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 746, Mode.ALPHANUMERIC: 452, Mode.BYTE: 310, Mode.KANJI: 191},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 1903, Mode.ALPHANUMERIC: 1153, Mode.BYTE: 792, Mode.KANJI: 488},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1500, Mode.ALPHANUMERIC: 909, Mode.BYTE: 624, Mode.KANJI: 384},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1063, Mode.ALPHANUMERIC: 644, Mode.BYTE: 442, Mode.KANJI: 272},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 813, Mode.ALPHANUMERIC: 493, Mode.BYTE: 338, Mode.KANJI: 208},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 2061, Mode.ALPHANUMERIC: 1249, Mode.BYTE: 858, Mode.KANJI: 528},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1600, Mode.ALPHANUMERIC: 970, Mode.BYTE: 666, Mode.KANJI: 410},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1159, Mode.ALPHANUMERIC: 702, Mode.BYTE: 482, Mode.KANJI: 297},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 919, Mode.ALPHANUMERIC: 557, Mode.BYTE: 382, Mode.KANJI: 235},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 2232, Mode.ALPHANUMERIC: 1352, Mode.BYTE: 929, Mode.KANJI: 572},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1708, Mode.ALPHANUMERIC: 1035, Mode.BYTE: 711, Mode.KANJI: 438},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1224, Mode.ALPHANUMERIC: 742, Mode.BYTE: 509, Mode.KANJI: 314},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 969, Mode.ALPHANUMERIC: 587, Mode.BYTE: 403, Mode.KANJI: 248},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 2409, Mode.ALPHANUMERIC: 1460, Mode.BYTE: 1003, Mode.KANJI: 618},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 1872, Mode.ALPHANUMERIC: 1134, Mode.BYTE: 779, Mode.KANJI: 480},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1358, Mode.ALPHANUMERIC: 823, Mode.BYTE: 565, Mode.KANJI: 348},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1056, Mode.ALPHANUMERIC: 640, Mode.BYTE: 439, Mode.KANJI: 270},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 2620, Mode.ALPHANUMERIC: 1588, Mode.BYTE: 1091, Mode.KANJI: 672},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2059, Mode.ALPHANUMERIC: 1248, Mode.BYTE: 857, Mode.KANJI: 528},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1468, Mode.ALPHANUMERIC: 890, Mode.BYTE: 611, Mode.KANJI: 376},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1108, Mode.ALPHANUMERIC: 672, Mode.BYTE: 461, Mode.KANJI: 284},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 2812, Mode.ALPHANUMERIC: 1704, Mode.BYTE: 1171, Mode.KANJI: 721},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2188, Mode.ALPHANUMERIC: 1326, Mode.BYTE: 911, Mode.KANJI: 561},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1588, Mode.ALPHANUMERIC: 963, Mode.BYTE: 661, Mode.KANJI: 407},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1228, Mode.ALPHANUMERIC: 744, Mode.BYTE: 511, Mode.KANJI: 315},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 3057, Mode.ALPHANUMERIC: 1853, Mode.BYTE: 1273, Mode.KANJI: 784},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2395, Mode.ALPHANUMERIC: 1451, Mode.BYTE: 997, Mode.KANJI: 614},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1718, Mode.ALPHANUMERIC: 1041, Mode.BYTE: 715, Mode.KANJI: 440},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1286, Mode.ALPHANUMERIC: 779, Mode.BYTE: 535, Mode.KANJI: 330},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 3283, Mode.ALPHANUMERIC: 1990, Mode.BYTE: 1367, Mode.KANJI: 842},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2544, Mode.ALPHANUMERIC: 1542, Mode.BYTE: 1059, Mode.KANJI: 652},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1804, Mode.ALPHANUMERIC: 1094, Mode.BYTE: 751, Mode.KANJI: 462},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1425, Mode.ALPHANUMERIC: 864, Mode.BYTE: 593, Mode.KANJI: 365},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 3514, Mode.ALPHANUMERIC: 2132, Mode.BYTE: 1465, Mode.KANJI: 902},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2701, Mode.ALPHANUMERIC: 1637, Mode.BYTE: 1125, Mode.KANJI: 692},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 1933, Mode.ALPHANUMERIC: 1172, Mode.BYTE: 805, Mode.KANJI: 496},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1501, Mode.ALPHANUMERIC: 910, Mode.BYTE: 625, Mode.KANJI: 385},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 3669, Mode.ALPHANUMERIC: 2223, Mode.BYTE: 1528, Mode.KANJI: 940},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 2857, Mode.ALPHANUMERIC: 1732, Mode.BYTE: 1190, Mode.KANJI: 732},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2085, Mode.ALPHANUMERIC: 1263, Mode.BYTE: 868, Mode.KANJI: 534},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1581, Mode.ALPHANUMERIC: 958, Mode.BYTE: 658, Mode.KANJI: 405},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 3909, Mode.ALPHANUMERIC: 2369, Mode.BYTE: 1628, Mode.KANJI: 1002},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 3035, Mode.ALPHANUMERIC: 1839, Mode.BYTE: 1264, Mode.KANJI: 778},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2181, Mode.ALPHANUMERIC: 1322, Mode.BYTE: 908, Mode.KANJI: 559},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1677, Mode.ALPHANUMERIC: 1016, Mode.BYTE: 698, Mode.KANJI: 430},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 4158, Mode.ALPHANUMERIC: 2520, Mode.BYTE: 1732, Mode.KANJI: 1066},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 3289, Mode.ALPHANUMERIC: 1994, Mode.BYTE: 1370, Mode.KANJI: 843},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2358, Mode.ALPHANUMERIC: 1429, Mode.BYTE: 982, Mode.KANJI: 604},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1782, Mode.ALPHANUMERIC: 1080, Mode.BYTE: 742, Mode.KANJI: 457},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 4417, Mode.ALPHANUMERIC: 2677, Mode.BYTE: 1840, Mode.KANJI: 1132},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 3486, Mode.ALPHANUMERIC: 2113, Mode.BYTE: 1452, Mode.KANJI: 894},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2473, Mode.ALPHANUMERIC: 1499, Mode.BYTE: 1030, Mode.KANJI: 634},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 1897, Mode.ALPHANUMERIC: 1150, Mode.BYTE: 790, Mode.KANJI: 486},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 4686, Mode.ALPHANUMERIC: 2840, Mode.BYTE: 1952, Mode.KANJI: 1201},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 3693, Mode.ALPHANUMERIC: 2238, Mode.BYTE: 1538, Mode.KANJI: 947},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2670, Mode.ALPHANUMERIC: 1618, Mode.BYTE: 1112, Mode.KANJI: 684},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2022, Mode.ALPHANUMERIC: 1226, Mode.BYTE: 842, Mode.KANJI: 518},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 4965, Mode.ALPHANUMERIC: 3009, Mode.BYTE: 2068, Mode.KANJI: 1273},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 3909, Mode.ALPHANUMERIC: 2369, Mode.BYTE: 1628, Mode.KANJI: 1002},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2805, Mode.ALPHANUMERIC: 1700, Mode.BYTE: 1168, Mode.KANJI: 719},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2157, Mode.ALPHANUMERIC: 1307, Mode.BYTE: 898, Mode.KANJI: 553},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 5253, Mode.ALPHANUMERIC: 3183, Mode.BYTE: 2188, Mode.KANJI: 1347},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 4134, Mode.ALPHANUMERIC: 2506, Mode.BYTE: 1722, Mode.KANJI: 1060},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 2949, Mode.ALPHANUMERIC: 1787, Mode.BYTE: 1228, Mode.KANJI: 756},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2301, Mode.ALPHANUMERIC: 1394, Mode.BYTE: 958, Mode.KANJI: 590},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 5529, Mode.ALPHANUMERIC: 3351, Mode.BYTE: 2303, Mode.KANJI: 1417},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 4343, Mode.ALPHANUMERIC: 2632, Mode.BYTE: 1809, Mode.KANJI: 1113},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3081, Mode.ALPHANUMERIC: 1867, Mode.BYTE: 1283, Mode.KANJI: 790},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2361, Mode.ALPHANUMERIC: 1431, Mode.BYTE: 983, Mode.KANJI: 605},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 5836, Mode.ALPHANUMERIC: 3537, Mode.BYTE: 2431, Mode.KANJI: 1496},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 4588, Mode.ALPHANUMERIC: 2780, Mode.BYTE: 1911, Mode.KANJI: 1176},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3244, Mode.ALPHANUMERIC: 1966, Mode.BYTE: 1351, Mode.KANJI: 832},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2524, Mode.ALPHANUMERIC: 1530, Mode.BYTE: 1051, Mode.KANJI: 647},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 6153, Mode.ALPHANUMERIC: 3729, Mode.BYTE: 2563, Mode.KANJI: 1577},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 4775, Mode.ALPHANUMERIC: 2894, Mode.BYTE: 1989, Mode.KANJI: 1224},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3417, Mode.ALPHANUMERIC: 2071, Mode.BYTE: 1423, Mode.KANJI: 876},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2625, Mode.ALPHANUMERIC: 1591, Mode.BYTE: 1093, Mode.KANJI: 673},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 6479, Mode.ALPHANUMERIC: 3927, Mode.BYTE: 2699, Mode.KANJI: 1661},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 5039, Mode.ALPHANUMERIC: 3054, Mode.BYTE: 2099, Mode.KANJI: 1292},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3599, Mode.ALPHANUMERIC: 2181, Mode.BYTE: 1499, Mode.KANJI: 923},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2735, Mode.ALPHANUMERIC: 1658, Mode.BYTE: 1139, Mode.KANJI: 701},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 6743, Mode.ALPHANUMERIC: 4087, Mode.BYTE: 2809, Mode.KANJI: 1729},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 5313, Mode.ALPHANUMERIC: 3220, Mode.BYTE: 2213, Mode.KANJI: 1362},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3791, Mode.ALPHANUMERIC: 2298, Mode.BYTE: 1579, Mode.KANJI: 972},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 2927, Mode.ALPHANUMERIC: 1774, Mode.BYTE: 1219, Mode.KANJI: 750},
	},
	{
		ErrorCorrection.LOW: {Mode.NUMERIC: 7089, Mode.ALPHANUMERIC: 4296, Mode.BYTE: 2953, Mode.KANJI: 1817},
		ErrorCorrection.MEDIUM: {Mode.NUMERIC: 5596, Mode.ALPHANUMERIC: 3391, Mode.BYTE: 2331, Mode.KANJI: 1435},
		ErrorCorrection.QUARTILE: {Mode.NUMERIC: 3993, Mode.ALPHANUMERIC: 2420, Mode.BYTE: 1663, Mode.KANJI: 1024},
		ErrorCorrection.HIGH: {Mode.NUMERIC: 3057, Mode.ALPHANUMERIC: 1852, Mode.BYTE: 1273, Mode.KANJI: 784}
	},
]

const _ALPHANUMERIC_CHARACTERS: Dictionary[String, int] = {
	"0": 0,
	"1": 1,
	"2": 2,
	"3": 3,
	"4": 4,
	"5": 5,
	"6": 6,
	"7": 7,
	"8": 8,
	"9": 9,
	"A": 10,
	"B": 11,
	"C": 12,
	"D": 13,
	"E": 14,
	"F": 15,
	"G": 16,
	"H": 17,
	"I": 18,
	"J": 19,
	"K": 20,
	"L": 21,
	"M": 22,
	"N": 23,
	"O": 24,
	"P": 25,
	"Q": 26,
	"R": 27,
	"S": 28,
	"T": 29,
	"U": 30,
	"V": 31,
	"W": 32,
	"X": 33,
	"Y": 34,
	"Z": 35,
	" ": 36,
	"$": 37,
	"%": 38,
	"*": 39,
	"+": 40,
	"-": 41,
	".": 42,
	"/": 43,
	":": 44,
}

## https://www.thonky.com/qr-code-tutorial/error-correction-table
## [total data codewords, EC codewords per block, number of blocks in group 1, number of data codewords in group 1 blocks, number of blocks in group 2, number of data codewords in group 2 blocks]
const _ERROR_CORRECTION: Array[Dictionary] = [
	# 1
	{
		ErrorCorrection.LOW: [19, 7, 1, 19, 0, 0],
		ErrorCorrection.MEDIUM: [16, 10, 1, 16, 0, 0],
		ErrorCorrection.QUARTILE: [13, 13, 1, 13, 0, 0],
		ErrorCorrection.HIGH: [9, 17, 1, 9, 0, 0],
	},
	# 2
	{
		ErrorCorrection.LOW: [34, 10, 1, 34, 0, 0],
		ErrorCorrection.MEDIUM: [28, 16, 1, 28, 0, 0],
		ErrorCorrection.QUARTILE: [22, 22, 1, 22, 0, 0],
		ErrorCorrection.HIGH: [16, 28, 1, 16, 0, 0],
	},
	# 3
	{
		ErrorCorrection.LOW: [55, 15, 1, 55, 0, 0],
		ErrorCorrection.MEDIUM: [44, 26, 1, 44, 0, 0],
		ErrorCorrection.QUARTILE: [34, 18, 2, 17, 0, 0],
		ErrorCorrection.HIGH: [26, 22, 2, 13, 0, 0],
	},
	# 4
	{
		ErrorCorrection.LOW: [80, 20, 1, 80, 0, 0],
		ErrorCorrection.MEDIUM: [64, 18, 2, 32, 0, 0],
		ErrorCorrection.QUARTILE: [48, 26, 2, 24, 0, 0],
		ErrorCorrection.HIGH: [36, 16, 4, 9, 0, 0],
	},
	# 5
	{
		ErrorCorrection.LOW: [108, 26, 1, 108, 0, 0],
		ErrorCorrection.MEDIUM: [86, 24, 2, 43, 0, 0],
		ErrorCorrection.QUARTILE: [62, 18, 2, 15, 2, 16],
		ErrorCorrection.HIGH: [46, 22, 2, 11, 2, 12],
	},
	# 6
	{
		ErrorCorrection.LOW: [136, 18, 2, 68, 0, 0],
		ErrorCorrection.MEDIUM: [108, 16, 4, 27, 0, 0],
		ErrorCorrection.QUARTILE: [76, 24, 4, 19, 0, 0],
		ErrorCorrection.HIGH: [60, 28, 4, 15, 0, 0],
	},
	# 7
	{
		ErrorCorrection.LOW: [156, 20, 2, 78, 0, 0],
		ErrorCorrection.MEDIUM: [124, 18, 4, 31, 0, 0],
		ErrorCorrection.QUARTILE: [88, 18, 2, 14, 4, 15],
		ErrorCorrection.HIGH: [66, 26, 4, 13, 1, 14],
	},
	# 8
	{
		ErrorCorrection.LOW: [194, 24, 2, 97, 0, 0],
		ErrorCorrection.MEDIUM: [154, 22, 2, 38, 2, 39],
		ErrorCorrection.QUARTILE: [110, 22, 4, 18, 2, 19],
		ErrorCorrection.HIGH: [86, 26, 4, 14, 2, 15],
	},
	# 9
	{
		ErrorCorrection.LOW: [232, 30, 2, 116, 0, 0],
		ErrorCorrection.MEDIUM: [182, 22, 3, 36, 2, 37],
		ErrorCorrection.QUARTILE: [132, 20, 4, 16, 4, 17],
		ErrorCorrection.HIGH: [100, 24, 4, 12, 4, 13],
	},
	# 10
	{
		ErrorCorrection.LOW: [274, 18, 2, 68, 2, 69],
		ErrorCorrection.MEDIUM: [216, 26, 4, 43, 1, 44],
		ErrorCorrection.QUARTILE: [154, 24, 6, 19, 2, 20],
		ErrorCorrection.HIGH: [122, 28, 6, 15, 2, 16],
	},
	# 11
	{
		ErrorCorrection.LOW: [324, 20, 4, 81, 0, 0],
		ErrorCorrection.MEDIUM: [254, 30, 1, 50, 4, 51],
		ErrorCorrection.QUARTILE: [180, 28, 4, 22, 4, 23],
		ErrorCorrection.HIGH: [140, 24, 3, 12, 8, 13],
	},
	# 12
	{
		ErrorCorrection.LOW: [370, 24, 2, 92, 2, 93],
		ErrorCorrection.MEDIUM: [290, 22, 6, 36, 2, 37],
		ErrorCorrection.QUARTILE: [206, 26, 4, 20, 6, 21],
		ErrorCorrection.HIGH: [158, 28, 7, 14, 4, 15],
	},
	# 13
	{
		ErrorCorrection.LOW: [428, 26, 4, 107, 0, 0],
		ErrorCorrection.MEDIUM: [334, 22, 8, 37, 1, 38],
		ErrorCorrection.QUARTILE: [244, 24, 8, 20, 4, 21],
		ErrorCorrection.HIGH: [180, 22, 12, 11, 4, 12],
	},
	# 14
	{
		ErrorCorrection.LOW: [461, 30, 3, 115, 1, 116],
		ErrorCorrection.MEDIUM: [365, 24, 4, 40, 5, 41],
		ErrorCorrection.QUARTILE: [261, 20, 11, 16, 5, 17],
		ErrorCorrection.HIGH: [197, 24, 11, 12, 5, 13],
	},
	# 15
	{
		ErrorCorrection.LOW: [523, 22, 5, 87, 1, 88],
		ErrorCorrection.MEDIUM: [415, 24, 5, 41, 5, 42],
		ErrorCorrection.QUARTILE: [295, 30, 5, 24, 7, 25],
		ErrorCorrection.HIGH: [223, 24, 11, 12, 7, 13],
	},
	# 16
	{
		ErrorCorrection.LOW: [589, 24, 5, 98, 1, 99],
		ErrorCorrection.MEDIUM: [453, 28, 7, 45, 3, 46],
		ErrorCorrection.QUARTILE: [325, 24, 15, 19, 2, 20],
		ErrorCorrection.HIGH: [253, 30, 3, 15, 13, 16],
	},
	# 17
	{
		ErrorCorrection.LOW: [647, 28, 1, 107, 5, 108],
		ErrorCorrection.MEDIUM: [507, 28, 10, 46, 1, 47],
		ErrorCorrection.QUARTILE: [367, 28, 1, 22, 15, 23],
		ErrorCorrection.HIGH: [283, 28, 2, 14, 17, 15],
	},
	# 18
	{
		ErrorCorrection.LOW: [721, 30, 5, 120, 1, 121],
		ErrorCorrection.MEDIUM: [563, 26, 9, 43, 4, 44],
		ErrorCorrection.QUARTILE: [397, 28, 17, 22, 1, 23],
		ErrorCorrection.HIGH: [313, 28, 2, 14, 19, 15],
	},
	# 19
	{
		ErrorCorrection.LOW: [795, 28, 3, 113, 4, 114],
		ErrorCorrection.MEDIUM: [627, 26, 3, 44, 11, 45],
		ErrorCorrection.QUARTILE: [445, 26, 17, 21, 4, 22],
		ErrorCorrection.HIGH: [341, 26, 9, 13, 16, 14],
	},
	# 20
	{
		ErrorCorrection.LOW: [861, 28, 3, 107, 5, 108],
		ErrorCorrection.MEDIUM: [669, 26, 3, 41, 13, 42],
		ErrorCorrection.QUARTILE: [485, 30, 15, 24, 5, 25],
		ErrorCorrection.HIGH: [385, 28, 15, 15, 10, 16],
	},
	# 21
	{
		ErrorCorrection.LOW: [932, 28, 4, 116, 4, 117],
		ErrorCorrection.MEDIUM: [714, 26, 17, 42, 0, 0],
		ErrorCorrection.QUARTILE: [512, 28, 17, 22, 6, 23],
		ErrorCorrection.HIGH: [406, 30, 19, 16, 6, 17],
	},
	# 22
	{
		ErrorCorrection.LOW: [1006, 28, 2, 111, 7, 112],
		ErrorCorrection.MEDIUM: [782, 28, 17, 46, 0, 0],
		ErrorCorrection.QUARTILE: [568, 30, 7, 24, 16, 25],
		ErrorCorrection.HIGH: [442, 24, 34, 13, 0, 0],
	},
	# 23
	{
		ErrorCorrection.LOW: [1094, 30, 4, 121, 5, 122],
		ErrorCorrection.MEDIUM: [860, 28, 4, 47, 14, 48],
		ErrorCorrection.QUARTILE: [614, 30, 11, 24, 14, 25],
		ErrorCorrection.HIGH: [464, 30, 16, 15, 14, 16],
	},
	# 24
	{
		ErrorCorrection.LOW: [1174, 30, 6, 117, 4, 118],
		ErrorCorrection.MEDIUM: [914, 28, 6, 45, 14, 46],
		ErrorCorrection.QUARTILE: [664, 30, 11, 24, 16, 25],
		ErrorCorrection.HIGH: [514, 30, 30, 16, 2, 17],
	},
	# 25
	{
		ErrorCorrection.LOW: [1276, 26, 8, 106, 4, 107],
		ErrorCorrection.MEDIUM: [1000, 28, 8, 47, 13, 48],
		ErrorCorrection.QUARTILE: [718, 30, 7, 24, 22, 25],
		ErrorCorrection.HIGH: [538, 30, 22, 15, 13, 16],
	},
	# 26
	{
		ErrorCorrection.LOW: [1370, 28, 10, 114, 2, 115],
		ErrorCorrection.MEDIUM: [1062, 28, 19, 46, 4, 47],
		ErrorCorrection.QUARTILE: [754, 28, 28, 22, 6, 23],
		ErrorCorrection.HIGH: [596, 30, 33, 16, 4, 17],
	},
	# 27
	{
		ErrorCorrection.LOW: [1468, 30, 8, 122, 4, 123],
		ErrorCorrection.MEDIUM: [1128, 28, 22, 45, 3, 46],
		ErrorCorrection.QUARTILE: [808, 30, 8, 23, 26, 24],
		ErrorCorrection.HIGH: [628, 30, 12, 15, 28, 16],
	},
	# 28
	{
		ErrorCorrection.LOW: [1531, 30, 3, 117, 10, 118],
		ErrorCorrection.MEDIUM: [1193, 28, 3, 45, 23, 46],
		ErrorCorrection.QUARTILE: [871, 30, 4, 24, 31, 25],
		ErrorCorrection.HIGH: [661, 30, 11, 15, 31, 16],
	},
	# 29
	{
		ErrorCorrection.LOW: [1631, 30, 7, 116, 7, 117],
		ErrorCorrection.MEDIUM: [1267, 28, 21, 45, 7, 46],
		ErrorCorrection.QUARTILE: [911, 30, 1, 23, 37, 24],
		ErrorCorrection.HIGH: [701, 30, 19, 15, 26, 16],
	},
	# 30
	{
		ErrorCorrection.LOW: [1735, 30, 5, 115, 10, 116],
		ErrorCorrection.MEDIUM: [1373, 28, 19, 47, 10, 48],
		ErrorCorrection.QUARTILE: [985, 30, 15, 24, 25, 25],
		ErrorCorrection.HIGH: [745, 30, 23, 15, 25, 16],
	},
	# 31
	{
		ErrorCorrection.LOW: [1843, 30, 13, 115, 3, 116],
		ErrorCorrection.MEDIUM: [1455, 28, 2, 46, 29, 47],
		ErrorCorrection.QUARTILE: [1033, 30, 42, 24, 1, 25],
		ErrorCorrection.HIGH: [793, 30, 23, 15, 28, 16],
	},
	# 32
	{
		ErrorCorrection.LOW: [1955, 30, 17, 115, 0, 0],
		ErrorCorrection.MEDIUM: [1541, 28, 10, 46, 23, 47],
		ErrorCorrection.QUARTILE: [1115, 30, 10, 24, 35, 25],
		ErrorCorrection.HIGH: [845, 30, 19, 15, 35, 16],
	},
	# 33
	{
		ErrorCorrection.LOW: [2071, 30, 17, 115, 1, 116],
		ErrorCorrection.MEDIUM: [1631, 28, 14, 46, 21, 47],
		ErrorCorrection.QUARTILE: [1171, 30, 29, 24, 19, 25],
		ErrorCorrection.HIGH: [901, 30, 11, 15, 46, 16],
	},
	# 34
	{
		ErrorCorrection.LOW: [2191, 30, 13, 115, 6, 116],
		ErrorCorrection.MEDIUM: [1725, 28, 14, 46, 23, 47],
		ErrorCorrection.QUARTILE: [1231, 30, 44, 24, 7, 25],
		ErrorCorrection.HIGH: [961, 30, 59, 16, 1, 17],
	},
	# 35
	{
		ErrorCorrection.LOW: [2306, 30, 12, 121, 7, 122],
		ErrorCorrection.MEDIUM: [1812, 28, 12, 47, 26, 48],
		ErrorCorrection.QUARTILE: [1286, 30, 39, 24, 14, 25],
		ErrorCorrection.HIGH: [986, 30, 22, 15, 41, 16],
	},
	# 36
	{
		ErrorCorrection.LOW: [2434, 30, 6, 121, 14, 122],
		ErrorCorrection.MEDIUM: [1914, 28, 6, 47, 34, 48],
		ErrorCorrection.QUARTILE: [1354, 30, 46, 24, 10, 25],
		ErrorCorrection.HIGH: [1054, 30, 2, 15, 64, 16],
	},
	# 37
	{
		ErrorCorrection.LOW: [2566, 30, 17, 122, 4, 123],
		ErrorCorrection.MEDIUM: [1992, 28, 29, 46, 14, 47],
		ErrorCorrection.QUARTILE: [1426, 30, 49, 24, 10, 25],
		ErrorCorrection.HIGH: [1096, 30, 24, 15, 46, 16],
	},
	# 38
	{
		ErrorCorrection.LOW: [2702, 30, 4, 122, 18, 123],
		ErrorCorrection.MEDIUM: [2102, 28, 13, 46, 32, 47],
		ErrorCorrection.QUARTILE: [1502, 30, 48, 24, 14, 25],
		ErrorCorrection.HIGH: [1142, 30, 42, 15, 32, 16],
	},
	# 39
	{
		ErrorCorrection.LOW: [2812, 30, 20, 117, 4, 118],
		ErrorCorrection.MEDIUM: [2216, 28, 40, 47, 7, 48],
		ErrorCorrection.QUARTILE: [1582, 30, 43, 24, 22, 25],
		ErrorCorrection.HIGH: [1222, 30, 10, 15, 67, 16],
	},
	# 40
	{
		ErrorCorrection.LOW: [2956, 30, 19, 118, 6, 119],
		ErrorCorrection.MEDIUM: [2334, 28, 18, 47, 31, 48],
		ErrorCorrection.QUARTILE: [1666, 30, 34, 24, 34, 25],
		ErrorCorrection.HIGH: [1276, 30, 20, 15, 61, 16],
	},
]

## sorted by version
const _ALIGNMENT_PATTERN_POSITIONS: Array[Array] = [
	[],
	[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],
]

## remainder bits after structured data bits
const _REMAINDER_BITS: Array[int] = [0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0]

const _DARK: int = 1
const _LIGHT: int = 0

static var _number_rx: RegEx = RegEx.create_from_string("[^\\d]*")
static var _alphanumeric_rx: RegEx = RegEx.create_from_string("[^0-9A-Z $%*+\\-.\\/:]*")

## this can be either a String or PackedByteArray, based on the current encoding mode
var _input_data: Variant = "":
	get = get_input_data
## Encoding Mode
var mode: Mode = Mode.NUMERIC:
	set = set_mode
## Error Correction
var error_correction: ErrorCorrection = ErrorCorrection.LOW:
	set = set_error_correction
## Set to true if you want to specify an ECI value.
var use_eci: bool = false:
	set = set_use_eci
## Extended Channel Interpretation (ECI) Value. Is only used if `use_eci` is true.
var eci_value: int = ECI.ISO_8859_1:
	set = set_eci_value
## Use automatically the smallest version
var auto_version: bool = true:
	set = set_auto_version
## Version
## Will be changed on encoding to the used version if auto_version is true
var version: int = 1:
	set = set_version
var auto_mask_pattern: bool = true:
	set = set_auto_mask_pattern
## Will be changed on encoding to the used mask pattern if auto_mask_pattern is true
var mask_pattern: int = 0:
	set = set_mask_pattern

func set_auto_version(new_auto_version: bool) -> void:
	if new_auto_version == auto_version:
		return
	auto_version = new_auto_version

func set_version(new_version: int) -> void:
	if new_version == version:
		return
	version = clampi(new_version, 1, 40)

func set_error_correction(new_error_correction: ErrorCorrection) -> void:
	if new_error_correction == error_correction:
		return
	error_correction = new_error_correction

func set_mode(new_mode: Mode) -> void:
	if new_mode == mode:
		return
	mode = new_mode

	match mode:
		Mode.NUMERIC, Mode.ALPHANUMERIC, Mode.KANJI:
			self._input_data = ""
		Mode.BYTE:
			self._input_data = PackedByteArray()

func set_use_eci(new_use_eci: bool) -> void:
	if new_use_eci == use_eci:
		return
	use_eci = new_use_eci

func set_eci_value(new_eci_value: int) -> void:
	if new_eci_value == eci_value:
		return
	if !ECI.values().has(new_eci_value):
		return
	eci_value = new_eci_value

func set_auto_mask_pattern(new_auto_mask_pattern: bool) -> void:
	if new_auto_mask_pattern == auto_mask_pattern:
		return
	auto_mask_pattern = new_auto_mask_pattern

func set_mask_pattern(new_mask_pattern: int) -> void:
	if new_mask_pattern == mask_pattern:
		return
	mask_pattern = clampi(new_mask_pattern, 0, 7)

## return the data which was put in
func get_input_data() -> Variant:
	return _input_data

## returns the dimension for the current version.
func get_dimension() -> int:
	return _calc_dimension(self.version)

## returns ONE minimum version which fits the data
## the returned version is just an approach
## returns -1 if too huge
func calc_min_version() -> int:
	var input_size: int = self._get_input_data_size()
	for idx: int in range(_DATA_CAPACITY.size()):
		var cap: int = _DATA_CAPACITY[idx][ self.error_correction][ self.mode]
		if self.use_eci:
			# subtract roughly eci header size
			match self.mode:
				Mode.NUMERIC:
					cap -= 4
				Mode.ALPHANUMERIC:
					cap -= 3
				Mode.BYTE:
					cap -= 2
				Mode.KANJI:
					cap -= 1
		if input_size <= cap:
			return idx + 1
	return -1

static func _get_alphanumeric_number(chr: String) -> int:
	return _ALPHANUMERIC_CHARACTERS[chr]

# functions are adapted to our starting point 0, 0
static func _mask_pattern_fns() -> Array[Callable]:
	return [
		func(pos: Vector2i) -> bool: return (pos.x + pos.y) % 2 == 0,
		func(pos: Vector2i) -> bool: return pos.y % 2 == 0,
		func(pos: Vector2i) -> bool: return pos.x % 3 == 0,
		func(pos: Vector2i) -> bool: return (pos.x + pos.y) % 3 == 0,
		func(pos: Vector2i) -> bool: return (pos.x / 3 + pos.y / 2) % 2 == 0,
		func(pos: Vector2i) -> bool: return (pos.x * pos.y % 2) + (pos.x * pos.y) % 3 == 0,
		func(pos: Vector2i) -> bool: return ((pos.x * pos.y % 2) + (pos.x * pos.y) % 3) % 2 == 0,
		func(pos: Vector2i) -> bool: return (((pos.x + pos.y) % 2) + (pos.x * pos.y) % 3) % 2 == 0,
	]

# helper function check if a bit is set
static func _get_state(value: int, idx: int) -> bool:
	return value & (1 << idx) != 0

func _get_data_codeword_count() -> int:
	return _ERROR_CORRECTION[ self.version - 1][ self.error_correction][0]

func _get_ec_codeword_count() -> int:
	return _ERROR_CORRECTION[ self.version - 1][ self.error_correction][1]

func _get_ec_block_count(group: int) -> int:
	return _ERROR_CORRECTION[ self.version - 1][ self.error_correction][2 + (group - 1) * 2]

func _get_ec_block_codeword_count(group: int) -> int:
	return _ERROR_CORRECTION[ self.version - 1][ self.error_correction][3 + (group - 1) * 2]

static func _calc_dimension(version: int) -> int:
	return 21 + 4 * (version - 1)

func _get_alignment_pattern_positions() -> Array[Vector2i]:
	var dimension: int = self.get_dimension()
	var positions: Array[Vector2i] = []
	for row: int in _ALIGNMENT_PATTERN_POSITIONS[ self.version - 1]:
		for col: int in _ALIGNMENT_PATTERN_POSITIONS[ self.version - 1]:
			# do not overlap finder positions
			if (row - 2 < 8 && col - 2 < 8) || \
				(row - 2 < 8 && col + 2 > dimension - 8) || \
				(row + 2 >= dimension - 8 && col - 2 < 8):
				continue
			positions.append(Vector2i(row, col))
	return positions

static func _get_remainder_bits(version: int) -> int:
	return _REMAINDER_BITS[version - 1]

func _get_input_data_size() -> int:
	match typeof(self._input_data):
		TYPE_STRING:
			return self._input_data.length()
		TYPE_PACKED_BYTE_ARRAY:
			return self._input_data.size()
	return 0

func _get_char_count_size() -> int:
	if self.version < 10:
		match self.mode:
			Mode.NUMERIC:
				return 10
			Mode.ALPHANUMERIC:
				return 9
			Mode.BYTE:
				return 8
			Mode.KANJI:
				return 8
	elif self.version < 27:
		match self.mode:
			Mode.NUMERIC:
				return 12
			Mode.ALPHANUMERIC:
				return 11
			Mode.BYTE:
				return 16
			Mode.KANJI:
				return 10
	else:
		match self.mode:
			Mode.NUMERIC:
				return 14
			Mode.ALPHANUMERIC:
				return 13
			Mode.BYTE:
				return 16
			Mode.KANJI:
				return 12
	return 0

# TODO: TEST IF STATIC VAR WORKS
static func _static_init() -> void:
	# TODO: static init is not called in editor if not @tool
	if _number_rx == null:
		_number_rx = RegEx.create_from_string("[^\\d]*")
	# TODO: static init is not called in editor if not @tool
	if _alphanumeric_rx == null:
		_alphanumeric_rx = RegEx.create_from_string("[^0-9A-Z $%*+\\-.\\/:]*")

func _init(error_correction_: ErrorCorrection = ErrorCorrection.LOW) -> void:
	self.error_correction = error_correction_

	# TODO: static init is not called in editor if not @tool
	if Engine.is_editor_hint():
		_static_init()

## generate an QR code image
static func generate_image(qr_data: PackedByteArray, module_size: int = 1, light_module_color: Color = Color.WHITE, dark_module_color: Color = Color.BLACK, quiet_zone_size: int = 4) -> Image:
	module_size = maxi(1, module_size)
	quiet_zone_size = maxi(0, quiet_zone_size)

	var dimension: int = sqrt(qr_data.size())
	var image_size: int = (dimension + 2 * quiet_zone_size) * module_size
	var image: Image = Image.create_empty(image_size, image_size, false, Image.FORMAT_RGB8)
	image.fill(light_module_color)

	for y: int in range(dimension):
		for x: int in range(dimension):
			var color: Color = Color.PINK
			match qr_data[x + y * dimension]:
				_LIGHT:
					color = light_module_color
				_DARK:
					color = dark_module_color
			for offset_x: int in range(module_size):
				for offset_y: int in range(module_size):
					image.set_pixel((x + quiet_zone_size) * module_size + offset_x, (y + quiet_zone_size) * module_size + offset_y, color)

	return image

func put_numeric(number: String) -> void:
	self.mode = Mode.NUMERIC
	self._input_data = _number_rx.sub(number, "", true)

func put_alphanumeric(text: String) -> void:
	self.mode = Mode.ALPHANUMERIC
	self._input_data = _alphanumeric_rx.sub(text, "", true)

func put_byte(data: PackedByteArray) -> void:
	self.mode = Mode.BYTE
	self._input_data = data

func put_kanji(data: String) -> void:
	self.mode = Mode.KANJI
	# clear invalid characters
	self._input_data = ShiftJIS.get_string_from_shift_jis_2004(ShiftJIS.to_shift_jis_2004_buffer(data))

## returns row by row
## to get row size use get_dimension
func encode() -> PackedByteArray:
	if self.auto_version:
		self.version = self.calc_min_version()

	var data_stream: BitStream = self._encode_data()
	var err_correction: Array[PackedByteArray] = self._error_correction(data_stream)
	var structured_data: BitStream = self._structure_data(data_stream, err_correction)
	var module_data: PackedByteArray = self._place_modules(structured_data)

	return self._mask_qr(module_data)

func _encode_data() -> BitStream:
	var stream: BitStream = BitStream.new()

	# add ECI header
	if self.use_eci:
		stream.append(0b0111, 4)
		if self.eci_value <= 127:
			stream.append(0, 1)
			stream.append(self.eci_value, 7)
		elif self.eci_value <= 16383:
			stream.append(0b10, 2)
			stream.append(self.eci_value, 14)
		else:
			stream.append(0b110, 3)
			stream.append(self.eci_value, 21)

	# add mode
	stream.append(int(self.mode), 4)

	# add character count indicator
	stream.append(self._get_input_data_size(), self._get_char_count_size())

	# add encoded data
	match self.mode:
		Mode.NUMERIC:
			self._encode_numeric(stream)
		Mode.ALPHANUMERIC:
			self._encode_alphanumeric(stream)
		Mode.BYTE:
			self._encode_byte(stream)
		Mode.KANJI:
			self._encode_kanji(stream)

	# add terminator
	var required_bytes: int = self._get_data_codeword_count()
	var terminator_size: int = mini(8 * required_bytes - stream.size(), 4)
	stream.append(0, terminator_size)

	# add bits to multiple of 8
	stream.append(0, (8 - stream.size() % 8) % 8)

	# pad bytes to capacity
	var missing_bytes = required_bytes - stream.size() / 8
	for idx: int in range(missing_bytes):
		if idx % 2 == 0:
			stream.append(236, 8)
		else:
			stream.append(17, 8)

	return stream

func _encode_numeric(stream: BitStream) -> void:
	assert(typeof(self._input_data) == TYPE_STRING)
	const GROUP_SIZE: int = 3

	for idx: int in range(ceili(self._input_data.length() / float(GROUP_SIZE))):
		var chars: String = self._input_data.substr(idx * GROUP_SIZE, GROUP_SIZE)
		var number: int = chars.to_int()
		var bit_count: int = 0
		match chars.length():
			3:
				bit_count = 10
			2:
				bit_count = 7
			1:
				bit_count = 4
		stream.append(number, bit_count)

func _encode_alphanumeric(stream: BitStream) -> void:
	assert(typeof(self._input_data) == TYPE_STRING)
	const GROUP_SIZE: int = 2

	for idx: int in range(ceili(self._input_data.length() / float(GROUP_SIZE))):
		var chars: String = self._input_data.substr(idx * GROUP_SIZE, GROUP_SIZE)
		var number: int = _get_alphanumeric_number(chars[0])
		if chars.length() == 2:
			number = 45 * number + _get_alphanumeric_number(chars[1])
			stream.append(number, 11)
		else:
			stream.append(number, 6)

func _encode_byte(stream: BitStream) -> void:
	assert(typeof(self._input_data) == TYPE_PACKED_BYTE_ARRAY)

	for val: int in self._input_data:
		stream.append(val, 8)

func _encode_kanji(stream: BitStream) -> void:
	assert(typeof(self._input_data) == TYPE_STRING)

	var jis_bytes: PackedByteArray = ShiftJIS.to_shift_jis_2004_buffer(self._input_data)
	for idx: int in range(jis_bytes.size() / 2):
		var value = jis_bytes.decode_u16(idx * 2)
		if value >= 0x8140 && value <= 0x9FFC:
			value = value - 0x8140
		elif value >= 0xE040 && value <= 0xEBBF:
			value = value - 0xC140
		value = ((value & 0xFF00) >> 8) * 0xC0 + (value & 0x00FF)
		stream.append(value, 13)

# returns an array of PackedByteArray's, structured as Group, Block [G1B1, G1B2, G1B3, G2B1, G2B2, ...]
func _error_correction(stream: BitStream) -> Array[PackedByteArray]:
	var data: PackedByteArray = stream.to_byte_array()

	var ec_words: int = self._get_ec_codeword_count()
	var group_blocks: PackedByteArray = [
		self._get_ec_block_count(1),
		self._get_ec_block_count(2),
	]
	var group_codewords: PackedByteArray = [
		self._get_ec_block_codeword_count(1),
		self._get_ec_block_codeword_count(2),
	]
	var groups: int = 1
	if group_blocks[1] > 0:
		groups += 1

	var err_corr: Array[PackedByteArray] = []
	for group_idx: int in range(groups):
		var block_size: int = group_codewords[group_idx]
		for block_idx: int in range(group_blocks[group_idx]):
			var start_idx: int = 0
			# add offset to current group
			for group_off: int in range(group_idx):
				start_idx += group_blocks[group_off] * group_codewords[group_off]
			start_idx = start_idx + block_idx * block_size
			var cur_data: PackedByteArray = data.slice(start_idx, start_idx + block_size)
			err_corr.append(ReedSolomon.encode(cur_data, ec_words))

	return err_corr

func _structure_data(data_stream: BitStream, err_correction: Array[PackedByteArray]) -> BitStream:
	if err_correction.size() == 1:
		var res: BitStream = data_stream.duplicate()
		res.append_byte_array(err_correction[0])
		# append remainder bits
		res.append(0, _get_remainder_bits(self.version))
		return res

	var res: BitStream = BitStream.new()
	var data_arr: PackedByteArray = data_stream.to_byte_array()
	var group_blocks: PackedByteArray = [
		self._get_ec_block_count(1),
		self._get_ec_block_count(2),
	]
	var group_codewords: Array[int] = [
		self._get_ec_block_codeword_count(1),
		self._get_ec_block_codeword_count(2),
	]
	var groups: int = 1
	if group_blocks[1] > 0:
		groups += 1

	# interleave data code words
	var max_code_words: int = group_codewords.max()
	for codeword_idx: int in range(max_code_words):
		for group_idx: int in range(groups):
			# if current group/block has not this much codewords skip
			if codeword_idx >= group_codewords[group_idx]:
				continue
			var group_offset: int = 0
			for group_off: int in range(group_idx):
				group_offset += group_blocks[group_off] * group_codewords[group_off]
			for block_idx: int in range(group_blocks[group_idx]):
				var idx: int = group_offset + codeword_idx + block_idx * group_codewords[group_idx]
				res.append(data_arr[idx], 8)

	# interleave error code words
	for word_idx: int in range(self._get_ec_codeword_count()):
		for block: int in range(err_correction.size()):
			res.append(err_correction[block][word_idx], 8)

	# append remainder bits
	res.append(0, _get_remainder_bits(self.version))

	return res

# pos is upper left black corner
# 7 x 7 size
static func _place_finder(data: PackedByteArray, dimension: int, pos: Vector2i) -> void:
	for row: int in range(7):
		for col: int in range(7):
			data[(pos.x + row) + (pos.y + col) * dimension] = _DARK
	for idx: int in range(5):
		data[(pos.x + 1 + idx) + (pos.y + 1) * dimension] = _LIGHT
		data[(pos.x + 1 + idx) + (pos.y + 5) * dimension] = _LIGHT
	for idx: int in range(3):
		data[(pos.x + 1) + (pos.y + 2 + idx) * dimension] = _LIGHT
		data[(pos.x + 5) + (pos.y + 2 + idx) * dimension] = _LIGHT

# pos is center
# 5 x 5 size
static func _place_align_pattern(data: PackedByteArray, dimension: int, pos: Vector2i) -> void:
	for row: int in range(5):
		for col: int in range(5):
			data[(pos.x - 2 + row) + (pos.y - 2 + col) * dimension] = _DARK
	for idx: int in range(3):
		data[(pos.x - 1 + idx) + (pos.y - 1) * dimension] = _LIGHT
		data[(pos.x - 1 + idx) + (pos.y + 1) * dimension] = _LIGHT
	data[(pos.x - 1) + (pos.y) * dimension] = _LIGHT
	data[(pos.x + 1) + (pos.y) * dimension] = _LIGHT

static func _place_separators(data: PackedByteArray, dimension: int) -> void:
	for idx: int in range(8):
		# upper left
		data[idx + 7 * dimension] = _LIGHT
		data[7 + idx * dimension] = _LIGHT
		# lower left
		data[idx + (dimension - 8) * dimension] = _LIGHT
		data[(dimension - 8) + idx * dimension] = _LIGHT
		# upper right
		data[(dimension - idx - 1) + 7 * dimension] = _LIGHT
		data[7 + (dimension - idx - 1) * dimension] = _LIGHT

static func _place_timing_patterns(data: PackedByteArray, dimension: int) -> void:
	for idx: int in range(dimension - 6 * 2):
		data[6 + idx + 6 * dimension] = (idx + 1) % 2
		data[6 + (6 + idx) * dimension] = (idx + 1) % 2

static func _is_data_module(dimension: int, alignment_pattern_pos: Array[Vector2i], pos: Vector2i) -> bool:
	# finder patterns with separators and format information area
	if (pos.x <= 8 && pos.y <= 8) || \
	   (pos.x >= dimension - 8 && pos.y <= 8) || \
	   (pos.x <= 8 && pos.y >= dimension - 8):
		return false
	
	# timing patterns (run through middle, already excluded in finder areas)
	if (pos.x == 6 && pos.y >= 8 && pos.y < dimension - 8) || \
	   (pos.y == 6 && pos.x >= 8 && pos.x < dimension - 8):
		return false
	
	# version information area (version >= 7 only)
	if dimension >= 45:
		# Bottom-left: 6 rows × 3 columns
		if pos.x <= 5 && pos.y >= dimension - 11 && pos.y <= dimension - 9:
			return false
		# Top-right: 3 rows × 6 columns  
		if pos.y <= 5 && pos.x >= dimension - 11 && pos.x <= dimension - 9:
			return false
	
	# alignment patterns
	for align_pos: Vector2i in alignment_pattern_pos:
		if pos.x >= align_pos.x - 2 && pos.x <= align_pos.x + 2 && \
		   pos.y >= align_pos.y - 2 && pos.y <= align_pos.y + 2:
			return false
	
	return true

static func _place_data(data: PackedByteArray, dimension: int, alignment_pattern_pos: Array[Vector2i], structured_data: BitStream) -> void:
	var data_idx: int = 0
	# base column where to go up or down
	var base_col: int = dimension - 1
	var upwards: bool = true

	while base_col > 0:
		# skip vertical timing pattern
		if base_col == 6:
			base_col -= 1

		for row: int in range(dimension):
			if upwards:
				row = dimension - 1 - row
			for offset: int in range(2):
				var pos: Vector2i = Vector2i(base_col - offset, row)
				if _is_data_module(dimension, alignment_pattern_pos, pos):
					data[pos.x + pos.y * dimension] = int(structured_data.get_bit(data_idx))
					data_idx += 1

		base_col -= 2
		upwards = !upwards

	# all data modules placed
	assert(data_idx == structured_data.size(), "failed to place all data (%d of %d)" % [data_idx, structured_data.size()])

static func _calc_mask_rating(data: PackedByteArray, dimension: int) -> int:
	var rating: int = 0

	# condition 1
	# horizontal
	for y: int in range(dimension):
		var count: int = 0
		var block_value: int = 0
		for x: int in range(dimension):
			var cur_value: int = data[x + y * dimension]
			if cur_value == block_value:
				count += 1
			else:
				if count >= 5:
					rating += count - 2
				count = 1
				block_value = cur_value
		if count >= 5:
			rating += count - 2
	# vertical
	for x: int in range(dimension):
		var count: int = 0
		var block_value: int = 0
		for y: int in range(dimension):
			var cur_value: int = data[x + y * dimension]
			if cur_value == block_value:
				count += 1
			else:
				if count >= 5:
					rating += count - 2
				count = 1
				block_value = cur_value
		if count >= 5:
			rating += count - 2

	# condition 2
	for x: int in range(dimension - 1):
		for y: int in range(dimension - 1):
			var val: int = data[x + y * dimension] + data[x + 1 + y * dimension] + data[x + (y + 1) * dimension] + data[x + 1 + (y + 1) * dimension]
			if val == 0 || val == 4:
				rating += 3

	# condition 3
	for y: int in range(dimension):
		for x: int in range(dimension - 6):
			var start_idx: int = x + y * dimension
			if (data[start_idx]
				&& !data[start_idx + 1]
				&& data[start_idx + 2]
				&& data[start_idx + 3]
				&& data[start_idx + 4]
				&& !data[start_idx + 5]
				&& data[start_idx + 6]):
					if x >= 4 && !data[start_idx - 1] && !data[start_idx - 2] && !data[start_idx - 3] && !data[start_idx - 4]:
						rating += 40
					if x <= (dimension - 11) && !data[start_idx + 7] && !data[start_idx + 8] && !data[start_idx + 9] && !data[start_idx + 10]:
						rating += 40

	for x: int in range(dimension):
		for y: int in range(dimension - 6):
			if (data[x + y * dimension]
				&& !data[x + (y + 1) * dimension]
				&& data[x + (y + 2) * dimension]
				&& data[x + (y + 3) * dimension]
				&& data[x + (y + 4) * dimension]
				&& !data[x + (y + 5) * dimension]
				&& data[x + (y + 6) * dimension]):
					if y >= 4 && !data[x + (y - 1) * dimension] && !data[x + (y - 2) * dimension] && !data[x + (y - 3) * dimension] && !data[x + (y - 4) * dimension]:
						rating += 40
					if y <= (dimension - 11) && !data[x + (y + 7) * dimension] && !data[x + (y + 8) * dimension] && !data[x + (y + 9) * dimension] && !data[x + (y + 10) * dimension]:
						rating += 40

	# condition 4
	var dark_mods: int = data.count(_DARK)
	var ratio: float = dark_mods / float(dimension * dimension)
	var percent: int = int((ratio * 100) - 50)
	rating += absi(percent) / 5 * 10
	return rating

static func _place_format(qr_data: PackedByteArray, dimension: int, error_corr: ErrorCorrection, mask_pattern_val: int) -> void:
	var base_code: int = (int(error_corr) << 3) | mask_pattern_val

	var code: int = base_code
	for _idx: int in range(10):
		code = (code << 1) ^ ((code >> 9) * 0x537)
	code = (base_code << 10 | code) ^ 0x5412

	# upper left finder
	for idx: int in range(8):
		# skip timing pattern
		var pos: int = idx
		if idx > 5:
			pos += 1
		# horizontal
		qr_data[pos + 8 * dimension] = int(_get_state(code, 14 - idx))
		# vertical
		qr_data[8 + pos * dimension] = int(_get_state(code, idx))
	# lower left finder
	for idx: int in range(7):
		qr_data[8 + (dimension - 1 - idx) * dimension] = int(_get_state(code, 14 - idx))
	# upper right finder
	for idx: int in range(8):
		qr_data[(dimension - 1 - idx) + 8 * dimension] = int(_get_state(code, idx))

static func _place_version(qr_data: PackedByteArray, version: int) -> void:
	if version < 7:
		return

	var code: int = version
	for _idx: int in range(12):
		code = (code << 1) ^ ((code >> 11) * 0x1F25)
	code = version << 12 | code

	var dimension: int = _calc_dimension(version)
	for idx: int in range(18):
		var x: int = idx / 3
		var y: int = dimension - 11 + idx % 3
		qr_data[x + y * dimension] = int(_get_state(code, idx))
		qr_data[y + x * dimension] = int(_get_state(code, idx))

# returns qr module data, ordered by rows
# (col/x, row/y)       | index
# (0, 0) (1, 0) (2, 0) | 0, 1, 2
# (0, 1) (1, 1) (2, 1) | 3, 4, 5
# (0, 2) (1, 2) (2, 2) | 6, 7, 8
func _place_modules(structured_data: BitStream) -> PackedByteArray:
	var qr_data: PackedByteArray = PackedByteArray()
	var dimension: int = self.get_dimension()
	qr_data.resize(dimension * dimension)

	# place upper left finder
	_place_finder(qr_data, dimension, Vector2i(0, 0))
	# place lower left finder
	_place_finder(qr_data, dimension, Vector2i(0, dimension - 7))
	# place upper right finder
	_place_finder(qr_data, dimension, Vector2i(dimension - 7, 0))
	_place_separators(qr_data, dimension)

	var alignment_pattern_pos: Array[Vector2i] = self._get_alignment_pattern_positions()
	for pos: Vector2i in alignment_pattern_pos:
		_place_align_pattern(qr_data, dimension, pos)

	_place_timing_patterns(qr_data, dimension)

	# dark module
	qr_data[8 + (dimension - 8) * dimension] = _DARK

	# place data
	_place_data(qr_data, dimension, alignment_pattern_pos, structured_data)

	return qr_data

static func _mask(qr_data: PackedByteArray, dimension: int, alignment_pattern_pos: Array[Vector2i], mask_pattern: int) -> void:
	var mask_fn: Callable = _mask_pattern_fns()[mask_pattern]

	for x: int in range(dimension):
		for y: int in range(dimension):
			var pos: Vector2i = Vector2i(x, y)
			if _is_data_module(dimension, alignment_pattern_pos, pos):
				var idx: int = x + y * dimension
				qr_data[idx] = int(mask_fn.call(pos)) ^ qr_data[idx]

# return mask pattern number
func _get_best_qr_mask(masked_qrs: Array[PackedByteArray], dimension: int) -> int:
	var min_idx: int = 0
	# integer max
	var cur_min_value: int = 9223372036854775807
	for idx: int in range(masked_qrs.size()):
		var rating: int = _calc_mask_rating(masked_qrs[idx], dimension)
		if rating < cur_min_value:
			min_idx = idx
			cur_min_value = rating

	return min_idx

func _mask_qr(qr_data: PackedByteArray) -> PackedByteArray:
	var dimension: int = self.get_dimension()
	var alignment_pattern_pos: Array[Vector2i] = self._get_alignment_pattern_positions()

	# apply mask pattern
	if ! self.auto_mask_pattern:
		_mask(qr_data, dimension, alignment_pattern_pos, self.mask_pattern)
		_place_format(qr_data, dimension, self.error_correction, self.mask_pattern)
		_place_version(qr_data, self.version)

		return qr_data

	# get best mask pattern
	var masked_qr: Array[PackedByteArray] = []
	var mask_fns: Array[Callable] = _mask_pattern_fns()

	for pattern_idx: int in range(mask_fns.size()):
		var cur_qr: PackedByteArray = qr_data.duplicate()
		_mask(cur_qr, dimension, alignment_pattern_pos, pattern_idx)
		# normally the format version is applied AFTER getting the best pattern, but will produce worse qr codes
		_place_format(cur_qr, dimension, self.error_correction, pattern_idx)
		_place_version(cur_qr, self.version)
		masked_qr.append(cur_qr)
	var best_mask: int = _get_best_qr_mask(masked_qr, dimension)
	self.mask_pattern = best_mask
	qr_data = masked_qr[best_mask]

	return qr_data

#### DEVEL TOOLS

static func _print_qr(data: PackedByteArray, dimension: int) -> void:
	for y: int in range(dimension):
		var row: String = ""
		for x: int in range(dimension):
			var value: int = data[y * dimension + x]
			match value:
				0:
					row += "⬜"
				1:
					row += "⬛"
				2:
					row += "🟨"
				3:
					row += "🟦"
				_:
					row += "🟥"
		print(row)

static func _bin_to_string(value: int, bits: int = 8) -> String:
	var val: String = ""
	for idx: int in range(bits):
		if idx % 4 == 0:
			val = " " + val
		val = str(int(bool(value & (1 << idx)))) + val
	return val.strip_edges()

static func _arr_to_string(arr: PackedByteArray) -> String:
	var val: String = ""
	for byte: int in arr:
		val += "[" + _bin_to_string(byte, 8) + "] "
	return val.strip_edges()
