/*
	oh4_lbp_serializer.h - v0.1 - public domain
	Authored 2026 by Eric Scrivner
	no warranty implied; use at your own risk

	Before including,

		#define OH4_LBP_SERIALIZER_IMPLEMENTATION

	in the file that you want to have the implementation.

ABOUT:

	Provides simple, versioned, little-endian binary serialization in the
	style of Media Molecule / Little Big Planet. Multi-byte values are read
	and written as native little-endian host data; big-endian hosts are not
	supported.

	See: https://handmade.network/p/29/swedish-cubes-for-unity/blog/p/2723-how_media_molecule_does_serialization

WORKFLOW:

	Define one serialize function per type. The same function handles both
	reading and writing. Fields are gated by a single monolithic
	`DataVersion`.

		LBPS_Serializer s = LBPS_Measure(CurrentVersion);
		MyTypeSerialize(&s, &value);
		required = LBPS_BytesRequired(&s);

		LBPS_Serializer w = LBPS_Write(buffer, required, CurrentVersion);
		MyTypeSerialize(&w, &value);

		LBPS_Serializer r = LBPS_Read(buffer, used, CurrentVersion);
		r.DataVersion = file_version;
		MyTypeSerialize(&r, &value);

	For bounded chunks and backpatched container headers:

		LBPS_PatchSite size_site = LBPS_ReserveU32(&w);
		LBPS_SIZE chunk_begin = LBPS_BytesUsed(&w);
		WriteChunkPayload(&w);
		LBPS_PatchU32(&w, size_site, (LBPS_U32)(LBPS_BytesUsed(&w) - chunk_begin));

		LBPS_Serializer chunk = LBPS_Chunk(&r, chunk_size);
		ReadChunkPayload(&chunk);
		LBPS_Finish(&chunk, kLBPS_Finish_Exact);

MODES:

	Read     consumes bytes from a caller-provided buffer
	Write    writes bytes into a caller-provided buffer
	Measure  computes the required byte count without touching memory

I/O:

	By default, LBPS works on caller-provided memory buffers. For larger
	files or custom storage backends, LBPS also supports stream serializers
	through user-provided absolute-offset callbacks:

		LBPS_IOReadAtFn
		LBPS_IOWriteAtFn
		LBPS_IOFlushFn
		LBPS_StreamRead(...)
		LBPS_StreamWrite(...)
		LBPS_Flush(...)

	The stream API is intentionally generic and does not depend on stdio or
	any platform-specific file layer. Callers provide the read/write/flush
	callbacks that make sense for their runtime. Stream callbacks operate on
	absolute byte offsets and may be backed by files, mapped memory, archive
	subranges, or any other random-access storage.

FIELD MACROS:

	LBPS_ADD(s, datum, version_added, TypeTag, FieldName)
	LBPS_ADD_BYTES(s, datum, version_added, FieldName, ByteCount)
	LBPS_REM(s, datum, version_added, version_removed, TypeTag, FieldName, DefaultValue)

	`LBPS_ADD` serializes a field that exists in the current runtime struct.
	`LBPS_ADD_BYTES` serializes a field as raw bytes with an explicit byte count.
	`LBPS_REM` materializes a temporary local for an older field that no
	longer exists in the runtime struct, allowing upgrade logic after the
	field list has been visited.

CONTAINER HELPERS:

	LBPS_BytesRemaining(s)
	LBPS_Finish(s, FinishMode)
	LBPS_Chunk(parent, Size)

	`LBPS_Chunk` creates a bounded child serializer over `Size` bytes while
	advancing the parent stream by that same amount. This is useful for
	size-delimited chunks and optional trailing sections. In Measure mode,
	bounded chunks are validated when you call `LBPS_Finish` on the child.

BACKPATCH HELPERS:

	LBPS_Reserve(s, Size)
	LBPS_ReserveU32(s)
	LBPS_PatchBytes(s, Site, Data, Size)
	LBPS_PatchU32(s, Site, Value)

	Reserve helpers advance the stream and hand back a patch site that can be
	filled later once the final size, offset, or checksum is known.

TYPE TAGS:

	Built-in tags:

		U8 S8 U16 S16 U32 S32 F32
		U64 S64 F64 (unless LBPS_NO_64_BIT is defined)

	Custom tags can be added by defining:

		#define LBPS_TYPE_MyTag       MyType
		#define LBPS_SERIALIZE_MyTag  MyTypeSerialize

OVERRIDES:

	#define LBPS_TYPES_ALREADY_DEFINED
		Provide your own LBPS_U8/LBPS_S32/etc typedefs.

	#define LBPS_NO_64_BIT
		Remove 64-bit integer and F64 helpers.

	#define LBPS_COPY_BYTES(dst, src, size)
		Override byte copy. If not provided, memcpy is used by default. Define
		LBPS_NO_CRT to force the internal byte-copy fallback instead.

EXAMPLE:

		enum {
			SV_Initial = 1,
			SV_Fouls,
			SV_NoFouls,
		};

		typedef struct ScoreState ScoreState;
		struct ScoreState {
			LBPS_S32 P1Score;
			LBPS_S32 P2Score;
		};

		static void ScoreStateSerialize(LBPS_Serializer* s, ScoreState* datum) {
			LBPS_ADD(s, datum, SV_Initial, S32, P1Score);
			LBPS_ADD(s, datum, SV_Initial, S32, P2Score);
			LBPS_REM(s, datum, SV_Fouls, SV_NoFouls, S32, P1Fouls, 0);
			LBPS_REM(s, datum, SV_Fouls, SV_NoFouls, S32, P2Fouls, 0);

			if ( s->Mode == kLBPS_Mode_Read && s->DataVersion < SV_NoFouls ) {
				datum->P1Score -= P1Fouls;
				datum->P2Score -= P2Fouls;
			}
		}

		LBPS_Serializer m = LBPS_Measure(SV_NoFouls);
		ScoreStateSerialize(&m, &state);
		LBPS_SIZE needed = LBPS_BytesRequired(&m);

		LBPS_Serializer w = LBPS_Write(buffer, needed, SV_NoFouls);
		ScoreStateSerialize(&w, &state);

		typedef struct MyStream MyStream;
		LBPS_SIZE MyReadAt(void* user, LBPS_SIZE offset, void* dst, LBPS_SIZE size);
		LBPS_SIZE MyWriteAt(void* user, LBPS_SIZE offset, void* src, LBPS_SIZE size);

		LBPS_IO io = {
			.User = stream,
			.ReadAt = MyReadAt,
			.WriteAt = MyWriteAt,
		};

		LBPS_Serializer sw = LBPS_StreamWrite(&io, SV_NoFouls);
		ScoreStateSerialize(&sw, &state);
		LBPS_Flush(&sw);
*/
#ifndef OH4_LBP_SERIALIZER_H
#define OH4_LBP_SERIALIZER_H

