#pragma once

#include <string.h>

#include "pxl8_types.h"

void pxl8_bit_clear(u32* val, u8 bit);
u32 pxl8_bit_count(u32 val);
void pxl8_bit_set(u32* val, u8 bit);
bool pxl8_bit_test(u32 val, u8 bit);
void pxl8_bit_toggle(u32* val, u8 bit);

void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);

u8 pxl8_unpack_u8(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);

typedef struct {
    const u8* bytes;
    u32 offset;
    u32 size;
    bool overflow;
} pxl8_stream;

typedef struct {
    u8* bytes;
    u32 capacity;
    u32 offset;
    bool overflow;
} pxl8_write_stream;

static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
    return (pxl8_stream){
        .bytes = bytes,
        .offset = 0,
        .size = size,
        .overflow = false
    };
}

static inline pxl8_write_stream pxl8_write_stream_create(u8* bytes, u32 capacity) {
    return (pxl8_write_stream){
        .bytes = bytes,
        .capacity = capacity,
        .offset = 0,
        .overflow = false
    };
}

static inline bool pxl8_stream_can_read(const pxl8_stream* s, u32 count) {
    return !s->overflow && s->offset + count <= s->size;
}

static inline bool pxl8_stream_has_overflow(const pxl8_stream* s) {
    return s->overflow;
}

static inline bool pxl8_write_stream_has_overflow(const pxl8_write_stream* s) {
    return s->overflow;
}

static inline u32 pxl8_stream_position(const pxl8_stream* s) {
    return s->offset;
}

static inline u32 pxl8_write_stream_position(const pxl8_write_stream* s) {
    return s->offset;
}

static inline void pxl8_stream_seek(pxl8_stream* s, u32 offset) {
    s->offset = offset;
}

static inline u8 pxl8_read_u8(pxl8_stream* s) {
    if (s->offset + 1 > s->size) { s->overflow = true; return 0; }
    return pxl8_unpack_u8(s->bytes, s->offset++);
}

static inline u16 pxl8_read_u16(pxl8_stream* s) {
    if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
    u16 val = pxl8_unpack_u16_le(s->bytes, s->offset);
    s->offset += 2;
    return val;
}

static inline u16 pxl8_read_u16_be(pxl8_stream* s) {
    if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
    u16 val = pxl8_unpack_u16_be(s->bytes, s->offset);
    s->offset += 2;
    return val;
}

static inline u32 pxl8_read_u32(pxl8_stream* s) {
    if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
    u32 val = pxl8_unpack_u32_le(s->bytes, s->offset);
    s->offset += 4;
    return val;
}

static inline u32 pxl8_read_u32_be(pxl8_stream* s) {
    if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
    u32 val = pxl8_unpack_u32_be(s->bytes, s->offset);
    s->offset += 4;
    return val;
}

static inline u64 pxl8_read_u64(pxl8_stream* s) {
    if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
    u64 val = pxl8_unpack_u64_le(s->bytes, s->offset);
    s->offset += 8;
    return val;
}

static inline u64 pxl8_read_u64_be(pxl8_stream* s) {
    if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
    u64 val = pxl8_unpack_u64_be(s->bytes, s->offset);
    s->offset += 8;
    return val;
}

static inline i16 pxl8_read_i16(pxl8_stream* s) {
    return (i16)pxl8_read_u16(s);
}

static inline i32 pxl8_read_i32(pxl8_stream* s) {
    return (i32)pxl8_read_u32(s);
}

static inline f32 pxl8_read_f32(pxl8_stream* s) {
    if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
    f32 val = pxl8_unpack_f32_le(s->bytes, s->offset);
    s->offset += 4;
    return val;
}

static inline f32 pxl8_read_f32_be(pxl8_stream* s) {
    if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
    f32 val = pxl8_unpack_f32_be(s->bytes, s->offset);
    s->offset += 4;
    return val;
}

static inline void pxl8_read_bytes(pxl8_stream* s, void* dest, u32 count) {
    if (s->offset + count > s->size) { s->overflow = true; return; }
    memcpy(dest, &s->bytes[s->offset], count);
    s->offset += count;
}

static inline void pxl8_skip_bytes(pxl8_stream* s, u32 count) {
    if (s->offset + count > s->size) { s->overflow = true; return; }
    s->offset += count;
}

static inline const u8* pxl8_read_ptr(pxl8_stream* s, u32 count) {
    if (s->offset + count > s->size) { s->overflow = true; return NULL; }
    const u8* ptr = &s->bytes[s->offset];
    s->offset += count;
    return ptr;
}

static inline void pxl8_write_u8(pxl8_write_stream* s, u8 val) {
    if (s->offset + 1 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u8(s->bytes, s->offset++, val);
}

static inline void pxl8_write_u16(pxl8_write_stream* s, u16 val) {
    if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u16_le(s->bytes, s->offset, val);
    s->offset += 2;
}

static inline void pxl8_write_u16_be(pxl8_write_stream* s, u16 val) {
    if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u16_be(s->bytes, s->offset, val);
    s->offset += 2;
}

static inline void pxl8_write_u32(pxl8_write_stream* s, u32 val) {
    if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u32_le(s->bytes, s->offset, val);
    s->offset += 4;
}

static inline void pxl8_write_u32_be(pxl8_write_stream* s, u32 val) {
    if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u32_be(s->bytes, s->offset, val);
    s->offset += 4;
}

static inline void pxl8_write_u64(pxl8_write_stream* s, u64 val) {
    if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u64_le(s->bytes, s->offset, val);
    s->offset += 8;
}

static inline void pxl8_write_u64_be(pxl8_write_stream* s, u64 val) {
    if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_u64_be(s->bytes, s->offset, val);
    s->offset += 8;
}

static inline void pxl8_write_f32(pxl8_write_stream* s, f32 val) {
    if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_f32_le(s->bytes, s->offset, val);
    s->offset += 4;
}

static inline void pxl8_write_f32_be(pxl8_write_stream* s, f32 val) {
    if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
    pxl8_pack_f32_be(s->bytes, s->offset, val);
    s->offset += 4;
}

static inline void pxl8_write_bytes(pxl8_write_stream* s, const void* src, u32 count) {
    if (s->offset + count > s->capacity) { s->overflow = true; return; }
    memcpy(&s->bytes[s->offset], src, count);
    s->offset += count;
}
