#ifndef JO_QR_H
#define JO_QR_H

typedef enum {
    jo_qr_Ecc_LOW,
    jo_qr_Ecc_MEDIUM,
    jo_qr_Ecc_QUARTILE,
    jo_qr_Ecc_HIGH
} jo_qr_Ecc;

typedef struct {
    int size;
    int modules[177][177];
} jo_qr_code;

#define JO_QR_VERSION 5
#define JO_QR_SIZE (4 * JO_QR_VERSION + 17)  // 37 for version 5
#define JO_QR_TOTAL_CODEWORDS 134

jo_qr_code jo_qr_encodeText(const char *text, jo_qr_Ecc ecc);

#ifdef JO_QR_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>

typedef struct {
    int exp[512];
    int log[256];
} jo_qr_gf_context;

static int jo_qr_gf_mul(int a, int b, const jo_qr_gf_context *gf) {
    return (!a || !b) ? 0 : gf->exp[(gf->log[a] + gf->log[b]) % 255];
}

static void jo_qr_appendBits(int val, int numBits, int *bitBuffer, int *bitLen) {
    for (int i = numBits - 1; i >= 0; i--)
        bitBuffer[(*bitLen)++] = (val >> i) & 1;
}

static void jo_qr_drawFinderPattern(int matrix[JO_QR_SIZE][JO_QR_SIZE], int isFunction[JO_QR_SIZE][JO_QR_SIZE], int row, int col) {
    const char *pattern = "1111111" "1000001" "1011101" "1011101" "1011101" "1000001" "1111111";
    for (int r = 0; r < 7; r++) {
        for (int c = 0; c < 7; c++) {
            matrix[row + r][col + c] = pattern[r * 7 + c] - '0';
            isFunction[row + r][col + c] = 1; // true
        }
    }
    // Draw a white border around the pattern.
    for (int r = row - 1; r < row + 8; r++) {
        for (int c = col - 1; c < col + 8; c++) {
            if (r < 0 || r >= JO_QR_SIZE || c < 0 || c >= JO_QR_SIZE) continue;
            if (r < row || r >= row + 7 || c < col || c >= col + 7) {
                matrix[r][c] = 0;
                isFunction[r][c] = 1;
            }
        }
    }
}