#ifndef LBPS_DEF
	#ifdef OH4_LBPS_STATIC
		#define LBPS_DEF static
	#else
		#ifdef __cplusplus
			#define LBPS_DEF extern "C"
		#else
			#define LBPS_DEF extern
		#endif
	#endif
#endif

/* === Configuration === */

#ifndef LBPS_TYPES_ALREADY_DEFINED
	#include <stdint.h>
	typedef uint8_t 	LBPS_U8;
	typedef int8_t		LBPS_S8;
	typedef uint16_t	LBPS_U16;
	typedef int16_t		LBPS_S16;
	typedef uint32_t	LBPS_U32;
	typedef int32_t		LBPS_S32;
	typedef float		LBPS_F32;
	#ifndef LBPS_NO_64_BIT
		typedef uint64_t	LBPS_U64;
		typedef int64_t		LBPS_S64;
		typedef double		LBPS_F64;
	#endif
#endif

#if !defined(LBPS_COPY_BYTES) && !defined(LBPS_NO_CRT)
	#include <string.h>
#endif

#ifndef LBPS_SIZE_TYPE_DEFINED
	#ifndef LBPS_NO_64_BIT
		typedef LBPS_U64	LBPS_SIZE;
	#else
		typedef LBPS_U32	LBPS_SIZE;
	#endif
#endif

/* === Enums === */

typedef enum eLBPS_Mode {
	kLBPS_Mode_Read,
	kLBPS_Mode_Write,
	kLBPS_Mode_Measure,

	kLBPS_Mode_COUNT
} eLBPS_Mode;

typedef enum eLBPS_Error {
	kLBPS_Error_None,
	kLBPS_Error_EndOfBuffer,
	kLBPS_Error_BadSize,
	kLBPS_Error_BadData,
	kLBPS_Error_OutOfSpace,

	kLBPS_Error_COUNT
} eLBPS_Error;

typedef enum eLBPS_FinishMode {
	kLBPS_Finish_Exact,
	kLBPS_Finish_AllowTrailing,

	kLBPS_FinishMode_COUNT
} eLBPS_FinishMode;

/* === Structs === */

/* Absolute-offset stream callbacks for generic random-access storage. */
typedef LBPS_SIZE LBPS_IOReadAtFn(void* user, LBPS_SIZE offset, void* dst, LBPS_SIZE size);
typedef LBPS_SIZE LBPS_IOWriteAtFn(void* user, LBPS_SIZE offset, void* src, LBPS_SIZE size);
typedef LBPS_S32 LBPS_IOFlushFn(void* user);

typedef struct LBPS_IO LBPS_IO;
struct LBPS_IO {
	void*				User;
	LBPS_IOReadAtFn*	ReadAt;
	LBPS_IOWriteAtFn*	WriteAt;
	LBPS_IOFlushFn*		Flush;
};

