qred 0.1.2

A fast, light-footprint native QR code generator with custom color styling.
Documentation
use crate::{bit_buffer::BitBuffer, qr_spec::{get_qr_spec, EcLevel, QrSpec}};

// Mode enum: Types of supported modes with their bit values
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    Numeric         = 0b0001,
    Alphanumeric    = 0b0010,
    Byte            = 0b0100,
}

// QR code data bits: 4 bits (mode) + n bits (number of chars)

impl Mode {
    pub fn mode_indicator(&self) -> u32 {
        *self as u32
    }

    pub fn char_count_bits(&self) -> usize {
        match self {
            Mode::Numeric       => 10,
            Mode::Alphanumeric  => 9,
            Mode::Byte          => 8,
        }
    }
}

// UTIL: check if character is alphanumeric
fn is_alphanumeric_qr_char(c: char) -> bool {
    matches!(c, '0'..='9' | 'A'..='Z' | ' ' | '$' | '%' | '*' | '+' | '-' | '.' | '/' | ':')
}

// UTIL: get numeric value of alphanumeric chars
fn get_alphanumeric_value(c: char) -> u32 {
    match c {
        '0'..='9' => (c as u32) - ('0' as u32),
        'A'..='Z' => (c as u32) - ('A' as u32) + 10,
        ' ' => 36,
        '$' => 37,
        '%' => 38,
        '*' => 39,
        '+' => 40,
        '-' => 41,
        '.' => 42,
        '/' => 43,
        ':' => 44,
        _ => panic!("Invalid QR alphanumeric character"),
    }
}

// UTIL: Pad buffer bits to fill capacity
pub fn pad_buf_bits(buf: &mut BitBuffer, target_byte_capacity: usize) {
    let target_bits = target_byte_capacity * 8;

    let terminator_len = (target_bits.saturating_sub(buf.len_bits())).min(4);
    buf.push_bits(0, terminator_len);

    let remainder = buf.len_bits() % 8;
    if remainder != 0 {
        buf.push_bits(0, 8 - remainder);
    }

    let mut pad_byte = 0b1110_1100;  // 0xEC
    while buf.len_bits() < target_bits {
        buf.push_bits(pad_byte, 8);
        pad_byte = if pad_byte == 0b1110_1100 {
            0b0001_0001     // 0x11
        } else {
            0b1110_1100     // 0xEC
        };
    }
}

// Detect best mode type
pub fn detect_mode(text: &str) -> Mode {
    if text.chars().all(|c| c.is_ascii_digit()) {
        Mode::Numeric
    } else if text.chars().all(is_alphanumeric_qr_char) {
        Mode::Alphanumeric
    } else {
        Mode::Byte
    }
}

// Encode the text into bits
pub fn encode(text: &str, mode: Mode, spec: QrSpec) -> BitBuffer {
    let mut buf = BitBuffer::new();

    // Mode bits (0001/0010/0100)
    buf.push_bits(mode.mode_indicator(), 4);

    // Character count bits
    let char_count = text.chars().count();
    buf.push_bits(char_count as u32, spec.char_count_bits(mode));

    match mode {
        Mode::Numeric       => encode_numeric(text, &mut buf),
        Mode::Alphanumeric  => encode_alphanumeric(text, &mut buf),
        Mode::Byte          => encode_byte(text, &mut buf),
    }

    buf
}


// ENCODE: Numeric mode
fn encode_numeric(text: &str, buf: &mut BitBuffer) {
    let bytes = text.as_bytes();
    let mut i = 0;

    while i < bytes.len() {
        if i+3 <= bytes.len() {                         // 3 digits: 10 bits
            let val = (bytes[i] - b'0') as u32 * 100
                + (bytes[i+1] - b'0') as u32 * 10
                + (bytes[i+2] - b'0') as u32;
            buf.push_bits(val, 10);
            i += 3;
        } else if i+2 == bytes.len() {                  // 2 digits: 7 bits
            let val = (bytes[i] - b'0') as u32 * 10
                + (bytes[i+1] - b'0') as u32;
            buf.push_bits(val, 7);
            i += 2;
        } else {                                        // 1 digit: 4 bits
            let val = (bytes[i] - b'0') as u32;
            buf.push_bits(val, 4);
            i += 1;
        }
    }
}

// ENCODE: Alphanumeric mode
fn encode_alphanumeric(text: &str, buf: &mut BitBuffer) {
    let chars: Vec<char> = text.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        if i+2 <= chars.len() {                         // 2 characters: 11 bits
            let val = get_alphanumeric_value(chars[i]) * 45 + get_alphanumeric_value(chars[i+1]);
            buf.push_bits(val, 11);
            i += 2;
        } else {                                        // 1 character: 6 bits
            let val = get_alphanumeric_value(chars[i]);
            buf.push_bits(val, 6);
            i += 1;
        }
    }
}

// ENCODE: Byte mode
fn encode_byte(text: &str, buf: &mut BitBuffer) {
    for byte in text.bytes() {
        buf.push_bits(byte as u32, 8);
    }
}


// Data Preparation
pub fn prepare_data(text: &str, ec_level: Option<EcLevel>) -> Result<(BitBuffer, QrSpec), &'static str> {
    let ec = ec_level.unwrap_or(EcLevel::M);
    let mode = detect_mode(text);
    let char_count = text.chars().count();

    // Smallest version that fits payload
    let mut spec: Option<QrSpec> = None;

    for version in 1..=10 {
        let cur_spec = get_qr_spec(version, ec);
        let header_bits = 4 + cur_spec.char_count_bits(mode);

        let payload_bits = match mode {
            Mode::Numeric       => (char_count / 3) * 10 + match char_count % 3 { 2 => 7, 1 => 4, _ => 0},
            Mode::Alphanumeric  => (char_count / 2) * 11 + (char_count % 2) * 6,
            Mode::Byte          => text.len() * 8,
        };

        if header_bits + payload_bits <= cur_spec.data_codewords * 8 {
            spec = Some(cur_spec);
            break;
        }
    }

    let spec = spec.ok_or("Payload too large for versions 1-10")?;

    let mut buf = encode(text, mode, spec);
    pad_buf_bits(&mut buf, spec.data_codewords);

    Ok((buf, spec))
}




// TESTS

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_prepare_data_hello() {
        let (buf, spec) = prepare_data("1028931", None).unwrap();

        // "hello" fits in Version 1-M
        assert_eq!(spec.version, 1);
        assert_eq!(spec.ec_level, EcLevel::M);
        assert_eq!(spec.data_codewords, 16);

        // Entire padded buffer must match exact target bit capacity (16 bytes * 8 = 128 bits)
        assert_eq!(buf.len_bits(), 128);

        // Extract bytes to inspect the header and pad sequence
        let bytes = buf.to_bytes(); // Assuming you have a helper to get Vec<u8> from BitBuffer
        assert_eq!(bytes.len(), 16);
    }
}