/* Bio-Digital folded lattice helpers. */
#ifndef BIO_LATTICE_H_
#define BIO_LATTICE_H_

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define BIO_MANIFOLD_DIMS 16000u
#define BIO_NATIVE_TILE_DIMS 16384u

typedef struct bio_int4_addr {
    size_t byte_offset;
    uint8_t high_nibble;
} bio_int4_addr;

static inline size_t bio_int4_packed_bytes(size_t element_count) {
    return (element_count + 1u) >> 1;
}

static inline bio_int4_addr bio_int4_addr_from_index(size_t element_index) {
    bio_int4_addr addr;
    addr.byte_offset = element_index >> 1;
    addr.high_nibble = (uint8_t)(element_index & 1u);
    return addr;
}

static inline uint8_t bio_int4_get_nibble(const uint8_t *base, size_t element_index) {
    const bio_int4_addr addr = bio_int4_addr_from_index(element_index);
    const uint8_t packed = base[addr.byte_offset];
    return addr.high_nibble ? (uint8_t)((packed >> 4) & 0x0fu)
                            : (uint8_t)(packed & 0x0fu);
}

static inline void bio_int4_set_nibble(uint8_t *base, size_t element_index,
                                       uint8_t value) {
    const bio_int4_addr addr = bio_int4_addr_from_index(element_index);
    uint8_t packed = base[addr.byte_offset];
    value &= 0x0fu;
    if (addr.high_nibble) {
        packed = (uint8_t)((packed & 0x0fu) | (uint8_t)(value << 4));
    } else {
        packed = (uint8_t)((packed & 0xf0u) | value);
    }
    base[addr.byte_offset] = packed;
}

#ifdef __cplusplus
}
#endif

#endif /* BIO_LATTICE_H_ */