typedef struct LBPS_Serializer LBPS_Serializer;
struct LBPS_Serializer {
	LBPS_S32		IsWriting; /* 1 = writing or measuring, 0 = reading */
	eLBPS_Mode		Mode;
	eLBPS_Error		Error;
	LBPS_U32		DataVersion;
	LBPS_U32		LatestVersion;
	LBPS_SIZE		Offset;
	LBPS_SIZE		Required;
	LBPS_S32		IsBounded;
	LBPS_SIZE		Limit;
	LBPS_SIZE		BaseOffset;
	LBPS_S32		IsStream;
	LBPS_IO*		IO;
	LBPS_U8*		Start;
	LBPS_U8*		Ptr;
	LBPS_U8*		End;
};

typedef struct LBPS_PatchSite LBPS_PatchSite;
struct LBPS_PatchSite {
	LBPS_SIZE		Offset;
	LBPS_SIZE		Size;
};

/* === Functions === */

/* Constructors */
LBPS_DEF LBPS_Serializer	LBPS_Read(void* data, LBPS_SIZE size, LBPS_U32 latest_version);
LBPS_DEF LBPS_Serializer	LBPS_Write(void* data, LBPS_SIZE size, LBPS_U32 version);
LBPS_DEF LBPS_Serializer	LBPS_Measure(LBPS_U32 version);
/* Unbuffered stream serializers over user-provided absolute-offset callbacks. */
LBPS_DEF LBPS_Serializer	LBPS_StreamRead(LBPS_IO* io, LBPS_SIZE size, LBPS_U32 latest_version);
LBPS_DEF LBPS_Serializer	LBPS_StreamWrite(LBPS_IO* io, LBPS_U32 version);

/* State helpers */
LBPS_DEF LBPS_S32			LBPS_IsValid(LBPS_Serializer* s);
LBPS_DEF void				LBPS_SetError(LBPS_Serializer* s, eLBPS_Error error);
LBPS_DEF LBPS_SIZE			LBPS_BytesUsed(LBPS_Serializer* s);
LBPS_DEF LBPS_SIZE			LBPS_BytesRequired(LBPS_Serializer* s);
LBPS_DEF LBPS_SIZE			LBPS_BytesRemaining(LBPS_Serializer* s);
LBPS_DEF LBPS_S32			LBPS_Finish(LBPS_Serializer* s, eLBPS_FinishMode mode);
LBPS_DEF LBPS_S32			LBPS_Flush(LBPS_Serializer* s);
LBPS_DEF LBPS_Serializer	LBPS_Chunk(LBPS_Serializer* parent, LBPS_SIZE size);

/* Backpatch helpers */
LBPS_DEF LBPS_PatchSite		LBPS_Reserve(LBPS_Serializer* s, LBPS_SIZE size);
LBPS_DEF LBPS_PatchSite		LBPS_ReserveU32(LBPS_Serializer* s);
LBPS_DEF LBPS_S32			LBPS_PatchBytes(LBPS_Serializer* s, LBPS_PatchSite site, void* data, LBPS_SIZE size);
LBPS_DEF LBPS_S32			LBPS_PatchU32(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_U32 value);

/* Primitive serialization */
LBPS_DEF void				LBPS_SerializeU8(LBPS_Serializer* s, LBPS_U8* value);
LBPS_DEF void				LBPS_SerializeS8(LBPS_Serializer* s, LBPS_S8* value);
LBPS_DEF void				LBPS_SerializeU16(LBPS_Serializer* s, LBPS_U16* value);
LBPS_DEF void				LBPS_SerializeS16(LBPS_Serializer* s, LBPS_S16* value);
LBPS_DEF void				LBPS_SerializeU32(LBPS_Serializer* s, LBPS_U32* value);
LBPS_DEF void				LBPS_SerializeS32(LBPS_Serializer* s, LBPS_S32* value);
LBPS_DEF void				LBPS_SerializeF32(LBPS_Serializer* s, LBPS_F32* value);
LBPS_DEF void				LBPS_SerializeBytes(LBPS_Serializer* s, void* data, LBPS_SIZE size);
LBPS_DEF void				LBPS_Skip(LBPS_Serializer* s, LBPS_SIZE size);
LBPS_DEF void				LBPS_Align(LBPS_Serializer* s, LBPS_SIZE alignment, LBPS_U8 pad_byte);

#ifndef LBPS_NO_64_BIT
LBPS_DEF void				LBPS_SerializeU64(LBPS_Serializer* s, LBPS_U64* value);
LBPS_DEF void				LBPS_SerializeS64(LBPS_Serializer* s, LBPS_S64* value);
LBPS_DEF void				LBPS_SerializeF64(LBPS_Serializer* s, LBPS_F64* value);
#endif

/*
	=== Typed Field Helpers ===

	Typed field helpers for LBP-style field lists.

	Built-in tags are provided for primitives. Custom tags can be added by
	defining both `LBPS_TYPE_<Tag>` and `LBPS_SERIALIZE_<Tag>`.
*/
#define LBPS_TYPE_U8				LBPS_U8
#define LBPS_TYPE_S8				LBPS_S8
#define LBPS_TYPE_U16				LBPS_U16
#define LBPS_TYPE_S16				LBPS_S16
#define LBPS_TYPE_U32				LBPS_U32
#define LBPS_TYPE_S32				LBPS_S32
#define LBPS_TYPE_F32				LBPS_F32
#ifndef LBPS_NO_64_BIT
	#define LBPS_TYPE_U64			LBPS_U64
	#define LBPS_TYPE_S64			LBPS_S64
	#define LBPS_TYPE_F64			LBPS_F64
