#ifndef BYTEIO_H
#define BYTEIO_H

struct byte_writer {
    struct buffer buff;
    b32 overflowed;
    u8 *at;
};

struct byte_reader {
    struct buffer buff;
    b32 overflowed;
    u8 *at;
};

/* ========================== *
 * Constructor utils
 * ========================== */

INLINE struct byte_writer bw_create_from_buffer(struct buffer buff)
{
    return (struct byte_writer) {
        .buff = buff,
        .overflowed = false,
        .at = buff.data
    };
}

INLINE struct byte_reader br_create_from_buffer(struct buffer buff)
{
    return (struct byte_reader) {
        .buff = buff,
        .overflowed = false,
        .at = buff.data
    };
}

INLINE struct byte_writer bw_copy(struct byte_writer *bw)
{
    return *bw;
}

INLINE struct byte_reader br_copy(struct byte_reader *br)
{
    return *br;
}

/* Generate a buffer struct containing written bytes only */
INLINE struct buffer bw_get_written_buffer(struct byte_writer *bw)
{
    return (struct buffer) {
        .data = bw->buff.data,
        .size = bw->at - bw->buff.data
    };
}

INLINE u64 bw_pos(struct byte_reader *bw)
{
    return bw->at - bw->buff.data;
}

/* ========================== *
 * Overflow utils
 * ========================== */

INLINE b32 bw_overflow_check(struct byte_writer *bw, u64 amount)
{
    if (bw->overflowed || bw->at + amount > bw->buff.data + bw->buff.size) {
        ASSERT(false);
        bw->overflowed = true;
        return true;
    }
    return false;
}

INLINE b32 br_overflow_check(struct byte_reader *br, u64 amount)
{
    if (br->overflowed || br->at + amount > br->buff.data + br->buff.size) {
        ASSERT(false);
        br->overflowed = true;
        return true;
    }
    return false;
}

/* ========================== *
 * Write
 * ========================== */

void bw_seek(struct byte_writer *bw, u64 amount);
void bw_seek_to(struct byte_writer *bw, u64 pos);

void bw_write_buffer(struct byte_writer *bw, struct buffer buff);
void bw_write_u8(struct byte_writer *bw, u8 v);
void bw_write_u16(struct byte_writer *bw, u16 v);
void bw_write_u32(struct byte_writer *bw, u32 v);
void bw_write_u64(struct byte_writer *bw, u64 v);
void bw_write_i8(struct byte_writer *bw, i8 v);
void bw_write_i16(struct byte_writer *bw, i16 v);
void bw_write_i32(struct byte_writer *bw, i32 v);
void bw_write_i64(struct byte_writer *bw, i64 v);
void bw_write_var_uint(struct byte_writer *bw, u64 v);
void bw_write_var_sint(struct byte_writer *bw, i64 v);

INLINE u64 bw_bytes_left(struct byte_writer *bw)
{
    return bw->overflowed ? 0 : bw->buff.size - (bw->at - bw->buff.data);
}

/* ========================== *
 * Read
 * ========================== */

void br_seek(struct byte_reader *br, u64 amount);
void br_seek_to(struct byte_reader *br, u64 pos);

/* Will not read any data on overflow */
#define br_read_to_struct(br_ptr, var_ptr) (br_read_to_buffer(br_ptr, BUFFER(sizeof(*var_ptr), (u8 *)var_ptr)))

void *br_read_raw(struct byte_reader *br, u64 size);
void br_read_to_buffer(struct byte_reader *br, struct buffer buff);
u8 br_read_u8(struct byte_reader *br);
u16 br_read_u16(struct byte_reader *br);
u32 br_read_u32(struct byte_reader *br);
u64 br_read_u64(struct byte_reader *br);
i8 br_read_i8(struct byte_reader *br);
i16 br_read_i16(struct byte_reader *br);
i32 br_read_i32(struct byte_reader *br);
i64 br_read_i64(struct byte_reader *br);
u64 br_read_var_uint(struct byte_reader *br);
i64 br_read_var_sint(struct byte_reader *br);

INLINE u64 br_bytes_left(const struct byte_reader *br)
{
    return br->overflowed ? 0 : br->buff.size - (br->at - br->buff.data);
}

INLINE u64 br_pos(struct byte_reader *br)
{
    return br->at - br->buff.data;
}

#endif