jo_qr_code jo_qr_encodeText(const char *text, jo_qr_Ecc ecc) {
    jo_qr_gf_context gf;
    gf.exp[0] = 1;
    for (int i = 1; i < 256; i++) {
        int x = gf.exp[i - 1] << 1;
        if (x & 0x100) x ^= 0x11d;
        gf.exp[i] = x;
    }
    for (int i = 0; i < 255; i++)
        gf.log[gf.exp[i]] = i;
    for (int i = 256; i < 512; i++)
        gf.exp[i] = gf.exp[i - 256];

    int dataCodewordsCount, ecCodewordsCount;
    switch (ecc) {
        case jo_qr_Ecc_MEDIUM:
            dataCodewordsCount = 84;
            ecCodewordsCount   = JO_QR_TOTAL_CODEWORDS - 84;
            break;
        case jo_qr_Ecc_QUARTILE:
            dataCodewordsCount = 62;
            ecCodewordsCount   = JO_QR_TOTAL_CODEWORDS - 62;
            break;
        case jo_qr_Ecc_HIGH:
            dataCodewordsCount = 46;
            ecCodewordsCount   = JO_QR_TOTAL_CODEWORDS - 46;
            break;
        case jo_qr_Ecc_LOW:
        default:
            dataCodewordsCount = 108;
            ecCodewordsCount   = JO_QR_TOTAL_CODEWORDS - 108;
            break;
    }
    int textLen = (int)strlen(text);
    int overhead = (JO_QR_VERSION < 10) ? 12 : 20;
    int maxTextLen = (dataCodewordsCount * 8 - overhead) / 8;
    if (textLen > maxTextLen) textLen = maxTextLen;
    
    int bitBuffer[1200] = { 0 }, bitLen = 0;
    jo_qr_appendBits(0x4, 4, bitBuffer, &bitLen);    // Mode indicator: byte mode
    jo_qr_appendBits(textLen, 8, bitBuffer, &bitLen);  // Character count
    for (int i = 0; i < textLen; i++) {
        jo_qr_appendBits((unsigned char)text[i], 8, bitBuffer, &bitLen);
    }
    int totalDataBits = dataCodewordsCount * 8;
    int remaining = totalDataBits - bitLen, term = (remaining < 4 ? remaining : 4);
    jo_qr_appendBits(0, term, bitBuffer, &bitLen);
    while (bitLen % 8 != 0) {
        jo_qr_appendBits(0, 1, bitBuffer, &bitLen);
    }

    unsigned char dataCodewords[JO_QR_TOTAL_CODEWORDS] = { 0 };
    int dataBytes = bitLen / 8;
    for (int i = 0; i < dataBytes && i < dataCodewordsCount; i++) {
        unsigned char cw = 0;
        for (int j = 0; j < 8; j++) {
            cw = (cw << 1) | bitBuffer[i * 8 + j];
        }
        dataCodewords[i] = cw;
    }
    int padIndex = 0;
    unsigned char padBytes[2] = { 0xEC, 0x11 };
    for (int i = dataBytes; i < dataCodewordsCount; i++) {
        dataCodewords[i] = padBytes[padIndex++ % 2];
    }

    unsigned char ecCodewords[JO_QR_TOTAL_CODEWORDS] = { 0 };
    unsigned char poly[600] = { 0 };
    poly[0] = 1;
    for (int i = 1; i <= ecCodewordsCount; i++)
        poly[i] = 0;
    for (int i = 0; i < ecCodewordsCount; i++) {
        for (int j = i + 1; j > 0; j--) {
            poly[j] ^= jo_qr_gf_mul(poly[j - 1], gf.exp[i], &gf);
        }
    }
    unsigned char buffer[600] = { 0 };
    memcpy(buffer, dataCodewords, dataCodewordsCount);
    for (int i = 0; i < dataCodewordsCount; i++) {
        int factor = buffer[i];
        if (factor) {
            for (int j = 0; j <= ecCodewordsCount; j++) {
                buffer[i + j] ^= jo_qr_gf_mul(factor, poly[j], &gf);
            }
        }
    }
    memcpy(ecCodewords, buffer + dataCodewordsCount, ecCodewordsCount);

    int totalCodewords = dataCodewordsCount + ecCodewordsCount;
    unsigned char finalCodewords[600];
    memcpy(finalCodewords, dataCodewords, dataCodewordsCount);
    memcpy(finalCodewords + dataCodewordsCount, ecCodewords, ecCodewordsCount);
    int finalBitLen = 0, finalBits[5000] = { 0 };
    for (int i = 0; i < totalCodewords; i++) {
        for (int j = 7; j >= 0; j--) {
            finalBits[finalBitLen++] = (finalCodewords[i] >> j) & 1;
        }
    }
    int matrix[JO_QR_SIZE][JO_QR_SIZE];
    int isFunction[JO_QR_SIZE][JO_QR_SIZE];
    for (int r = 0; r < JO_QR_SIZE; r++) {
        for (int c = 0; c < JO_QR_SIZE; c++) {
            matrix[r][c] = -1;
            isFunction[r][c] = 0;
        }
    }
    jo_qr_drawFinderPattern(matrix, isFunction, 0, 0);
    jo_qr_drawFinderPattern(matrix, isFunction, 0, JO_QR_SIZE - 7);
    jo_qr_drawFinderPattern(matrix, isFunction, JO_QR_SIZE - 7, 0);

    for (int c = 8; c < JO_QR_SIZE - 8; c++) {
        matrix[6][c] = (c % 2 == 0) ? 1 : 0;
        isFunction[6][c] = 1;
    }
    for (int r = 8; r < JO_QR_SIZE - 8; r++) {
        matrix[r][6] = (r % 2 == 0) ? 1 : 0;
        isFunction[r][6] = 1;
    }

    const char *pattern = "11111" "10001" "10101" "10001" "11111";
    for (int r = 0; r < 5; r++) {
        for (int c = 0; c < 5; c++) {
            matrix[28 + r][28 + c] = pattern[r * 5 + c] - '0';
            isFunction[28 + r][28 + c] = 1;
        }
    }

    for (int i = 0; i < 9; i++) {
        if (i != 6) {
            isFunction[8][i] = 1;
            isFunction[i][8] = 1;
        }
    }
    for (int i = 0; i < 8; i++) {
        isFunction[JO_QR_SIZE - 1 - i][8] = 1;
        isFunction[8][JO_QR_SIZE - 1 - i] = 1;
    }
    isFunction[JO_QR_SIZE - 8][8] = 1;
    matrix[JO_QR_SIZE - 8][8] = 1;

    int bitIndex = 0, direction = -1, col = JO_QR_SIZE - 1;
    while (col > 0) {
        if (col == 6) col--;
        for (int i = 0; i < JO_QR_SIZE; i++) {
            int r = (direction == -1) ? (JO_QR_SIZE - 1 - i) : i;
            for (int j = 0; j < 2; j++) {
                int c = col - j;
                if (isFunction[r][c]) continue;
                matrix[r][c] = (bitIndex < finalBitLen) ? finalBits[bitIndex++] : 0;
            }
        }
        col -= 2;
        direction = -direction;
    }

    for (int r = 0; r < JO_QR_SIZE; r++) {
        for (int c = 0; c < JO_QR_SIZE; c++) {
            if (!isFunction[r][c] && ((r + c) % 2 == 0)) {
                matrix[r][c] ^= 1;
            }
        }
    }

    int maskPattern = 0;
    int formatData = (1 << 3) | (maskPattern & 7);
    int formatInfo = formatData << 10;
    int generator = 0x537;
    for (int i = 14; i >= 10; i--) {
        if ((formatInfo >> i) & 1) {
            formatInfo ^= generator << (i - 10);
        }
    }
    formatInfo = (formatData << 10) | formatInfo;
    formatInfo = (formatInfo ^ 0x5412) & 0x7FFF;
    
    for (int i = 0; i < 6; i++)
        matrix[8][i] = (formatInfo >> (14 - i)) & 1;
    matrix[8][7] = (formatInfo >> 8) & 1;
    matrix[8][8] = (formatInfo >> 7) & 1;
    for (int i = 8; i < 15; i++)
        matrix[14 - i][8] = (formatInfo >> (14 - i)) & 1;
    for (int i = 0; i < 8; i++)
        matrix[JO_QR_SIZE - 1 - i][8] = (formatInfo >> i) & 1;
    for (int i = 8; i < 15; i++)
        matrix[8][JO_QR_SIZE - 1 - i] = (formatInfo >> i) & 1;
    
    jo_qr_code qr;
    qr.size = JO_QR_SIZE;
    for (int r = 0; r < JO_QR_SIZE; r++)
        for (int c = 0; c < JO_QR_SIZE; c++)
            qr.modules[r][c] = (matrix[r][c] == 1);
    return qr;
}

#endif // JO_QR_IMPLEMENTATION
#endif // JO_QR_H