#endif

#define LBPS_SERIALIZE_U8			LBPS_SerializeU8
#define LBPS_SERIALIZE_S8			LBPS_SerializeS8
#define LBPS_SERIALIZE_U16			LBPS_SerializeU16
#define LBPS_SERIALIZE_S16			LBPS_SerializeS16
#define LBPS_SERIALIZE_U32			LBPS_SerializeU32
#define LBPS_SERIALIZE_S32			LBPS_SerializeS32
#define LBPS_SERIALIZE_F32			LBPS_SerializeF32
#ifndef LBPS_NO_64_BIT
	#define LBPS_SERIALIZE_U64		LBPS_SerializeU64
	#define LBPS_SERIALIZE_S64		LBPS_SerializeS64
	#define LBPS_SERIALIZE_F64		LBPS_SerializeF64
#endif

#define LBPS_ADD(_s, _datum, _field_added_version, _type_tag, _field_name) \
	if ( (_s)->DataVersion >= (_field_added_version) ) { \
		LBPS_SERIALIZE_##_type_tag((_s), &((_datum)->_field_name)); \
	}

#define LBPS_ADD_BYTES(_s, _datum, _field_added_version, _field_name, _byte_count) \
	if ( (_s)->DataVersion >= (_field_added_version) ) { \
		LBPS_SerializeBytes((_s), (_datum)->_field_name, (LBPS_SIZE)(_byte_count)); \
	}

#define LBPS_REM(_s, _datum, _field_added_version, _field_removed_version, _type_tag, _field_name, _default_value) \
	LBPS_TYPE_##_type_tag _field_name = (_default_value); \
	if ( (_s)->DataVersion >= (_field_added_version) && \
	     (_s)->DataVersion < (_field_removed_version) ) { \
		LBPS_SERIALIZE_##_type_tag((_s), &(_field_name)); \
	}

#if defined(OH4_LBP_SERIALIZER_IMPLEMENTATION)

#if !defined(LBPS_COPY_BYTES)
	#if defined(LBPS_NO_CRT)
		static void LBPS__FallbackCopyBytes(LBPS_U8* dst, LBPS_U8* src, LBPS_SIZE size) {
			while ( size > 0 ) {
				*dst++ = *src++;
				size -= 1;
			}
		}

		#define LBPS_COPY_BYTES(dst, src, size) LBPS__FallbackCopyBytes((LBPS_U8*)(dst), (LBPS_U8*)(src), (size))
	#else
		#define LBPS_COPY_BYTES(dst, src, size) memcpy((dst), (src), (size))
	#endif
#endif

static LBPS_S32 LBPS__AdvanceChecked(LBPS_Serializer* s, LBPS_SIZE size) {
	LBPS_SIZE max_value = (LBPS_SIZE)~(LBPS_SIZE)0;
	if ( s->Offset > max_value - size ) {
		LBPS_SetError(s, kLBPS_Error_BadSize);
		return( 0 );
	}
	return( 1 );
}

static void LBPS__Advance(LBPS_Serializer* s, LBPS_SIZE size) {
	s->Offset += size;
	if ( s->Mode != kLBPS_Mode_Read ) {
		s->Required = s->Offset;
	}
	if ( s->Mode != kLBPS_Mode_Measure && !s->IsStream ) {
		s->Ptr += size;
	}
}

static LBPS_S32 LBPS__AbsoluteOffset(LBPS_Serializer* s, LBPS_SIZE relative, LBPS_SIZE* out_offset) {
	if ( out_offset == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( s->BaseOffset > (LBPS_SIZE)~(LBPS_SIZE)0 - relative ) {
		LBPS_SetError(s, kLBPS_Error_BadSize);
		return( 0 );
	}
	*out_offset = s->BaseOffset + relative;
	return( 1 );
}

static LBPS_S32 LBPS__StreamWriteFill(LBPS_Serializer* s, LBPS_SIZE size, LBPS_U8 value) {
	LBPS_SIZE abs_off = 0;
	LBPS_SIZE relative = 0;
	LBPS_SIZE remaining = 0;
	LBPS_U8 temp[64];
	LBPS_SIZE fill_size = 0;
	if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( size == 0 ) {
		return( 1 );
	}
	while ( fill_size < (LBPS_SIZE)sizeof(temp) ) {
		temp[fill_size] = value;
		fill_size += 1;
	}
	relative = s->Offset;
	remaining = size;
	while ( remaining > 0 ) {
		LBPS_SIZE chunk_size = remaining;
		if ( chunk_size > (LBPS_SIZE)sizeof(temp) ) {
			chunk_size = (LBPS_SIZE)sizeof(temp);
		}
		if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
			return( 0 );
		}
		if ( s->IO->WriteAt(s->IO->User, abs_off, temp, chunk_size) != chunk_size ) {
			LBPS_SetError(s, kLBPS_Error_OutOfSpace);
			return( 0 );
		}
		relative += chunk_size;
		remaining -= chunk_size;
	}
	return( 1 );
}

static LBPS_S32 LBPS__StreamReadExact(LBPS_Serializer* s, LBPS_SIZE relative, void* dst, LBPS_SIZE size) {
	LBPS_SIZE abs_off = 0;
	LBPS_SIZE read_size = 0;
	if ( s->IO == 0 || s->IO->ReadAt == 0 || ( dst == 0 && size != 0 ) ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
		return( 0 );
	}
	read_size = s->IO->ReadAt(s->IO->User, abs_off, dst, size);
	if ( read_size != size ) {
		LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
		return( 0 );
	}
	return( 1 );
}

static LBPS_S32 LBPS__StreamWriteExact(LBPS_Serializer* s, LBPS_SIZE relative, void* src, LBPS_SIZE size) {
	LBPS_SIZE abs_off = 0;
	LBPS_SIZE write_size = 0;
	if ( s->IO == 0 || s->IO->WriteAt == 0 || ( src == 0 && size != 0 ) ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
		return( 0 );
	}
	write_size = s->IO->WriteAt(s->IO->User, abs_off, src, size);
	if ( write_size != size ) {
		LBPS_SetError(s, kLBPS_Error_OutOfSpace);
		return( 0 );
	}
	return( 1 );
}

static LBPS_S32 LBPS__CanRead(LBPS_Serializer* s, LBPS_SIZE size) {
	LBPS_SIZE remaining = 0;
	if ( !LBPS__AdvanceChecked(s, size) ) {
		return( 0 );
	}
	if ( size == 0 ) {
		return( 1 );
	}
	if ( s->IsStream ) {
		if ( s->IO == 0 || s->IO->ReadAt == 0 ) {
			LBPS_SetError(s, kLBPS_Error_BadData);
			return( 0 );
		}
		if ( s->IsBounded ) {
			remaining = LBPS_BytesRemaining(s);
			if ( remaining < size ) {
				LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
				return( 0 );
			}
		}
		return( 1 );
	}
	if ( s->Ptr == 0 || s->End == 0 ) {
		LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
		return( 0 );
	}
	remaining = (LBPS_SIZE)(s->End - s->Ptr);
	if ( remaining < size ) {
		LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
		return( 0 );
	}
	return( 1 );
}

static LBPS_S32 LBPS__CanWrite(LBPS_Serializer* s, LBPS_SIZE size) {
	LBPS_SIZE remaining = 0;
	if ( !LBPS__AdvanceChecked(s, size) ) {
		return( 0 );
	}
	if ( size == 0 ) {
		return( 1 );
	}
	if ( s->IsStream ) {
		if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
			LBPS_SetError(s, kLBPS_Error_BadData);
			return( 0 );
		}
		if ( s->IsBounded ) {
			remaining = LBPS_BytesRemaining(s);
			if ( remaining < size ) {
				LBPS_SetError(s, kLBPS_Error_OutOfSpace);
				return( 0 );
			}
		}
		return( 1 );
	}
	if ( s->Ptr == 0 || s->End == 0 ) {
		LBPS_SetError(s, kLBPS_Error_OutOfSpace);
		return( 0 );
	}
	remaining = (LBPS_SIZE)(s->End - s->Ptr);
	if ( remaining < size ) {
		LBPS_SetError(s, kLBPS_Error_OutOfSpace);
		return( 0 );
	}
	return( 1 );
}

static LBPS_S32 LBPS__PatchSiteInBounds(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_SIZE size) {
	LBPS_SIZE end = 0;
	LBPS_SIZE limit = 0;
	if ( size != site.Size ) {
		LBPS_SetError(s, kLBPS_Error_BadSize);
		return( 0 );
	}
	if ( site.Offset > (LBPS_SIZE)~(LBPS_SIZE)0 - size ) {
		LBPS_SetError(s, kLBPS_Error_BadSize);
		return( 0 );
	}
	end = site.Offset + size;
	limit = s->Required;
	if ( end > limit ) {
		LBPS_SetError(s, kLBPS_Error_BadSize);
		return( 0 );
	}
	return( 1 );
}

LBPS_DEF LBPS_Serializer LBPS_Read(void* data, LBPS_SIZE size, LBPS_U32 latest_version) {
	LBPS_Serializer result = { 0 };
	LBPS_U8* start = (LBPS_U8*)data;
	result.IsWriting = 0;
	result.Mode = kLBPS_Mode_Read;
	result.Error = ( start == 0 && size != 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
	result.DataVersion = 0;
	result.LatestVersion = latest_version;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 1;
	result.Limit = size;
	result.BaseOffset = 0;
	result.IsStream = 0;
	result.IO = 0;
	result.Start = start;
	result.Ptr = start;
	result.End = (start != 0) ? (start + size) : 0;
	return( result );
}

LBPS_DEF LBPS_Serializer LBPS_Write(void* data, LBPS_SIZE size, LBPS_U32 version) {
	LBPS_Serializer result = { 0 };
	LBPS_U8* start = (LBPS_U8*)data;
	result.IsWriting = 1;
	result.Mode = kLBPS_Mode_Write;
	result.Error = ( start == 0 && size != 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
	result.DataVersion = version;
	result.LatestVersion = version;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 1;
	result.Limit = size;
	result.BaseOffset = 0;
	result.IsStream = 0;
	result.IO = 0;
	result.Start = start;
	result.Ptr = start;
	result.End = (start != 0) ? (start + size) : 0;
	return( result );
}

LBPS_DEF LBPS_Serializer LBPS_Measure(LBPS_U32 version) {
	LBPS_Serializer result = { 0 };
	result.IsWriting = 1;
	result.Mode = kLBPS_Mode_Measure;
	result.Error = kLBPS_Error_None;
	result.DataVersion = version;
	result.LatestVersion = version;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 0;
	result.Limit = 0;
	result.BaseOffset = 0;
	result.IsStream = 0;
	result.IO = 0;
	result.Start = 0;
	result.Ptr = 0;
	result.End = 0;
	return( result );
}

LBPS_DEF LBPS_Serializer LBPS_StreamRead(LBPS_IO* io, LBPS_SIZE size, LBPS_U32 latest_version) {
	LBPS_Serializer result = { 0 };
	result.IsWriting = 0;
	result.Mode = kLBPS_Mode_Read;
	result.Error = ( io == 0 || io->ReadAt == 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
	result.DataVersion = 0;
	result.LatestVersion = latest_version;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 1;
	result.Limit = size;
	result.BaseOffset = 0;
	result.IsStream = 1;
	result.IO = io;
	result.Start = 0;
	result.Ptr = 0;
	result.End = 0;
	return( result );
}

LBPS_DEF LBPS_Serializer LBPS_StreamWrite(LBPS_IO* io, LBPS_U32 version) {
	LBPS_Serializer result = { 0 };
	result.IsWriting = 1;
	result.Mode = kLBPS_Mode_Write;
	result.Error = ( io == 0 || io->WriteAt == 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
	result.DataVersion = version;
	result.LatestVersion = version;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 0;
	result.Limit = 0;
	result.BaseOffset = 0;
	result.IsStream = 1;
	result.IO = io;
	result.Start = 0;
	result.Ptr = 0;
	result.End = 0;
	return( result );
}

LBPS_DEF LBPS_S32 LBPS_IsValid(LBPS_Serializer* s) {
	return( s->Error == kLBPS_Error_None );
}

LBPS_DEF void LBPS_SetError(LBPS_Serializer* s, eLBPS_Error error) {
	if ( s->Error == kLBPS_Error_None ) {
		s->Error = error;
	}
}

LBPS_DEF LBPS_SIZE LBPS_BytesUsed(LBPS_Serializer* s) {
	return( s->Offset );
}

LBPS_DEF LBPS_SIZE LBPS_BytesRequired(LBPS_Serializer* s) {
	return( s->Required );
}

LBPS_DEF LBPS_SIZE LBPS_BytesRemaining(LBPS_Serializer* s) {
	LBPS_SIZE result = 0;
	if ( s != 0 && s->IsBounded ) {
		if ( s->Offset < s->Limit ) {
			result = s->Limit - s->Offset;
		}
	}
	return( result );
}

LBPS_DEF LBPS_S32 LBPS_Finish(LBPS_Serializer* s, eLBPS_FinishMode mode) {
	LBPS_S32 result = 0;
	if ( s == 0 ) {
		return( 0 );
	}
	if ( s->Error != kLBPS_Error_None ) {
		return( 0 );
	}
	if ( s->IsBounded ) {
		if ( mode == kLBPS_Finish_Exact ) {
			if ( s->Offset != s->Limit ) {
				LBPS_SetError(s, kLBPS_Error_BadSize);
			}
		}
		else if ( mode == kLBPS_Finish_AllowTrailing ) {
			if ( s->Offset > s->Limit ) {
				LBPS_SetError(s, kLBPS_Error_BadSize);
			}
		}
		else {
			LBPS_SetError(s, kLBPS_Error_BadData);
		}
	}
	else if ( mode != kLBPS_Finish_Exact && mode != kLBPS_Finish_AllowTrailing ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
	}
	result = ( s->Error == kLBPS_Error_None );
	return( result );
}

LBPS_DEF LBPS_S32 LBPS_Flush(LBPS_Serializer* s) {
	LBPS_S32 result = 0;
	if ( s == 0 ) {
		return( 0 );
	}
	if ( s->Error != kLBPS_Error_None ) {
		return( 0 );
	}
	result = 1;
	if ( s->IsStream && s->IO != 0 && s->IO->Flush != 0 ) {
		result = s->IO->Flush(s->IO->User);
		if ( !result ) {
			LBPS_SetError(s, kLBPS_Error_BadData);
		}
	}
	return( result && s->Error == kLBPS_Error_None );
}

LBPS_DEF LBPS_Serializer LBPS_Chunk(LBPS_Serializer* parent, LBPS_SIZE size) {
	LBPS_Serializer result = { 0 };
	if ( parent == 0 ) {
		result.Error = kLBPS_Error_BadData;
		return( result );
	}

	result.IsWriting = parent->IsWriting;
	result.Mode = parent->Mode;
	result.Error = parent->Error;
	result.DataVersion = parent->DataVersion;
	result.LatestVersion = parent->LatestVersion;
	result.Offset = 0;
	result.Required = 0;
	result.IsBounded = 1;
	result.Limit = size;
	result.BaseOffset = 0;
	result.IsStream = parent->IsStream;
	result.IO = parent->IO;
	result.Start = parent->Ptr;
	result.Ptr = parent->Ptr;
	result.End = ( parent->Ptr != 0 ) ? ( parent->Ptr + size ) : 0;

	if ( parent->Error != kLBPS_Error_None ) {
		return( result );
	}
	if ( !LBPS__AbsoluteOffset(parent, parent->Offset, &result.BaseOffset) ) {
		result.Error = parent->Error;
		result.Start = 0;
		result.Ptr = 0;
		result.End = 0;
		return( result );
	}

	if ( parent->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(parent, size) ) {
			result.Error = parent->Error;
			result.Start = 0;
			result.Ptr = 0;
			result.End = 0;
			return( result );
		}
	}
	else if ( parent->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(parent, size) ) {
			result.Error = parent->Error;
			result.Start = 0;
			result.Ptr = 0;
			result.End = 0;
			return( result );
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(parent, size) ) {
			result.Error = parent->Error;
			result.Start = 0;
			result.Ptr = 0;
			result.End = 0;
			return( result );
		}
		result.Start = 0;
		result.Ptr = 0;
		result.End = 0;
	}
	if ( parent->IsStream ) {
		result.Start = 0;
		result.Ptr = 0;
		result.End = 0;
	}

	LBPS__Advance(parent, size);
	return( result );
}

LBPS_DEF LBPS_PatchSite LBPS_Reserve(LBPS_Serializer* s, LBPS_SIZE size) {
	LBPS_PatchSite result = { 0 };
	if ( s == 0 ) {
		return( result );
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( result );
	}
	result.Offset = s->Offset;
	result.Size = size;
	LBPS_Skip(s, size);
	return( result );
}

LBPS_DEF LBPS_PatchSite LBPS_ReserveU32(LBPS_Serializer* s) {
	return( LBPS_Reserve(s, 4) );
}

LBPS_DEF LBPS_S32 LBPS_PatchBytes(LBPS_Serializer* s, LBPS_PatchSite site, void* data, LBPS_SIZE size) {
	LBPS_S32 result = 0;
	LBPS_SIZE abs_off = 0;
	if ( s == 0 ) {
		return( 0 );
	}
	if ( s->Error != kLBPS_Error_None ) {
		return( 0 );
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( size != 0 && data == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return( 0 );
	}
	if ( !LBPS__PatchSiteInBounds(s, site, size) ) {
		return( 0 );
	}
	if ( s->Mode == kLBPS_Mode_Write && size != 0 ) {
		if ( s->IsStream ) {
			if ( !LBPS__AbsoluteOffset(s, site.Offset, &abs_off) ) {
				return( 0 );
			}
			if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
				LBPS_SetError(s, kLBPS_Error_BadData);
				return( 0 );
			}
			if ( s->IO->WriteAt(s->IO->User, abs_off, data, size) != size ) {
				LBPS_SetError(s, kLBPS_Error_OutOfSpace);
				return( 0 );
			}
		}
		else {
			if ( s->Start == 0 ) {
				LBPS_SetError(s, kLBPS_Error_BadData);
				return( 0 );
			}
			LBPS_COPY_BYTES(s->Start + site.Offset, (void*)data, size);
		}
	}
	result = 1;
	return( result );
}

LBPS_DEF LBPS_S32 LBPS_PatchU32(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_U32 value) {
	return( LBPS_PatchBytes(s, site, &value, 4) );
}

LBPS_DEF void LBPS_SerializeU8(LBPS_Serializer* s, LBPS_U8* value) {
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, 1) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamReadExact(s, s->Offset, value, 1) ) {
				return;
			}
		}
		else {
			*value = s->Ptr[0];
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, 1) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteExact(s, s->Offset, value, 1) ) {
				return;
			}
		}
		else {
			s->Ptr[0] = *value;
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, 1) ) {
			return;
		}
	}
	LBPS__Advance(s, 1);
}

LBPS_DEF void LBPS_SerializeS8(LBPS_Serializer* s, LBPS_S8* value) {
	LBPS_U8 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 1);
	}
	LBPS_SerializeU8(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 1);
	}
}

LBPS_DEF void LBPS_SerializeU16(LBPS_Serializer* s, LBPS_U16* value) {
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, 2) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamReadExact(s, s->Offset, value, 2) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(value, s->Ptr, 2);
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, 2) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteExact(s, s->Offset, value, 2) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(s->Ptr, value, 2);
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, 2) ) {
			return;
		}
	}
	LBPS__Advance(s, 2);
}

LBPS_DEF void LBPS_SerializeS16(LBPS_Serializer* s, LBPS_S16* value) {
	LBPS_U16 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 2);
	}
	LBPS_SerializeU16(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 2);
	}
}

LBPS_DEF void LBPS_SerializeU32(LBPS_Serializer* s, LBPS_U32* value) {
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, 4) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamReadExact(s, s->Offset, value, 4) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(value, s->Ptr, 4);
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, 4) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteExact(s, s->Offset, value, 4) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(s->Ptr, value, 4);
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, 4) ) {
			return;
		}
	}
	LBPS__Advance(s, 4);
}

LBPS_DEF void LBPS_SerializeS32(LBPS_Serializer* s, LBPS_S32* value) {
	LBPS_U32 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 4);
	}
	LBPS_SerializeU32(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 4);
	}
}

LBPS_DEF void LBPS_SerializeF32(LBPS_Serializer* s, LBPS_F32* value) {
	LBPS_U32 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 4);
	}
	LBPS_SerializeU32(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 4);
	}
}

LBPS_DEF void LBPS_SerializeBytes(LBPS_Serializer* s, void* data, LBPS_SIZE size) {
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( size == 0 ) {
		return;
	}
	if ( data == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, size) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamReadExact(s, s->Offset, data, size) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(data, s->Ptr, size);
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, size) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteExact(s, s->Offset, data, size) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(s->Ptr, data, size);
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, size) ) {
			return;
		}
	}
	LBPS__Advance(s, size);
}

LBPS_DEF void LBPS_Skip(LBPS_Serializer* s, LBPS_SIZE size) {
	LBPS_SIZE idx = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( size == 0 ) {
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, size) ) {
			return;
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, size) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteFill(s, size, 0) ) {
				return;
			}
		}
		else {
			for ( idx = 0; idx < size; ++idx ) {
				s->Ptr[idx] = 0;
			}
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, size) ) {
			return;
		}
	}
	LBPS__Advance(s, size);
}

LBPS_DEF void LBPS_Align(LBPS_Serializer* s, LBPS_SIZE alignment, LBPS_U8 pad_byte) {
	LBPS_SIZE pad_size = 0;
	LBPS_SIZE idx = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( alignment == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( alignment == 1 ) {
		return;
	}
	pad_size = s->Offset % alignment;
	if ( pad_size != 0 ) {
		pad_size = alignment - pad_size;
	}
	if ( pad_size == 0 ) {
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, pad_size) ) {
			return;
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, pad_size) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteFill(s, pad_size, pad_byte) ) {
				return;
			}
		}
		else {
			for ( idx = 0; idx < pad_size; ++idx ) {
				s->Ptr[idx] = pad_byte;
			}
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, pad_size) ) {
			return;
		}
	}
	LBPS__Advance(s, pad_size);
}

#ifndef LBPS_NO_64_BIT
LBPS_DEF void LBPS_SerializeU64(LBPS_Serializer* s, LBPS_U64* value) {
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode == kLBPS_Mode_Read ) {
		if ( !LBPS__CanRead(s, 8) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamReadExact(s, s->Offset, value, 8) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(value, s->Ptr, 8);
		}
	}
	else if ( s->Mode == kLBPS_Mode_Write ) {
		if ( !LBPS__CanWrite(s, 8) ) {
			return;
		}
		if ( s->IsStream ) {
			if ( !LBPS__StreamWriteExact(s, s->Offset, value, 8) ) {
				return;
			}
		}
		else {
			LBPS_COPY_BYTES(s->Ptr, value, 8);
		}
	}
	else {
		if ( !LBPS__AdvanceChecked(s, 8) ) {
			return;
		}
	}
	LBPS__Advance(s, 8);
}

LBPS_DEF void LBPS_SerializeS64(LBPS_Serializer* s, LBPS_S64* value) {
	LBPS_U64 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 8);
	}
	LBPS_SerializeU64(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 8);
	}
}

LBPS_DEF void LBPS_SerializeF64(LBPS_Serializer* s, LBPS_F64* value) {
	LBPS_U64 bits = 0;
	if ( s->Error != kLBPS_Error_None ) {
		return;
	}
	if ( value == 0 ) {
		LBPS_SetError(s, kLBPS_Error_BadData);
		return;
	}
	if ( s->Mode != kLBPS_Mode_Read ) {
		LBPS_COPY_BYTES(&bits, value, 8);
	}
	LBPS_SerializeU64(s, &bits);
	if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
		LBPS_COPY_BYTES(value, &bits, 8);
	}
}
#endif /* LBPS_NO_64_BIT */

#endif /* OH4_LBP_SERIALIZER_IMPLEMENTATION */

#endif /* OH4_LBP_SERIALIZER_H */
