/**
 * librg - a library for building simple and elegant cross-platform multiplayer client-server solutions.
 *
 * Usage:
 * #define LIBRG_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like:
 *
 * #define LIBRG_IMPLEMENTATION
 * #include <librg.h>
 *
 * Credits:
 * Vladyslav Hrytsenko (GitHub: inlife)
 * Dominik Madarasz (GitHub: zaklaus)
 *
 * Dependencies:
 * zpl.h
 * enet.h
 *
 * For the demo:
 * sdl2.h
 *
 * Version History:
 *
 * 5.0.6
 * - Fix forced_inline on librg__space_insert
 * 
 * 5.0.5
 *  - Fixes to selection and deduplication flow
 *
 * 5.0.3
 *  - Minor fixes by @markatk
 *
 * 5.0.2
 *  - Fixed issue related to visiblity destruction
 *
 * 5.0.1
 *  - Fixed entity visiblity states after disconnection
 *
 * 5.0.0
 *  - Changed API for visibility feature:
 *    - Instead of booleans setting whether or not entity would be included in the query or not
 *      a multiple constant-based states were inroduced:
 *        - LIBRG_VISIBILITY_DEFAULT - the original state of visibility, entity is only visible if it is in the stream range
 *        - LIBRG_ALWAYS_VISIBLE - the entity is visible disregarding if it is the stream range or not
 *        - LIBRG_ALWAYS_INVISIBLE - opposite of the above, entity will be always invisible
 *      Entity visibility can be set on per entity-to-entity or global levels. The entity-to-entity relation has a bigger
 *      priority, so f.e. setting entity to be LIBRG_ALWAYS_VISIBLE on relation level, will override global visibility.
 *   - Aditionally, if the virtual world feature is enabled, it gains main priotity over visibility, thus entities located in 2 different
 *     virtual worlds will not be able to see each other in spite of visibility settings.
 *
 * 4.1.5
 *  - Fix to data send
 *
 * 4.1.4
 *  - Fixed issue with async flow inconsitensies of control_generations
 *  - Fixed boolean validation in disconnection flow
 *  - Added proxying of user_data from msg to evt in disconnect event
 *
 * 4.1.1
 * - Added compile-time 'features':
 *     - Ability to enable/disable some librg compile-time features
 *     - Entity igore tables are now optional, and can be disabled
 *     - Implmented simple optional Virtual world feature for entities
 *     - Implemented a feature to enable/disable octree culler (falls back to linear check)
 *     - Multiple features can be combined
 * - Added 'generation' to entity control lists:
 *     Setting, removing and setting control to the same entity again with same owner
 *     will now distinct between old and new controllers, and messages still coming
 *     from old control generation will be rejected in favor of new ones.
 * - Added guard to minimum sized packet in receive for both sides
 * - Added spherical culler handler, and ability to do runtime switch (LIBRG_USE_RADIUS_CULLING)
 * - Added return codes to some functions @markatk
 * - Streamed entities are now going to be always returned in query for controlling peer
 * - Fixed issue with host setting on the server side
 * - Fixed nullptr crash on empty host string for client on connect
 * - Removed experimental multithreading code
 *
 * 4.1.0
 * - Added new, extended message methods and sending options
 *
 * 4.0.0
 * - Coding style changes and bug fixes
 *
 * 3.3.1
 * - Updated zpl dependencies
 * - Removed zpl_math dependency (replaced by internal module in zpl)
 *
 * 3.3.0
 * - Added ipv6 support
 * - Added safe bitstream reads for internal methods
 * - Updated enet to latest version (2.0.1, ipv6 support)
 * - Updated zpl to latest version
 *
 * 3.2.0
 * - Fixed minor memory client-side memory leak with empty control list
 * - Fixed issue with client stream update and removed entity on server
 * - Updated zpl to new major version, watch out for possible incompatibilities
 * - Added method for alloc/dealloc the librg_ctx, librg_data, librg_event for the bindings
 * - Added experimental support for update buffering, disabled by default, and not recommended to use
 * - Added built-in timesyncer, working on top of monotonic time, syncing client clock to server one
 * - Added helper methods: librg_time_now, librg_standard_deviation
 * - Changed ctx->tick_delay from u16 to f64 (slightly more precision)
 *
 * Version History:
 * 3.1.0
 * - Removed zpl_cull and zpl_event dependencies
 * - added librg_network_kick()
 * - saving current librg_address to ctx->network
 * - refactor to proper disconnection code
 * - exclude local client entity from LIBRG_CONNECTION_DISCONNECT
 * - moved options and some few other things to the implementation part
 * - fixed issue with replacing entity control
 * - fixed issue with add control queuing beign sent before create entity packet
 *
 * 3.0.7 - Fix for entity query dublication for player entities
 * 3.0.5 - Patched librg_callback_cb arg value
 * 3.0.4 - Fixed Android and iOS support
 * 3.0.3 - Small fixes
 * 3.0.2 - Dependency updates
 * 3.0.1 - minor api patch
 * 3.0.0 - contexts, major api changes, fried potato, other stuff
 *
 * 2.2.3 - fixed mem leak on net event
 * 2.2.2 - Fixed client issue with librg_message_send_instream_except
 * 2.2.1 - Fixed cpp issues with librg_data pointers
 * 2.2.0 - Inner message system rafactor
 * 2.1.0 - Inner bitstream refactors, with slight interface changes
 * 2.0.2 - C++ and MSVC related fixes
 * 2.0.0 - Initial C version rewrite
 *
 * Things TODO:
 *     find a nice way to decrease size on client for peer struct
 *     try to remember stuff about bistram safety
 *     fix disconnection code issues
 * - Add method to check if entity is in stream of other entity
 * - Add DEBUG packet size validation (FEATURE)
 * - refactoring librg_table (FEATURE)
 * - remove entity ignore for target entity that was disconnected/deleted (BUG)
 * - remove controller peer for entity, on owner disconnect (BUG)
 * - possibly adding stream_range to the query, to make it bi-sided (FEATURE)
 * - tree/space node flattening (FEATURE)
 *
 * Useful links:
 * - https://antriel.com/post/online-platformer-5/#server-update-data
 * - https://github.com/fnuecke/Space/blob/5ddfec5fc8139c4b0def8fdfc16b5f5357a8cd38/EngineController/AbstractTssClient.cs#L325
 *
 * License notice:
 *
 * Copyright 2017-2019 Vladyslav Hrytsenko
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef LIBRG_INCLUDE_H
#define LIBRG_INCLUDE_H

#define LIBRG_VERSION_MAJOR 5
#define LIBRG_VERSION_MINOR 0
#define LIBRG_VERSION_PATCH 5
#define LIBRG_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch))
#define LIBRG_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF)
#define LIBRG_VERSION_GET_MINOR(version) (((version)>>8)&0xFF)
#define LIBRG_VERSION_GET_PATCH(version) ((version)&0xFF)
#define LIBRG_VERSION LIBRG_VERSION_CREATE(LIBRG_VERSION_MAJOR, LIBRG_VERSION_MINOR, LIBRG_VERSION_PATCH)

/* include definitions */
#ifndef LIBRG_CUSTOM_INCLUDES
    #ifdef LIBRG_IMPLEMENTATION
        #define ZPL_IMPLEMENTATION
        #define ENET_IMPLEMENTATION
    #endif
    #include "zpl.h"

    //#ifdef ZPL_SYSTEM_WINDOWS
    //    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    //#endif

    #include "enet.h"
#endif

/* library mode (stastic or dynamic) */
#ifdef LIBRG_SHARED
    #if defined(_WIN32)
    #define LIBRG_API ZPL_EXTERN __declspec(dllexport)
    #else
    #define LIBRG_API ZPL_EXTERN __attribute__((visibility("default")))
    #endif
#else
    #ifndef LIBRG_API
    #define LIBRG_API ZPL_DEF
    #endif
#endif

#ifndef LIBRG_INTERNAL
#define LIBRG_INTERNAL zpl_internal
#endif

#define librg_global        zpl_global
#define librg_inline        ZPL_INLINE //zpl_inline
#define librg_internal      zpl_internal
#define librg_assert        ZPL_ASSERT
#define librg_assert_msg    ZPL_ASSERT_MSG
#define librg_lambda(name)  name

/* default constants/methods used */
#ifndef LIBRG_DATA_GROW_FORMULA
#define LIBRG_DATA_GROW_FORMULA(x) (2*(x) + 16)
#endif

#if !defined(librg_log)
#define librg_log zpl_printf
#endif

#if defined(LIBRG_DEBUG)
#define librg_dbg(fmt, ...) librg_log(fmt, ##__VA_ARGS__)
#else
#define librg_dbg(fmt, ...)
#endif

/* keeping enabled by default depecrecated */
/* naming support till next major release */
#ifndef LIBRG_NO_DEPRECATIONS
#define librg_ctx_t     librg_ctx
#define librg_data_t    librg_data
#define librg_entity_t  librg_entity
#define librg_address_t librg_address
#define librg_message_t librg_message
#define librg_event_t   librg_event
#define librg_peer_t    librg_peer
#define librg_host_t    librg_host
#define librg_packet_t  librg_packet
#endif

#ifdef __cplusplus
extern "C" {
#endif


// =======================================================================//
// !
// ! Library compile-time configure features
// !
// =======================================================================//

#define LIBRG_FEATURE_VIRTUAL_WORLDS 1      // enabled by default (define LIBRG_DISABLE_FEATURE_VIRTUAL_WORLDS before including to disable)
#define LIBRG_FEATURE_ENTITY_VISIBILITY 1   // enabled by default (define LIBRG_DISABLE_FEATURE_ENTITY_VISIBILITY before including to disable)
#define LIBRG_FEATURE_OCTREE_CULLER 1    // disabled by default (define LIBRG_DISABLE_FEATURE_OCTREE_CULLER before including to disable)

#if defined(LIBRG_FEATURE_VIRTUAL_WORLDS) && defined(LIBRG_DISABLE_FEATURE_VIRTUAL_WORLDS)
#undef LIBRG_FEATURE_VIRTUAL_WORLDS
#endif

#if defined(LIBRG_FEATURE_ENTITY_VISIBILITY) && defined(LIBRG_DISABLE_FEATURE_ENTITY_VISIBILITY)
#undef LIBRG_FEATURE_ENTITY_VISIBILITY
#endif

#if defined(LIBRG_FEATURE_OCTREE_CULLER) && defined(LIBRG_DISABLE_FEATURE_OCTREE_CULLER)
#undef LIBRG_FEATURE_OCTREE_CULLER
#endif

// =======================================================================//
// !
// ! Public API
// !
// =======================================================================//

struct librg_ctx;
struct librg_data;
struct librg_entity;
struct librg_address;
struct librg_message;
struct librg_event;

typedef ENetPeer   librg_peer;
typedef ENetHost   librg_host;
typedef ENetPacket librg_packet;

enum librg_mode         { LIBRG_MODE_SERVER, LIBRG_MODE_CLIENT };
enum librg_space_type   { LIBRG_SPACE_2D = 2, LIBRG_SPACE_3D = 3 };
enum librg_pointer_type { LIBRG_POINTER_CTX, LIBRG_POINTER_DATA, LIBRG_POINTER_EVENT };
enum librg_thread_state { LIBRG_THREAD_IDLE, LIBRG_THREAD_WORK, LIBRG_THREAD_EXIT };

/**
 * Simple host address
 * used to configure network on start
 */
typedef struct librg_address { i32 port; char *host; } librg_address;

typedef void (librg_entity_cb)(struct librg_ctx *ctx, struct librg_entity *entity);
typedef void (librg_message_cb)(struct librg_message *msg);
typedef void (librg_event_cb)(struct librg_event *event);
typedef librg_event_cb **librg_event_block; ///< zpl_array

ZPL_TABLE_DECLARE(extern, librg_table, librg_table_, u32);
ZPL_TABLE_DECLARE(static, librg_event_pool, librg_event_pool_, librg_event_block);

/**
 * Allocate librg ctx
 * (to be used inside bindings)
 */
LIBRG_API void *librg_allocate_ptr(enum librg_pointer_type pointer_type);
/**
 * Frees a pointer allocated by library
 * usually used in bindings.
 */
LIBRG_API void librg_release_ptr(void *ptr);
/**
 * Main initialization method
 * MUST BE called in the begging of your application
 */
LIBRG_API u32 librg_init(struct librg_ctx *ctx);
/**
 * Should be called at the end of
 * execution of the program
 */
LIBRG_API void librg_free(struct librg_ctx *ctx);
/**
 * Main tick method
 * MUST BE called in your loop
 * preferably w/o delays
 *
 * Calling tick ensures that packets will be sent and received,
 * world state will be recalculated and new update data will be created/used
 */
LIBRG_API void librg_tick(struct librg_ctx *ctx);

// =======================================================================//
// !
// ! Entities
// !
// =======================================================================//

#define librg_entity_id u32

enum librg_entity_flag {
    LIBRG_ENTITY_NONE              = 0,        /* general flag, all destroyed/non-created entities have it */
    LIBRG_ENTITY_ALIVE             = (1 << 0), /* general flag, all created entities have it */
    LIBRG_ENTITY_CLIENT            = (1 << 1), /* flag describing entities created for client peer */
    LIBRG_ENTITY_VISIBILITY        = (1 << 2), /* flag showing that entity has visibility overrides */
    LIBRG_ENTITY_QUERIED           = (1 << 3), /* flag showing that entity has a cached culler query */
    LIBRG_ENTITY_CONTROLLED        = (1 << 4), /* flag showing if the entity is controlled(streamed) by some peer */
    LIBRG_ENTITY_CONTROL_REQUESTED = (1 << 5), /* flag showing whether or not an entity control has been requested, but not yet sent */
    LIBRG_ENTITY_MARKED_REMOVAL    = (1 << 6), /* flag showing whether or not an entity is marked to be removed */
    LIBRG_ENTITY_UNUSED            = (1 << 7), /* flag showing whether the entity's space is unused */
    LIBRG_ENTITY_FLAG_LAST         = (1 << 8),
};

typedef struct librg_entity {
    u32 id;
    u32 type;
    u64 flags;

    zpl_vec3 position;
    f32 stream_range;

    void *user_data;
    struct librg_space *stream_branch;

    #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
    librg_table visibility;
    #endif

    #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
    u32 virual_world;
    #endif

    librg_table last_snapshot;

    librg_peer *client_peer;
    librg_peer *control_peer;

    u8 control_generation;

    librg_entity_id *last_query; ///< zpl_array
} librg_entity;

/**
 * Create entity and return handle
 */
LIBRG_API struct librg_entity *librg_entity_create(struct librg_ctx *ctx, u32 type);
/**
 * Try to find an existing entity by a provided id, or NULL
 */
LIBRG_API struct librg_entity *librg_entity_fetch(struct librg_ctx *ctx, librg_entity_id entity_id);
/**
 * Reuqest an entity destruction (will be marked as destroyed, and send to be destroyed on clients on the next tick)
 */
LIBRG_API void librg_entity_destroy(struct librg_ctx *ctx, librg_entity_id entity_id);
/**
 * Query for entities that are in stream zone
 * for current entity, and are visible to this entity
 * an uninitialized pointer to the pointer of librg_entity_id should be provided:
 *
 * EXAMPLE:
 * librg_entity_id *result;
 * usize amount = librg_entity_query(&ctx, 15, &result);
 */
LIBRG_API usize librg_entity_query(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id **result);
/**
 * Check if provided entity is a valid entity
 */
LIBRG_API b32 librg_entity_valid(struct librg_ctx *ctx, librg_entity_id id);
/**
 * Try to find an entity that is assossiated with a particular peer, or NULL
 */

LIBRG_API struct librg_entity *librg_entity_find(struct librg_ctx *ctx, librg_peer *peer);

/**
 * Concept of entity control describes an entity behavior, data of which is constantly sent to the server from a particular peer (controller).
 * And, updates about that entity are later sent to other peers, except the original controller.
 *
 * New data will arrive, accordingly to configured client tick delay, and the LIBRG_CLIENT_STREAMER_UPDATE event will be triggered.
 * librg_entity_control_set is an async method, it will send a message to the client, notifiyng him of becoming a controller.
 * librg_entity_control_remove is a hybrid sync+async method. Behaves similar wat to _set, described above, however,
 * even thought client will still be sending control messages for some time, server will reject them if method has been called.
 *
 * For occasions, where some sort of an authoritative pause is needed for the server, in the controlled data stream,
 * for example you want to change some property data of the entity, authoritatively from the server, like position.
 * Calling librg_entity_control_remove(ctx, id), making the neccessary changes, and calling librg_entity_control_set(ctx, id, peer)
 * will make sure all current streamed data from the client will be ignored, and new data will be force-delivered to client
 */

LIBRG_API librg_peer *librg_entity_control_get(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_control_set(struct librg_ctx *ctx, librg_entity_id entity_id, librg_peer *peer);
LIBRG_API void librg_entity_control_remove(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_control_ignore_next_update(struct librg_ctx *ctx, librg_entity_id entity_id);

/**
 * Iterate over all the entities with a flag
 */
LIBRG_API void librg_entity_iterate(struct librg_ctx *ctx, u64 flags, librg_entity_cb callback);
/**
 * C based entity iteration macro
 */
#define librg_entity_iteratex(ctx, cflags, cname, code) do { \
    for (u32 _ent = 0, _valid = 0; _ent < ctx->max_entities && _valid < ctx->entity.count; ++_ent) { \
        if ((ctx->entity.list[_ent].flags & (LIBRG_ENTITY_ALIVE | cflags)) == (LIBRG_ENTITY_ALIVE | cflags)) { \
            _valid++; librg_entity_id cname = _ent; code; \
        } \
    } \
} while (0);

/**
 * Return entity type
 */
LIBRG_API u32 librg_entity_type(struct librg_ctx *ctx, librg_entity_id entity_id);

#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY

    typedef enum {
        LIBRG_DEFAULT_VISIBILITY,
        LIBRG_ALWAYS_VISIBLE,
        LIBRG_ALWAYS_INVISIBLE,
    } librg_visibility;

    /**
     * A complex api allowing to change visibility of the entities via specific set of rules.
     *
     * Calling librg_entity_visibility_set, will change global visibility of partiulcar entity for others.
     * Meaning, the entity is still gonna be able to see other entities, while others won't be able to see it back.
     *
     * Calling librg_entity_visibility_set_for, has a similar effect to method mentioned above,
     * however extends also to specify entity inivisibilty for other specific entity.
     */

    LIBRG_API void librg_entity_visibility_set(struct librg_ctx *ctx, librg_entity_id entity_id, librg_visibility state);
    LIBRG_API librg_visibility librg_entity_visibility_get(struct librg_ctx *ctx, librg_entity_id entity_id);

    LIBRG_API void librg_entity_visibility_set_for(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id target, librg_visibility state);
    LIBRG_API librg_visibility librg_entity_visibility_get_for(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id target);
#endif

#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS

    /**
     * A simple thin api allowing to change virtual world for specific entity
     * "Virtual world" is a concept where entities can see other entities near
     * each other only when they share the virtual world.
     *
     * If entity is being removed from the virtual world, it's, LIBRG_ENTITY_REMOVED
     * will be called for all the clients that were sharing same world with target entity.
     */

    LIBRG_API u32  librg_entity_world_get(struct librg_ctx *ctx, librg_entity_id entity_id);
    LIBRG_API void librg_entity_world_set(struct librg_ctx *ctx, librg_entity_id entity_id, u32 world);
#endif

// =======================================================================//
// !
// ! Events
// !
// =======================================================================//

/**
 * Default built-in events
 * define your events likes this:
 *     enum {
 *         MY_NEW_EVENT_1 = LIBRG_LAST_EVENT,
 *         MY_NEW_EVENT_2,
 *         MY_NEW_EVENT_3,
 *     };
 */
enum librg_events {
    LIBRG_CONNECTION_INIT,
    LIBRG_CONNECTION_REQUEST,
    LIBRG_CONNECTION_REFUSE,
    LIBRG_CONNECTION_ACCEPT,
    LIBRG_CONNECTION_DISCONNECT,
    LIBRG_CONNECTION_TIMEOUT,
    LIBRG_CONNECTION_TIMESYNC,

    LIBRG_ENTITY_CREATE,
    LIBRG_ENTITY_UPDATE,
    LIBRG_ENTITY_REMOVE,
    LIBRG_CLIENT_STREAMER_ADD,
    LIBRG_CLIENT_STREAMER_REMOVE,
    LIBRG_CLIENT_STREAMER_UPDATE,

    LIBRG_EVENT_LAST,
};

enum librg_event_flags {
    LIBRG_EVENT_NONE        = 0,        /* default empty user-created event */
    LIBRG_EVENT_REJECTED    = (1 << 0), /* whether or not this event was rejected */
    LIBRG_EVENT_REJECTABLE  = (1 << 1), /* can this event be rejected by user */
    LIBRG_EVENT_REMOTE      = (1 << 2), /* event was based on network message */
    LIBRG_EVENT_LOCAL       = (1 << 3), /* event was created locally */
};

typedef struct librg_event {
    u32 id; /* librg event id, that is being triggered */
    struct librg_ctx *ctx;   /* librg context where event has been called */
    struct librg_data *data;  /* optional: data is used for built-in events */
    struct librg_entity *entity; /* optional: entity is used for built-in events */

    librg_peer *peer;  /* optional: peer is used for built-in events */

    u64 flags;  /* flags for that event */
    void *user_data; /* optional: user information */
} librg_event;

/**
 * Used to attach event handler
 * You can bind as many event handlers onto
 * single event, as you want
 *
 * In the callback you will need to cast event
 * to type of structure that you've triggered this event with
 *
 * @param  id usually you define event ids inside enum
 * @param  callback
 * @return index of added event, can be used to remove particular event handler
 */
LIBRG_API u64 librg_event_add(struct librg_ctx *ctx, u64 id, librg_event_cb callback);
/**
 * Used to trigger execution of all attached
 * event handlers for particlar event
 *
 * You can provide pointer to any data, which will be
 * passed inside the event callback
 *
 * @param id usually you define event ids inside enum
 * @param event pointer onto data or NULL
 */
LIBRG_API void librg_event_trigger(struct librg_ctx *ctx, u64 id, struct librg_event *event);
/**
 * Used to remove particular callback from
 * event chain, so it wont be called ever again
 *
 * @param id usually you define event ids inside enum
 * @param index returned by librg_event_add
 */
LIBRG_API void librg_event_remove(struct librg_ctx *ctx, u64 id, u64 index);
/**
 * Used to reject some event from triggering from
 * inside of executing callback
 */
LIBRG_API void librg_event_reject(struct librg_event *event);
/**
 * Used to check if some event can be rejected
 */
LIBRG_API b32 librg_event_rejectable(struct librg_event *event);
/**
 * Checks if current event was not rejected
 * inside any of the callbacks
 */
LIBRG_API b32 librg_event_succeeded(struct librg_event *event);

// =======================================================================//
// !
// ! Binary data (bitstream)
// !
// =======================================================================//

typedef struct librg_data {
    usize capacity;
    usize read_pos;
    usize write_pos;

    void *rawptr;

    zpl_allocator allocator;
} librg_data;

/**
 * Initialize new bitstream with default mem size
 */
LIBRG_API void librg_data_init(struct librg_data *data);
/**
 * Initialize new bitstream with custom mem size
 */
LIBRG_API void librg_data_init_size(struct librg_data *data, usize size);
/**
 * Initialize new bitstream with custom mem size (bindings stuff)
 */
LIBRG_API struct librg_data *librg_data_init_new();
/**
 * Free initialized bitstream
 */
LIBRG_API void librg_data_free(struct librg_data *data);
/**
 * Reset initialized bitstream
 * NOTE: doesnt remove any data, just resets read and write pos to 0
 */
LIBRG_API void librg_data_reset(struct librg_data *data);
/**
 * Increase size of bitstream
 */
LIBRG_API void librg_data_grow(struct librg_data *data, usize min_size);
/**
 * Methods for getting various parameters of bitstream
 */
LIBRG_API usize librg_data_capacity(struct librg_data *data);
LIBRG_API usize librg_data_get_rpos(struct librg_data *data);
LIBRG_API usize librg_data_get_wpos(struct librg_data *data);
LIBRG_API void  librg_data_set_rpos(struct librg_data *data, usize position);
LIBRG_API void  librg_data_set_wpos(struct librg_data *data, usize position);
/**
 * Read and write methods for custom sized data
 */
LIBRG_API b32 librg_data_rptr(struct librg_data *data, void *ptr, usize size);
LIBRG_API void librg_data_wptr(struct librg_data *data, void *ptr, usize size);
/**
 * Read and write methods for custom sized data
 * at particular position in memory
 */
LIBRG_API b32 librg_data_rptr_at(struct librg_data *data, void *ptr, usize size, isize position);
LIBRG_API void librg_data_wptr_at(struct librg_data *data, void *ptr, usize size, isize position);
/**
 * A helprer macro for onliner methods
 */
#define LIBRG_GEN_DATA_READWRITE(TYPE) \
    LIBRG_API TYPE ZPL_JOIN2(librg_data_r,TYPE)(struct librg_data *data); \
    LIBRG_API void ZPL_JOIN2(librg_data_w,TYPE)(struct librg_data *data, TYPE value); \
    LIBRG_API TYPE ZPL_JOIN3(librg_data_r,TYPE,_at)(struct librg_data *data, isize position); \
    LIBRG_API void ZPL_JOIN3(librg_data_w,TYPE,_at)(struct librg_data *data, TYPE value, isize position);
/**
 * General one-line methods for reading/writing different types
 */
LIBRG_GEN_DATA_READWRITE( i8);
LIBRG_GEN_DATA_READWRITE( u8);
LIBRG_GEN_DATA_READWRITE(i16);
LIBRG_GEN_DATA_READWRITE(u16);
LIBRG_GEN_DATA_READWRITE(i32);
LIBRG_GEN_DATA_READWRITE(u32);
LIBRG_GEN_DATA_READWRITE(i64);
LIBRG_GEN_DATA_READWRITE(u64);
LIBRG_GEN_DATA_READWRITE(f32);
LIBRG_GEN_DATA_READWRITE(f64);
LIBRG_GEN_DATA_READWRITE( b8);
LIBRG_GEN_DATA_READWRITE(b16);
LIBRG_GEN_DATA_READWRITE(b32);
#undef LIBRG_GEN_DATA_READWRITE

/**
 * Read/write methods for entity (aliases for u32)
 */
#define librg_data_went ZPL_JOIN2(librg_data_w, librg_entity_id)
#define librg_data_rent ZPL_JOIN2(librg_data_r, librg_entity_id)

// =======================================================================//
// !
// ! Network
// !
// =======================================================================//

#define librg_message_id u16

#define librg_data_wmid ZPL_JOIN2(librg_data_w, librg_message_id)
#define librg_data_rmid ZPL_JOIN2(librg_data_r, librg_message_id)

/**
 * Message structure
 * created inside network handler
 * and injected to each incoming message
 */
typedef struct librg_message {
    u32 id;
    struct librg_ctx  *ctx;
    struct librg_data *data;

    librg_peer *peer;
    librg_packet *packet;

    void *user_data; /* optional: user information */
} librg_message;

/**
 * Check are we connected
 */
LIBRG_API b32 librg_is_connected(struct librg_ctx *ctx);
/**
 * Is librg instance is running
 * in the server mode
 */
LIBRG_API b32 librg_is_server(struct librg_ctx *ctx);
/**
 * Is librg instance is running
 * in the client mode
 */
LIBRG_API b32 librg_is_client(struct librg_ctx *ctx);
/**
 * Starts network connection
 * Requires you to provide .port (if running as server)
 * or both .port and .host (if running as client)
 *
 * For server mode - starts server
 * For client mode - starts client, and connects to provided host & port
 */
LIBRG_API u32 librg_network_start(struct librg_ctx *ctx, librg_address address);
/**
 * Disconnects (if connected), stops network
 * and releases resources
 */
LIBRG_API void librg_network_stop(struct librg_ctx *ctx);
/**
 * Forces disconnection for provided peer
 * @param ctx
 * @param peer
 */
LIBRG_API void librg_network_kick(struct librg_ctx *ctx, librg_peer *peer);
/**
 * Can be used to add handler
 * to a particular message id
 */
LIBRG_API void librg_network_add(struct librg_ctx *ctx, librg_message_id id, librg_message_cb callback);
/**
 * Can be used to remove a handler
 * from particular message id
 */
LIBRG_API void librg_network_remove(struct librg_ctx *ctx, librg_message_id id);

/**
 * General reliable message/data sending API
 * (original message api version since v2.0.0, might be subject for changes in the future)
 *
 * Message id - is an identifier which librg will use to route the message from
 * (to particlar handlers which are added via librg_network_add)
 *
 * method _all - sends the data to all connected peers
 * method _to - sends the data to particular peer
 * method _except - sends the data to all but particular peer
 * method _instream - sends the data to all connected peers in the particular range (based off entity's stream_range)
 * method _instream_except - similar to method above, however excludes particular peer
 *
 * The data which is provided inside the method will be copied inside packet struct, so it can be safely freed after you've called a method.
 */

LIBRG_API void librg_message_send_all             (struct librg_ctx *ctx, librg_message_id id, void *data, usize size);
LIBRG_API void librg_message_send_to              (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size);
LIBRG_API void librg_message_send_except          (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size);
LIBRG_API void librg_message_send_instream        (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, void *data, usize size);
LIBRG_API void librg_message_send_instream_except (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *target, void *data, usize size);

/**
 * The basic message/data sending API
 * Supports sending both reliable and unreliable messages, and ability to set which channel to use for message sending.
 *
 * All previous message sending methods are using these extended variants internally.
 *
 * method sendex:
 *     if target is NULL - will send the data to all connected peers, else - to a particular peer
 *     if except is NULL - argument does nothing, else - prevents data from being sent to a particlar peer
 *     channel - provides which network channel is supposed to be used for message sending
 *         (make that channel id is always < librg_option_get(LIBRG_NETWORK_CHANNELS), or use librg_option_set to change the default value)
 *     if reliable is 1 (true) - message will be sent using reliable methods, else using unreliable
 */

LIBRG_API void librg_message_sendex               (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, librg_peer *except, u16 channel, b8 reliable, void *data, usize size);
LIBRG_API void librg_message_sendex_instream      (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, u16 channel, b8 reliable, void *data, usize size);

// =======================================================================//
// !
// ! Helper methods
// !
// =======================================================================//

/**
 * Get current execution time
 * On server side returns time aprox since process is up and running
 * On client side returns synced time client time to server time.
 * Ideally calling this method on both connected client and server at the same time
 * will return pretty much similar time, or at least 2 that are quite close to one another
 *
 * Retuned time is a CLOCK_MONOTONIC, the format is second before comma,
 * and parts lesser than second after comma
 *
 * EXAMPLE:
 *     1.2424244 - 1 second 242 milliseconds
 *     255.4224244 - 255 seconds
 */
LIBRG_API f64 librg_time_now(struct librg_ctx *ctx);
/**
 * Calcualte standard devaiation for a set of values
 * Example:
 * f64 values[3] = { 1, 2, 3 };
 * f64 dev = librg_standard_deviation(&values, 3);
 */
LIBRG_API f64 librg_standard_deviation(f64 *values, usize size);

// =======================================================================//
// !
// ! Options
// !
// =======================================================================//

enum librg_options {
    LIBRG_PLATFORM_ID,
    LIBRG_PLATFORM_PROTOCOL,
    LIBRG_PLATFORM_BUILD,

    LIBRG_DEFAULT_CLIENT_TYPE,
    LIBRG_DEFAULT_STREAM_RANGE,
    LIBRG_DEFAULT_DATA_SIZE,

    LIBRG_NETWORK_CAPACITY,
    LIBRG_NETWORK_CHANNELS,
    LIBRG_NETWORK_PRIMARY_CHANNEL,
    LIBRG_NETWORK_SECONDARY_CHANNEL,
    LIBRG_NETWORK_MESSAGE_CHANNEL,
    LIBRG_NETWORK_BUFFER_SIZE,

    LIBRG_MAX_ENTITIES_PER_BRANCH,
    LIBRG_USE_RADIUS_CULLING,

    LIBRG_OPTIONS_SIZE,
};

/**
 * Get global cross-instance option for librg
 */
LIBRG_API u32 librg_option_get(u32 option);
/**
 * Set global cross-instance option for librg
 */
LIBRG_API void librg_option_set(u32 option, u32 value);

// =======================================================================//
// !
// ! Main context definition
// !
// =======================================================================//

typedef struct librg_snapshot {
    f64 time;
    void *data;
    usize size;
    librg_peer *peer;
} librg_snapshot;

ZPL_RING_DECLARE(librg_snapshot);

typedef struct librg_space_node {
    struct librg_entity *blob;
    b32 unused;
} librg_space_node;

typedef struct librg_space {
    zpl_allocator allocator;
    u32 max_nodes;
    isize dimensions;
    zpl_aabb3 boundary;
    zpl_vec3 min_bounds;
    b32 use_min_bounds;
    usize *free_nodes; ///< zpl_array
    struct librg_space *spaces; ///< zpl_array
    struct librg_space_node *nodes; ///< zpl_array
} librg_space;

#define LIBRG_TIMESYNC_SIZE 5
#define LIBRG_DATA_STREAMS_AMOUNT 4

/**
 * Context + config struct
 */
typedef struct librg_ctx {
    // core
    u16 mode;
    f64 tick_delay;

    // configuration
    u16 max_connections;
    u32 max_entities;

    zpl_vec3 world_size;
    zpl_vec3 min_branch_size;

    f64 last_update;
    void *user_data;

    struct {
        librg_peer *peer;
        librg_host *host;
        librg_table connected_peers;
        librg_address last_address;
        b32 created;
        b32 connected;
    } network;

    struct {
        u32 count;
        u32 cursor;

        #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
        librg_table visibility;
        #endif

        struct librg_entity *list;
        librg_entity_id *remove_queue; ///< zpl_array
        librg_message **add_control_queue; ///< zpl_array
    } entity;

    union {
        struct {
            librg_data stream_input;
            librg_data stream_output;
            librg_data stream_upd_reliable;
            librg_data stream_upd_unreliable;
        };

        librg_data streams[LIBRG_DATA_STREAMS_AMOUNT];
    };

    struct {
        f64 start_time;
        f64 offset_time;
        f64 median;
        f64 history[LIBRG_TIMESYNC_SIZE];
        f32 server_delay;
        zpl_timer *timer;
    } timesync;

    usize buffer_size;
    zpl_timer *buffer_timer;
    zpl_ring_librg_snapshot buffer;
    librg_message_cb **messages; ///< zpl_buffer

    zpl_allocator     allocator;
    zpl_timer_pool    timers;
    librg_event_pool  events;
    librg_space       world;

} librg_ctx;

// =======================================================================//
// !
// ! Extensions
// !
// =======================================================================//

#if 1
    #define librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, SEND_CODE) \
        librg_data NAME; \
        librg_data_init(&NAME); \
        CALLBACK_CODE; SEND_CODE; \
        librg_data_free(&NAME);

    #define librg_send_all(CTX, ID, NAME, CALLBACK_CODE) do { \
        librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
            librg_message_send_all(CTX, ID, NAME.rawptr, librg_data_get_wpos(&NAME)); \
        }); \
    } while(0);

    #define librg_send_to(CTX, ID, PEER, NAME, CALLBACK_CODE) do { \
        librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
            librg_message_send_to(CTX, ID, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
        }); \
    } while(0);

    #define librg_send_except(CTX, ID, PEER, NAME, CALLBACK_CODE) do { \
        librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
            librg_message_send_except(CTX, ID, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
        }); \
    } while(0);

    #define librg_send_instream(CTX, ID, ENTITY, NAME, CALLBACK_CODE) do { \
        librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
            librg_message_send_instream(CTX, ID, ENTITY, NAME.rawptr, librg_data_get_wpos(&NAME)); \
        }); \
    } while(0);

    #define librg_send_instream_except(CTX, ID, ENTITY, PEER, NAME, CALLBACK_CODE) do { \
        librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
            librg_message_send_instream(CTX, ID, ENTITY, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
        }); \
    } while(0);

    #define librg_send librg_send_all
#endif

#ifdef __cplusplus
}
#endif

#if defined(LIBRG_IMPLEMENTATION) && !defined(LIBRG_IMPLEMENTATION_DONE)
#define LIBRG_IMPLEMENTATION_DONE

#ifdef __cplusplus
extern "C" {
#endif

// =======================================================================//
// !
// ! Implementation part
// !
// =======================================================================//

    ZPL_TABLE_DEFINE(librg_event_pool, librg_event_pool_, librg_event_block);
    ZPL_TABLE_DEFINE(librg_table, librg_table_, u32);
    ZPL_RING_DEFINE(librg_snapshot);

    /* internal declarations */
    LIBRG_INTERNAL void librg__callback_connection_init(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_connection_request(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_connection_refuse(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_connection_accept(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_connection_disconnect(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_connection_timesync(librg_message *msg);

    LIBRG_INTERNAL void librg__callback_entity_create(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_entity_update(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_add(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_remove(librg_message *msg);
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_update(librg_message *msg);

    LIBRG_INTERNAL void librg__timesync_start(librg_ctx *ctx);
    LIBRG_INTERNAL void librg__timesync_tick(void *usrptr);
    LIBRG_INTERNAL void librg__timesync_stop(librg_ctx *ctx);

    LIBRG_INTERNAL void librg__buffer_init(librg_ctx *ctx, usize size);
    LIBRG_INTERNAL void librg__buffer_free(librg_ctx *ctx);
    LIBRG_INTERNAL void librg__buffer_push(librg_ctx *ctx, f64 time, librg_peer *peer, void *data, usize size);
    LIBRG_INTERNAL void librg__buffer_tick(void *usrptr);

    // short internal helper macro
    #define LIBRG_MESSAGE_TO_EVENT(NAME, MSG) \
        librg_event NAME = {0}; \
        NAME.peer   = MSG->peer; \
        NAME.data   = MSG->data; \
        NAME.flags  = LIBRG_EVENT_REMOTE;


// =======================================================================//
// !
// ! Work in progress area
// ! TODO: move stuff below
// !
// =======================================================================//

    /* world stuff  */

    typedef struct {
        usize id;
        usize offset;
        usize count;
        struct librg_ctx *ctx;
    } librg_update_worker_si_t;


    LIBRG_INTERNAL void librg__world_update(void *);

    LIBRG_INTERNAL void librg__world_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_space *c, zpl_aabb3 bounds, librg_entity_id **out_entities);
    LIBRG_INTERNAL b32 librg__world_entity_destroy(librg_ctx *ctx, librg_entity_id id);

    /* space stuff */

    LIBRG_INTERNAL void librg__space_init(librg_space *c, zpl_allocator a, isize dims, zpl_aabb3 bounds, zpl_vec3 min_bounds, u32 max_nodes);
    LIBRG_INTERNAL void librg__space_clear(librg_space *c);
    b32 librg__space_remove_node(librg_space *c, librg_entity *tag);

    LIBRG_INTERNAL b32 librg__space_intersects(isize dims, zpl_aabb3 a, zpl_aabb3 b) {
        for (i32 i = 0; i < dims; ++i) {
            if (zpl_abs(a.centre.e[i] - b.centre.e[i]) > (a.half_size.e[i] + b.half_size.e[i])) return false;
        }

        return true;
    }

    LIBRG_INTERNAL b32 librg__space_contains(isize dims, zpl_aabb3 a, f32 *point) {
        for (i32 i = 0; i < dims; ++i) {
            if (!( a.centre.e[i] - a.half_size.e[i] <= point[i]
                   && a.centre.e[i] + a.half_size.e[i] >= point[i])) {
                return false;
            }
        }

        return true;
    }

// =======================================================================//
// !
// ! Genaral methods
// !
// =======================================================================//

#if 1
    void *librg_allocate_ptr(enum librg_pointer_type type) {
        void *ptr = NULL;

        switch (type) {
            case LIBRG_POINTER_CTX:
                ptr = (void *)zpl_malloc(sizeof(librg_ctx));
                zpl_zero_size(ptr, sizeof(librg_ctx));
                break;

            case LIBRG_POINTER_DATA:
                ptr = (void *)zpl_malloc(sizeof(librg_data));
                zpl_zero_size(ptr, sizeof(librg_data));
                break;

            case LIBRG_POINTER_EVENT:
                ptr = (void *)zpl_malloc(sizeof(librg_event));
                zpl_zero_size(ptr, sizeof(librg_event));
                break;
        }

        return ptr;
    }

    void librg_release_ptr(void *ptr) {
        librg_assert(ptr);
        zpl_mfree(ptr);
    }

    u32 librg_init(librg_ctx *ctx) {
        librg_dbg("[dbg] librg_init\n");

        #define librg_set_default(expr, value) if (!expr) expr = value

        // apply default settings (if no user provided)
        librg_set_default(ctx->tick_delay, 32.0);
        librg_set_default(ctx->max_connections, 16);
        librg_set_default(ctx->max_entities, 8192);
        librg_set_default(ctx->world_size.x, 5000.0f);
        librg_set_default(ctx->world_size.y, 5000.0f);
        librg_set_default(ctx->min_branch_size.x, 50.0f);
        librg_set_default(ctx->min_branch_size.y, 50.0f);
        librg_set_default(ctx->mode, LIBRG_MODE_SERVER);

        if (!ctx->allocator.proc && !ctx->allocator.data) {
            ctx->allocator = zpl_heap_allocator();
        }

        #undef librg_set_default

        // init entities system
        ctx->entity.list = (librg_entity*)zpl_alloc(ctx->allocator, sizeof(librg_entity) * ctx->max_entities);
        for (librg_entity_id i = 0; i < ctx->max_entities; i++) {
            librg_entity blob = { i, 0 };
            ctx->entity.list[i] = blob;
        }

        zpl_array_init(ctx->entity.remove_queue, ctx->allocator);
        zpl_array_init(ctx->entity.add_control_queue, ctx->allocator);

        for (i8 i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) {
            librg_data_init(&ctx->streams[i]);
        }

        // streamer
        zpl_aabb3 world = {0};
        world.centre    = zpl_vec3f(0, 0, 0);
        world.half_size = zpl_vec3f(ctx->world_size.x, ctx->world_size.y, ctx->world_size.z);
        u32 dimension   = ctx->world_size.z == 0.0f ? LIBRG_SPACE_2D : LIBRG_SPACE_3D;

        if (ctx->min_branch_size.x == -1.0f &&
            ctx->min_branch_size.y == -1.0f &&
            ctx->min_branch_size.z == -1.0f) {
            zpl_vec3 no_min_bounds = { 0 };
            ctx->min_branch_size = no_min_bounds;
        }

        librg__space_init(&ctx->world, ctx->allocator, dimension, world, ctx->min_branch_size, librg_option_get(LIBRG_MAX_ENTITIES_PER_BRANCH));

        #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
        librg_table_init(&ctx->entity.visibility, ctx->allocator);
        #endif

        // events
        librg_event_pool_init(&ctx->events, ctx->allocator);
        zpl_buffer_init(ctx->messages, ctx->allocator, librg_option_get(LIBRG_NETWORK_CAPACITY));

        // init timers
        zpl_array_init(ctx->timers, ctx->allocator);
        zpl_timer *tick_timer = zpl_timer_add(ctx->timers);
        tick_timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */
        zpl_timer_set(tick_timer, ctx->tick_delay * 0.001, -1, librg__world_update);
        zpl_timer_start(tick_timer, 0.250);

        ctx->timesync.timer = zpl_timer_add(ctx->timers);
        ctx->timesync.timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */

        ctx->buffer_timer = zpl_timer_add(ctx->timers);
        ctx->buffer_timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */

        // network
        u8 enet_init = enet_initialize();
        librg_assert_msg(enet_init == 0, "cannot initialize enet");
        if (enet_init != 0) {
            return -1;
        }

        // add event handlers for our network stufz
        ctx->messages[LIBRG_CONNECTION_INIT]        = librg__callback_connection_init;
        ctx->messages[LIBRG_CONNECTION_REQUEST]     = librg__callback_connection_request;
        ctx->messages[LIBRG_CONNECTION_REFUSE]      = librg__callback_connection_refuse;
        ctx->messages[LIBRG_CONNECTION_ACCEPT]      = librg__callback_connection_accept;
        ctx->messages[LIBRG_CONNECTION_DISCONNECT]  = librg__callback_connection_disconnect;
        ctx->messages[LIBRG_CONNECTION_TIMESYNC]    = librg__callback_connection_timesync;
        ctx->messages[LIBRG_ENTITY_CREATE]          = librg__callback_entity_create;
        ctx->messages[LIBRG_ENTITY_UPDATE]          = librg__callback_entity_update;
        ctx->messages[LIBRG_CLIENT_STREAMER_ADD]    = librg__callback_entity_client_streamer_add;
        ctx->messages[LIBRG_CLIENT_STREAMER_REMOVE] = librg__callback_entity_client_streamer_remove;
        ctx->messages[LIBRG_CLIENT_STREAMER_UPDATE] = librg__callback_entity_client_streamer_update;

        return 0;
    }

    void librg_free(librg_ctx *ctx) {
        librg_dbg("[dbg] librg_free\n");

        // free all timers and events first
        zpl_array_free(ctx->timers);
        zpl_buffer_free(ctx->messages, ctx->allocator);

        for (isize i = 0; i < zpl_array_count(ctx->events.entries); ++i) {
            librg_event_block *block = &ctx->events.entries[i].value;

            if (block) {
                zpl_array_free(*block);
            }
        }

        librg_event_pool_destroy(&ctx->events);

        zpl_free(ctx->allocator, ctx->entity.list);
        zpl_array_free(ctx->entity.remove_queue);
        zpl_array_free(ctx->entity.add_control_queue);

        // streamer
        librg__space_clear(&ctx->world);

        #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
        librg_table_destroy(&ctx->entity.visibility);
        #endif

        for (isize i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) {
            librg_data_free(&ctx->streams[i]);
        }

        enet_deinitialize();
    }

    void librg_tick(librg_ctx *ctx) {
        zpl_timer_update(ctx->timers);

        if (!ctx->network.host) {
            return; /* occasion where we are not started network yet */
        }

        ENetEvent event;
        while (enet_host_service(ctx->network.host, &event, 0) > 0) {
            librg_message msg = {0}; {
                msg.ctx     = ctx;
                msg.data    = NULL;
                msg.peer    = event.peer;
                msg.packet  = event.packet;
            }

            switch (event.type) {
                case ENET_EVENT_TYPE_RECEIVE: {
                    librg_data data = {0};

                    data.rawptr = event.packet->data;
                    data.capacity = event.packet->dataLength;

                    if (!data.rawptr || data.capacity < sizeof(librg_message_id)) {
                        librg_assert(false);
                        librg_dbg("[dbg] corrupted packet in librg_tick, on receive\n");
                        continue;
                    }

                    // get curernt packet id
                    librg_message_id id = librg_data_rmid(&data);
                    f64 server_time = 0;

                    msg.id = id;

                    if (id == LIBRG_ENTITY_UPDATE) {
                        server_time = librg_data_rf64(&data);
                        // librg_log("server_time: %f, client_predicted: %f, diff: %f\n", server_time, librg_time_now(ctx), librg_time_now(ctx) - server_time);

                        if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
                            librg__buffer_push(ctx, server_time, event.peer, data.rawptr, data.capacity);
                            enet_packet_destroy(event.packet);
                            return;
                        }
                    }

                    if (ctx->messages[id]) {
                        msg.data = &data;
                        ctx->messages[id](&msg);
                    }
                    else {
                        /* print unknown message id  */
                        librg_dbg("[dbg] unknown message: %u\n", id);
                    }

                    enet_packet_destroy(event.packet);
                } break;
                case ENET_EVENT_TYPE_CONNECT:    ctx->messages[LIBRG_CONNECTION_INIT](&msg); break;
                case ENET_EVENT_TYPE_DISCONNECT: ctx->messages[LIBRG_CONNECTION_DISCONNECT](&msg); break;
                case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: {
                    msg.user_data = (void *)1; /* mark the timeout */
                    ctx->messages[LIBRG_CONNECTION_DISCONNECT](&msg);
                } break;
                case ENET_EVENT_TYPE_NONE: break;
            }
        }
    }
#endif

// =======================================================================//
// !
// ! Entities
// !
// =======================================================================//

#if 1
    librg_entity *librg_entity_create(librg_ctx *ctx, u32 type) {
        librg_assert(ctx);
        librg_assert(librg_is_server(ctx));
        librg_assert_msg(ctx->entity.count < ctx->max_entities, "reached max_entities limit");
        if (ctx->entity.count >= ctx->max_entities) {
            return NULL;
        }

        ++ctx->entity.count;

        if (ctx->entity.cursor >= (ctx->max_entities - 1) || ctx->max_entities == 0) {
            ctx->entity.cursor = 0;
        }

        for (; ctx->entity.cursor < ctx->max_entities; ++ctx->entity.cursor) {
            librg_entity *entity = &ctx->entity.list[ctx->entity.cursor]; librg_assert(entity);

            if (entity->flags & LIBRG_ENTITY_ALIVE) continue;

            entity->type               = type;
            entity->flags              = LIBRG_ENTITY_ALIVE;
            entity->position           = zpl_vec3f_zero();
            entity->stream_range       = librg_option_get(LIBRG_DEFAULT_STREAM_RANGE) * 1.0f;
            entity->stream_branch      = NULL;
            entity->control_generation = 0;

            return entity;
        }

        librg_assert_msg(false, "no entities to spawn");
        return NULL;
    }

    librg_entity *librg_entity_fetch(librg_ctx *ctx, librg_entity_id id) {
        if (librg_entity_valid(ctx, id))
            return &ctx->entity.list[id];

        return NULL;
    }

    void librg_entity_destroy(librg_ctx *ctx, librg_entity_id id) {
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, id);
        blob->flags |= LIBRG_ENTITY_MARKED_REMOVAL;
        zpl_array_append(ctx->entity.remove_queue, id);
    }

    usize librg_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_entity_id **out_entities) {
        librg_assert(ctx && out_entities);
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, entity);

        if (!(blob->flags & LIBRG_ENTITY_QUERIED)) {
            blob->flags |= LIBRG_ENTITY_QUERIED;
            zpl_array_init(blob->last_query, ctx->allocator);
        }

        // reset array to 0
        zpl_array(librg_entity_id) temp_array;
        zpl_array_init(temp_array, ctx->allocator);
        zpl_array_count(blob->last_query) = 0;

        /* add all currently streamed entities automatically */
        librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROLLED, librg_lambda(controlled), {
            if (blob->client_peer && librg_entity_control_get(ctx, controlled) == blob->client_peer) {
                if (librg_entity_valid(ctx, controlled) && !(librg_entity_fetch(ctx, controlled)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
                    zpl_array_append(temp_array, controlled);
                }
            }
        });

        #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY

        /* add related visible entities */
        if (blob->flags & LIBRG_ENTITY_VISIBILITY) {
            for (int i = 0; i < zpl_array_count(blob->visibility.entries); ++i) {
                #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
                if (librg_entity_world_get(ctx, blob->id) != librg_entity_world_get(ctx, blob->visibility.entries[i].key)) continue;
                #endif

                if (blob->visibility.entries[i].value == LIBRG_ALWAYS_VISIBLE) {
                    if (librg_entity_valid(ctx, blob->visibility.entries[i].key) &&
                        !(librg_entity_fetch(ctx, blob->visibility.entries[i].key)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
                        zpl_array_append(temp_array, blob->visibility.entries[i].key);
                    }
                }
            }
        }

        /* add global visible entities */
        for (int i = 0; i < zpl_array_count(ctx->entity.visibility.entries); ++i) {
            #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
            if (librg_entity_world_get(ctx, blob->id) != librg_entity_world_get(ctx, ctx->entity.visibility.entries[i].key)) continue;
            #endif

            if (ctx->entity.visibility.entries[i].value == LIBRG_ALWAYS_VISIBLE) {
                if (librg_entity_visibility_get_for(ctx, blob->id, ctx->entity.visibility.entries[i].key) != LIBRG_ALWAYS_INVISIBLE) {
                    if (librg_entity_valid(ctx, ctx->entity.visibility.entries[i].key) &&
                        !(librg_entity_fetch(ctx, ctx->entity.visibility.entries[i].key)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
                        zpl_array_append(temp_array, ctx->entity.visibility.entries[i].key);
                    }
                }
            }
        }
        #endif

        zpl_aabb3 search_bounds; {
            search_bounds.centre    = blob->position;
            search_bounds.half_size = zpl_vec3f(blob->stream_range, blob->stream_range, blob->stream_range);
        };

        librg__world_entity_query(ctx, entity, &ctx->world, search_bounds, &temp_array);

        /* remove duplicate entities */
        for (int i = 0; i < zpl_array_count(temp_array); ++i) {
            bool isduplicate = false;

            for (int j = 0; j < zpl_array_count(blob->last_query); ++j) {
                if (temp_array[i] == blob->last_query[j]) {
                    isduplicate = true;
                    break;
                }
            }

            if (!isduplicate) {
                zpl_array_append(blob->last_query, temp_array[i]);
            }
        }

        zpl_array_free(temp_array);
        *out_entities = blob->last_query;

        return zpl_array_count(blob->last_query);
    }

    b32 librg_entity_valid(librg_ctx *ctx, librg_entity_id id) {
        librg_assert(ctx);
        return (id < ctx->max_entities && (ctx->entity.list[id].flags & LIBRG_ENTITY_ALIVE));
    }

    librg_entity *librg_entity_find(librg_ctx *ctx, librg_peer *peer) {
        librg_assert(ctx && librg_is_server(ctx) && peer);
        librg_entity_id *id = librg_table_get(&ctx->network.connected_peers, (u64)peer);

        if (id) {
            return librg_entity_fetch(ctx, *id);
        }

        return NULL;
    }

    void librg_entity_iterate(librg_ctx *ctx, u64 flags, librg_entity_cb callback) {
        librg_entity_iteratex(ctx, flags, librg_lambda(entity), { callback(ctx, librg_entity_fetch(ctx, entity)); });
    }

    u32 librg_entity_type(librg_ctx *ctx, librg_entity_id id) {
        librg_assert(librg_entity_valid(ctx, id));
        return ctx->entity.list[id].type;
    }

    #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
        void librg_entity_visibility_set(librg_ctx *ctx, librg_entity_id entity, librg_visibility state) {
            librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
            librg_table_set(&ctx->entity.visibility, entity, (u32)state);
        }

        void librg_entity_visibility_set_for(librg_ctx *ctx, librg_entity_id entity, librg_entity_id target, librg_visibility state) {
            librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
            librg_entity *blob = librg_entity_fetch(ctx, entity);

            /* prevent any operations on the same entity*/
            if (entity == target) return;

            if (!(blob->flags & LIBRG_ENTITY_VISIBILITY)) {
                blob->flags |= LIBRG_ENTITY_VISIBILITY;
                librg_table_init(&blob->visibility, ctx->allocator);
            }

            librg_table_set(&blob->visibility, target, (u32)state);
        }

        librg_visibility librg_entity_visibility_get(librg_ctx *ctx, librg_entity_id entity) {
            librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
            u32 *visibility = librg_table_get(&ctx->entity.visibility, entity);
            return (librg_visibility)(visibility ? *visibility : LIBRG_DEFAULT_VISIBILITY);
        }

        librg_visibility librg_entity_visibility_get_for(librg_ctx *ctx, librg_entity_id entity, librg_entity_id target) {
            librg_assert(librg_is_server(ctx));
            librg_entity *blob = librg_entity_fetch(ctx, entity);

            /* prevent any operations on the same entity*/
            if (entity == target) return LIBRG_DEFAULT_VISIBILITY;

            if (!(blob->flags & LIBRG_ENTITY_VISIBILITY)) {
                return LIBRG_DEFAULT_VISIBILITY;
            }

            u32 *visibility = librg_table_get(&blob->visibility, target);
            return (librg_visibility)(visibility ? *visibility : LIBRG_DEFAULT_VISIBILITY);
        }
    #endif

    #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
        u32 librg_entity_world_get(struct librg_ctx *ctx, librg_entity_id entity_id) {
            librg_assert(ctx);
            librg_entity *entity = librg_entity_fetch(ctx, entity_id);

            if (entity) {
                return entity->virual_world;
            }

            return 0;
        }

        void librg_entity_world_set(struct librg_ctx *ctx, librg_entity_id entity_id, u32 world) {
            librg_assert(ctx);
            librg_entity *entity = librg_entity_fetch(ctx, entity_id);

            if (entity) {
                entity->virual_world = world;
            }
        }
    #endif

    void librg_entity_control_set(librg_ctx *ctx, librg_entity_id entity, librg_peer *peer) {
        librg_assert(ctx && peer && librg_entity_valid(ctx, entity));
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, entity);

        // replace current entity owner
        if (blob->flags & LIBRG_ENTITY_CONTROLLED) {
            if (blob->control_peer == peer) {
                return;
            }

            librg_send_to(ctx, LIBRG_CLIENT_STREAMER_REMOVE, blob->control_peer, librg_lambda(data), {
                librg_data_went(&data, entity);
            });
        }

        // attach new entity owner
        blob->flags |= (LIBRG_ENTITY_CONTROLLED | LIBRG_ENTITY_CONTROL_REQUESTED);
        blob->control_peer = peer;

        // main compare/validation thingy
        blob->control_generation++;

        librg_message *msg = (librg_message *)zpl_alloc(ctx->allocator, sizeof(librg_message)); {
            librg_message_id id = LIBRG_CLIENT_STREAMER_ADD;

            librg_data data = {0};
            librg_data_init(&data);
            librg_data_went(&data, entity);
            librg_data_wu8(&data, blob->control_generation);

            msg->id     = id;
            msg->peer   = peer;
            msg->packet = enet_packet_create_offset(
                data.rawptr, librg_data_get_wpos(&data), sizeof(librg_message_id), ENET_PACKET_FLAG_RELIABLE
            );

            zpl_memcopy(msg->packet->data, &id, sizeof(librg_message_id));
            librg_data_free(&data);
        }

        zpl_array_append(ctx->entity.add_control_queue, msg);
    }

    librg_peer *librg_entity_control_get(librg_ctx *ctx, librg_entity_id entity) {
        librg_assert(ctx);
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, entity);
        return (blob && blob->flags & LIBRG_ENTITY_CONTROLLED) ? blob->control_peer : NULL;
    }

    void librg_entity_control_remove(librg_ctx *ctx, librg_entity_id entity) {
        librg_assert(ctx && librg_entity_valid(ctx, entity));
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, entity);

        // some minor validations
        if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) { return; }

        librg_send_to(ctx, LIBRG_CLIENT_STREAMER_REMOVE, blob->control_peer, librg_lambda(data), {
            librg_data_went(&data, entity);
        });

        blob->flags &= ~LIBRG_ENTITY_CONTROLLED;
        blob->control_peer = NULL;
    }

    void librg_entity_control_ignore_next_update(librg_ctx *ctx, librg_entity_id entity) {
        librg_assert(ctx && librg_entity_valid(ctx, entity));
        librg_assert(librg_is_server(ctx));
        librg_entity *blob = librg_entity_fetch(ctx, entity);

        // some minor validations
        if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) { return; }
        if (blob->flags & LIBRG_ENTITY_CONTROL_REQUESTED) { return; }

        // and super complex algo to reset the peer, and update the control_generation
        librg_peer *client_peer = librg_entity_control_get(ctx, entity);
        librg_entity_control_remove(ctx, entity);
        librg_entity_control_set(ctx, entity, client_peer);
    }

#endif

// =======================================================================//
// !
// ! Events
// !
// =======================================================================//

#if 1
    u64 librg_event_add(librg_ctx *ctx, u64 id, librg_event_cb callback) {
        librg_assert(ctx);
        librg_event_block *block = librg_event_pool_get(&ctx->events, id);

        if (!block) {
            librg_event_block arr;
            zpl_array_init(arr, ctx->allocator);
            librg_event_pool_set(&ctx->events, id, arr);
            block = librg_event_pool_get(&ctx->events, id);
        }

        u64 offset = zpl_array_count(block);
        zpl_array_append(*block, callback);
        return offset;
    }

    void librg_event_trigger(librg_ctx *ctx, u64 id, librg_event *event) {
        librg_assert(event);

        event->id = id;
        event->ctx = ctx;

        librg_event_block *block = librg_event_pool_get(&ctx->events, id);
        if (!block) return;

        for (isize i = 0; i < zpl_array_count(*block) && !(event->flags & LIBRG_EVENT_REJECTED); ++i) {
            (*block)[i](event);
        }
    }

    void librg_event_remove(librg_ctx *ctx, u64 id, u64 index) {
        librg_assert(ctx);
        librg_event_block *block = librg_event_pool_get(&ctx->events, id);

        if (block) {
            zpl_array_remove_at(*block, (isize)index);
        }
    }

    void librg_event_reject(librg_event *event) {
        librg_assert(event);
        event->flags = (event->flags | LIBRG_EVENT_REJECTED);
    }

    b32 librg_event_rejectable(librg_event *event) {
        librg_assert(event);
        return (event->flags & LIBRG_EVENT_REJECTABLE);
    }

    b32 librg_event_succeeded(librg_event *event) {
        librg_assert(event);
        return !(event->flags & LIBRG_EVENT_REJECTED);
    }
#endif

// =======================================================================//
// !
// ! Binary data (bitstream)
// !
// =======================================================================//

#if 1
    void librg_data_init_size(librg_data *data, usize size) {
        librg_assert(data);

        data->capacity  = size;
        data->read_pos  = 0;
        data->write_pos = 0;
        data->allocator = zpl_heap_allocator();
        data->rawptr    = zpl_alloc(data->allocator, size);
    }

    void librg_data_init(librg_data *data) {
        librg_assert(data);
        librg_data_init_size(data, librg_option_get(LIBRG_DEFAULT_DATA_SIZE));
    }

    librg_data *librg_data_init_new() {
        librg_data *data = (librg_data *)zpl_malloc(sizeof(librg_data));
        librg_data_init(data);
        return data;
    }

    void librg_data_free(librg_data *data) {
        librg_assert(data && data->rawptr);
        zpl_free(data->allocator, data->rawptr);
    }

    void librg_data_reset(librg_data *data) {
        librg_data_set_wpos(data, 0);
        librg_data_set_rpos(data, 0);
    }

    void librg_data_grow(librg_data *data, usize min_size) {
        librg_assert(data && data->rawptr);

        usize new_capacity = LIBRG_DATA_GROW_FORMULA(data->capacity);
        if (new_capacity < (min_size))
            new_capacity = (min_size);

        void *newptr = zpl_alloc(data->allocator, new_capacity);

        zpl_memcopy(newptr, data->rawptr, data->write_pos);
        zpl_free(data->allocator, data->rawptr);

        data->capacity = new_capacity;
        data->rawptr = newptr;
    }

    usize librg_data_capacity(librg_data *data) {
        librg_assert(data); return data->capacity;
    }

    usize librg_data_get_rpos(librg_data *data) {
        librg_assert(data); return data->read_pos;
    }

    usize librg_data_get_wpos(librg_data *data) {
        librg_assert(data); return data->write_pos;
    }

    void librg_data_set_rpos(librg_data *data, usize position) {
        librg_assert(data); data->read_pos = position;
    }

    void librg_data_set_wpos(librg_data *data, usize position) {
        librg_assert(data); data->write_pos = position;
    }

    b32 librg_data_rptr_at(librg_data *data, void *ptr, usize size, isize position) {
        librg_assert(data && data->rawptr && ptr);

        librg_assert_msg(position + size <= librg_data_capacity(data),
            "librg_data: trying to read from outside of the bounds");
        if (position + size > librg_data_capacity(data)) {
            return false;
        }

        zpl_memcopy(ptr, (char *)data->rawptr + position, size);

        return true;
    }

    void librg_data_wptr_at(librg_data *data, void *ptr, usize size, isize position) {
        librg_assert(data && data->rawptr && ptr);

        if (position + size > librg_data_capacity(data)) {
            librg_data_grow(data, librg_data_capacity(data) + size + position);
        }

        zpl_memcopy((char *)data->rawptr + position, ptr, size);
    }

    b32 librg_data_rptr(librg_data *data, void *ptr, usize size) {
        librg_assert(data && data->rawptr && ptr);
        if (!librg_data_rptr_at(data, ptr, size, librg_data_get_rpos(data))) {
            return false;
        }

        data->read_pos += size;

        return true;
    }

    void librg_data_wptr(librg_data *data, void *ptr, usize size) {
        librg_assert(data && data->rawptr && ptr);
        librg_data_wptr_at(data, ptr, size, librg_data_get_wpos(data));
        data->write_pos += size;
    }

    /**
     * Value writers and readers
     */

    #define LIBRG_GEN_DATA_READWRITE(TYPE) \
        TYPE ZPL_JOIN2(librg_data_r,TYPE)(librg_data *data) { \
            TYPE value; librg_data_rptr(data, &value, sizeof(value)); return value; \
        } \
        void ZPL_JOIN2(librg_data_w,TYPE)(librg_data *data, TYPE value) { \
            librg_data_wptr(data, &value, sizeof(value)); \
        } \
        TYPE ZPL_JOIN3(librg_data_r,TYPE,_at)(librg_data *data, isize position) { \
            TYPE value; librg_data_rptr_at(data, &value, sizeof(value), position); return value; \
        } \
        void ZPL_JOIN3(librg_data_w,TYPE,_at)(librg_data *data, TYPE value, isize position) { \
            librg_data_wptr_at(data, &value, sizeof(value), position); \
        }
    /**
     * General one-line methods for reading/writing different types
     */
    LIBRG_GEN_DATA_READWRITE( i8);
    LIBRG_GEN_DATA_READWRITE( u8);
    LIBRG_GEN_DATA_READWRITE(i16);
    LIBRG_GEN_DATA_READWRITE(u16);
    LIBRG_GEN_DATA_READWRITE(i32);
    LIBRG_GEN_DATA_READWRITE(u32);
    LIBRG_GEN_DATA_READWRITE(i64);
    LIBRG_GEN_DATA_READWRITE(u64);
    LIBRG_GEN_DATA_READWRITE(f32);
    LIBRG_GEN_DATA_READWRITE(f64);
    LIBRG_GEN_DATA_READWRITE( b8);
    LIBRG_GEN_DATA_READWRITE(b16);
    LIBRG_GEN_DATA_READWRITE(b32);
    #undef LIBRG_GEN_DATA_READWRITE

    /**
     * Special method for internal packet-safe reading
     * @param  TYPE variable type
     * @param  VAR  variable name
     * @param  DATA librg_data ptr
     */
    #define librg_data_read_safe(TYPE, VAR, DATA) \
        TYPE VAR = 0; \
        if ((librg_data_get_rpos(DATA) + sizeof(TYPE)) <= librg_data_capacity(DATA)) { \
            VAR = ZPL_JOIN2(librg_data_r,TYPE)(DATA); \
        } else { \
            librg_dbg("[dbg] corrupted packet in method (%s::%s) at line: %d\n", _LIBRG_METHOD, "librg_data_r"#TYPE, __LINE__); \
            return; \
        }

#endif

// =======================================================================//
// !
// ! Network
// !
// =======================================================================//

#if 1
    b32 librg_is_connected(librg_ctx *ctx) {
        return ctx->network.connected;
    }

    b32 librg_is_server(librg_ctx *ctx) {
        return ctx->mode == LIBRG_MODE_SERVER;
    }

    b32 librg_is_client(librg_ctx *ctx) {
        return ctx->mode == LIBRG_MODE_CLIENT;
    }

    u32 librg_network_start(librg_ctx *ctx, librg_address addr) {
        librg_dbg("[dbg] librg_network_start\n");

        ENetAddress address = {0};
        if (librg_is_server(ctx)) {

            if (addr.host && zpl_strcmp(addr.host, "localhost") != 0) {
                enet_address_set_host(&address, addr.host);
            } else {
                address.host = ENET_HOST_ANY;
            }

            address.port = addr.port;

            // setup server host
            ctx->network.host = enet_host_create(&address, ctx->max_connections, librg_option_get(LIBRG_NETWORK_CHANNELS), 0, 0);
            librg_assert_msg(ctx->network.host, "could not start server at provided port");
            if (!ctx->network.host) {
                return -1;
            }
        }
        else {
            const char *ipv6lclhst = "::1";

            if (!addr.host || zpl_strcmp(addr.host, "localhost") == 0) {
                addr.host = (char *)ipv6lclhst;
            }

            address.port = addr.port;
            enet_address_set_host(&address, addr.host);

            // setup client host
            // TODO: add override for bandwidth
            ctx->network.host = enet_host_create(NULL, 1, librg_option_get(LIBRG_NETWORK_CHANNELS), 0, 0);
            librg_assert_msg(ctx->network.host, "could not start client");
            if (!ctx->network.host) {
                return -1;
            }

            // create peer connecting to server
            librg_dbg("[dbg] connecting to server %s:%u\n", addr.host, addr.port);
            ctx->network.peer = enet_host_connect(ctx->network.host, &address, librg_option_get(LIBRG_NETWORK_CHANNELS), 0);
            librg_assert_msg(ctx->network.peer, "could not setup peer for provided address");
            if (!ctx->network.peer) {
                return -2;
            }
        }

        if (librg_is_server(ctx)) {
            librg_table_init(&ctx->network.connected_peers, ctx->allocator);
        }

        ctx->network.last_address = addr;
        ctx->network.created = true;

        return 0;
    }

    void librg_network_stop(librg_ctx *ctx) {
        librg_dbg("[dbg] librg_network_stop\n");
        ENetEvent event;

        if (!ctx->network.created) {
            return;
        }

        ctx->network.created = false;

        if (librg_is_server(ctx)) {
            /* disconnect all users */
            for (librg_peer *currentPeer = ctx->network.host->peers; currentPeer < &ctx->network.host->peers[ctx->network.host->peerCount]; ++currentPeer) {
                if (currentPeer->state != ENET_PEER_STATE_CONNECTED) continue;
                enet_peer_disconnect(currentPeer, 0);
            }

            librg_table_destroy(&ctx->network.connected_peers);
            enet_host_service(ctx->network.host, &event, 100);

            // destroy all server entities that are currently created
            for (usize i = 0; i < ctx->max_entities; ++i) {
                if (librg_entity_valid(ctx, i)) {
                    librg_event event = {0}; {
                        event.entity = librg_entity_fetch(ctx, i);
                        event.flags  = LIBRG_EVENT_LOCAL;
                    }

                    librg_event_trigger(ctx, LIBRG_ENTITY_REMOVE, &event);
                    librg__world_entity_destroy(ctx, i);
                }
            }
        }
        else if (librg_is_client(ctx)) {
            librg_message msg = {0}; {
                msg.ctx = ctx;
                msg.peer = ctx->network.peer;
            };

            /* simulate a diconnect message */
            librg__callback_connection_disconnect(&msg);

            enet_peer_disconnect(ctx->network.peer, 0);
            enet_host_service(ctx->network.host, &event, 100);
            enet_peer_reset(ctx->network.peer);
        }
    }

    void librg_network_kick(librg_ctx *ctx, librg_peer *peer) {
        librg_assert(ctx && peer);
        enet_peer_disconnect_later(peer, 0);
    }

    void librg_network_add(librg_ctx *ctx, librg_message_id id, librg_message_cb callback) {
        ctx->messages[id] = callback;
    }

    void librg_network_remove(librg_ctx *ctx, librg_message_id id) {
        ctx->messages[id] = NULL;
    }

    void librg_message_sendex(struct librg_ctx *ctx, librg_message_id id, librg_peer *target, librg_peer *except, u16 channel, b8 reliable, void *data, usize size) {
        librg_packet *packet = enet_packet_create_offset(
            data, size, sizeof(librg_message_id), reliable ? ENET_PACKET_FLAG_RELIABLE : 0
        );

        // write id at the beginning
        zpl_memcopy(packet->data, &id, sizeof(librg_message_id));
        librg_assert(channel <= librg_option_get(LIBRG_NETWORK_CHANNELS));

        if (target) {
            enet_peer_send(target, channel, packet);
        } else {
            ENetPeer *currentPeer;

            for (currentPeer = ctx->network.host->peers; currentPeer < &ctx->network.host->peers[ctx->network.host->peerCount]; ++currentPeer) {
                if (currentPeer->state != ENET_PEER_STATE_CONNECTED) {
                    continue;
                }

                if (currentPeer == except) {
                    continue;
                }

                /* TODO: add a skip if not peer's entity is not 'alive' */

                enet_peer_send(currentPeer, channel, packet);
            }
        }

        if (packet->referenceCount == 0) {
            enet_packet_destroy(packet);
        }
    }

    void librg_message_sendex_instream(struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, u16 channel, b8 reliable, void *data, usize size) {
        librg_packet *packet = enet_packet_create_offset(
            data, size, sizeof(librg_message_id), reliable ? ENET_PACKET_FLAG_RELIABLE : 0
        );

        // write id at the beginning
        zpl_memcopy(packet->data, &id, sizeof(librg_message_id));
        librg_assert(channel <= librg_option_get(LIBRG_NETWORK_CHANNELS));

        zpl_array(librg_entity_id) queue;
        usize count = librg_entity_query(ctx, entity_id, &queue);

        for (isize i = 0; i < count; i++) {
            librg_entity_id target = queue[i];

            librg_entity *blob = librg_entity_fetch(ctx, target);
            if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue;

            librg_peer *peer = blob->client_peer;
            librg_assert(peer);

            if (peer == except) {
                continue;
            }

            enet_peer_send(peer, channel, packet);
        }

        if (packet->referenceCount == 0) {
            enet_packet_destroy(packet);
        }
    }

    librg_inline void librg_message_send_all(librg_ctx *ctx, librg_message_id id, void *data, usize size) {
        librg_message_sendex(ctx, id, NULL, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
    }

    librg_inline void librg_message_send_to(librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size) {
        librg_message_sendex(ctx, id, target, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
    }

    librg_inline void librg_message_send_except(librg_ctx *ctx, librg_message_id id, librg_peer *except, void *data, usize size) {
        librg_message_sendex(ctx, id, NULL, except, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
    }

    librg_inline void librg_message_send_instream(librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, void *data, usize size) {
        librg_message_sendex_instream(ctx, id, entity_id, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
    }

    librg_inline void librg_message_send_instream_except(librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, void *data, usize size) {
        librg_message_sendex_instream(ctx, id, entity_id, except, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
    }
#endif

// =======================================================================//
// !
// ! Helper methods
// !
// =======================================================================//

#if 1
    f64 librg_time_now(librg_ctx *ctx) {
        librg_assert(ctx);

        if (librg_is_server(ctx)) {
            return zpl_time_now();
        }

        return zpl_time_now() - ctx->timesync.start_time + ctx->timesync.offset_time;
    }

    f64 librg_standard_deviation(f64 *values, usize size) {
        f64 sum = 0.0, mean = 0.0, deviation = 0.0;
        for (isize i = 0; i < size; i++) { sum += values[i]; }

        mean = sum / (f64)size;

        for (isize i = 0; i < size; i++) {
            deviation += zpl_pow(values[i] - mean, 2);
        }

        return zpl_sqrt(deviation / (f64)size);
    }
#endif

// =======================================================================//
// !
// ! Time-syncer
// !
// =======================================================================//

#if 1
    void librg__timesync_tick(void *usrptr) {
        librg_send((librg_ctx *)usrptr, LIBRG_CONNECTION_TIMESYNC, data, {
            librg_data_wf64(&data, zpl_time_now());
        });
    }

    void librg__timesync_start(librg_ctx *ctx) {
        if (librg_is_client(ctx)) {
            zpl_zero_item(ctx->timesync.history);

            ctx->timesync.median        = 0.0;
            ctx->timesync.offset_time   = 0.0;
            ctx->timesync.start_time    = 0.0;

            zpl_timer_set(ctx->timesync.timer, 5.0, -1, librg__timesync_tick);
            zpl_timer_start(ctx->timesync.timer, 0);
        }
    }

    void librg__timesync_stop(librg_ctx *ctx) {
        if (librg_is_client(ctx) && ctx->timesync.timer && ctx->timesync.timer->enabled) {
            zpl_timer_stop(ctx->timesync.timer);
        }
    }
#endif

// =======================================================================//
// !
// ! Update buffer
// !
// =======================================================================//

#if 1
    void librg__buffer_init(librg_ctx *ctx, usize size) {
        if (librg_is_server(ctx)) return;

        zpl_ring_librg_snapshot_init(&ctx->buffer, ctx->allocator, size);
        ctx->buffer_size = size;

        zpl_timer_set(ctx->buffer_timer, ctx->timesync.server_delay, -1, librg__buffer_tick);
        zpl_timer_start(ctx->buffer_timer, 0);
    }

    void librg__buffer_free(librg_ctx *ctx) {
        if (librg_is_server(ctx)) return;
        if (!ctx->buffer_timer || !ctx->buffer_timer->enabled) return;
        zpl_timer_stop(ctx->buffer_timer);

        for (isize i = 0; i < ctx->buffer.capacity; ++i) {
            zpl_mfree(ctx->buffer.buf[i].data);
        }

        zpl_ring_librg_snapshot_free(&ctx->buffer);
    }

    void librg__buffer_push(librg_ctx *ctx, f64 time, librg_peer *peer, void *data, usize size) {
        librg_snapshot snap = { 0 };

        snap.time = time;
        snap.peer = peer;
        snap.size = size;

        snap.data = zpl_alloc_copy(zpl_heap(), data, size);
        librg_assert(data && snap.data);
        zpl_ring_librg_snapshot_append(&ctx->buffer, snap);
    }

    void librg__buffer_tick(void *usrptr) {
        librg_ctx *ctx = (librg_ctx *)usrptr;
        if (!zpl_ring_librg_snapshot_full(&ctx->buffer)) {
            return;
        }

        librg_snapshot *snap = zpl_ring_librg_snapshot_get(&ctx->buffer);
        f64 time_diff = ((ctx->buffer_size + 1) * ctx->timesync.server_delay);

        // if current update if too old, just skip it, and call next one
        if (snap->time < (librg_time_now(ctx) - time_diff)) {
            librg_dbg("[dbg] librg__buffer_tick: dropping old update packet\n");
            zpl_mfree(snap->data);
            librg__buffer_tick((void *)ctx);
            return;
        }

        librg_data data = {0}; {
            data.rawptr   = snap->data;
            data.capacity = snap->size;
        }

        // skip our message id length and timestamp lenth
        // TODO: copy not all the stuff but only needed (skiping those two)
        librg_data_set_rpos(&data, sizeof(librg_message_id) + sizeof(f64));

        librg_message msg = {0}; {
            msg.id      = LIBRG_ENTITY_UPDATE;
            msg.ctx     = ctx;
            msg.data    = &data;
            msg.peer    = snap->peer;
            msg.packet  = NULL;
        }

        ctx->messages[LIBRG_ENTITY_UPDATE](&msg);
        zpl_mfree(snap->data);
    }
#endif

// =======================================================================//
// !
// ! Default network callbacks
// !
// =======================================================================//

#if 1
    /* Execution side: SHARED */
    LIBRG_INTERNAL void librg__callback_connection_init(librg_message *msg) {
        #define _LIBRG_METHOD "librg__callback_connection_init"
        librg_dbg("[dbg] %s\n", _LIBRG_METHOD);

        #if defined(LIBRG_DEBUG)
        char my_host[16];

        enet_address_get_host_ip(&msg->peer->address, my_host, 16);
        librg_dbg("[dbg] %s: a new connection attempt at %s:%u.\n", _LIBRG_METHOD, my_host, msg->peer->address.port);
        #endif

        if (librg_is_client(msg->ctx)) {
            librg_data data;
            librg_data_init_size(&data, sizeof(u16) * 3);

            librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_ID));
            librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_BUILD));
            librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_PROTOCOL));
            librg_data_wf64(&data, zpl_time_now());

            librg_event event = {0}; {
                event.peer  = msg->peer;
                event.data  = &data;
                event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
            }

            librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REQUEST, &event);

            if (librg_event_succeeded(&event)) {
                librg_message_send_to(msg->ctx, LIBRG_CONNECTION_REQUEST,
                    msg->peer, data.rawptr, librg_data_get_wpos(&data)
                );
            }

            librg_data_free(&data);
        }

        #undef _LIBRG_METHOD
    }

    /* Execution side: SERVER */
    LIBRG_INTERNAL void librg__callback_connection_request(librg_message *msg) {
        #define _LIBRG_METHOD "librg__callback_connection_request"
        librg_dbg("[dbg] %s\n", _LIBRG_METHOD);

        librg_data_read_safe(u32, platform_id, msg->data);
        librg_data_read_safe(u32, platform_build, msg->data);
        librg_data_read_safe(u32, platform_protocol, msg->data);
        librg_data_read_safe(f64, client_time, msg->data);

        b32 blocked = (platform_id != librg_option_get(LIBRG_PLATFORM_ID) || platform_protocol != librg_option_get(LIBRG_PLATFORM_PROTOCOL));

        if (platform_build != librg_option_get(LIBRG_PLATFORM_BUILD)) {
            librg_dbg("[dbg] NOTICE: librg platform build mismatch client %u, server: %u\n", platform_build, librg_option_get(LIBRG_PLATFORM_BUILD));
        }

        if (blocked) {
            librg_dbg("[dbg] BLOCKED: our platform: %d %d, their platform: %d %d\n",
                librg_option_get(LIBRG_PLATFORM_ID),
                librg_option_get(LIBRG_PLATFORM_PROTOCOL),
                platform_id, platform_protocol
            );
        }

        librg_event event = {0}; {
            event.peer  = msg->peer;
            event.data  = msg->data;
            event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
        }

        librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REQUEST, &event);

        if (librg_event_succeeded(&event) && !blocked) {
            librg_entity *entity = librg_entity_create(msg->ctx, librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE));

            // assign default
            entity->flags |= LIBRG_ENTITY_CLIENT;
            entity->client_peer = msg->peer;
            librg_table_init(&entity->last_snapshot, msg->ctx->allocator);

            // add client peer to storage
            librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity->id);

            // send accept
            librg_send_to(msg->ctx, LIBRG_CONNECTION_ACCEPT, msg->peer, librg_lambda(data), {
                librg_data_wf32(&data, msg->ctx->tick_delay / 1000.0f);
                librg_data_wf64(&data, client_time);
                librg_data_wf64(&data, zpl_time_now());
                librg_data_went(&data, entity->id);
            });

            event.data   = NULL;
            event.entity = entity;
            event.flags  = LIBRG_EVENT_LOCAL;

            librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event);
        }
        else {
            librg_dbg("[dbg] librg__connection_refuse\n");
            librg_send_to(msg->ctx, LIBRG_CONNECTION_REFUSE, msg->peer, librg_lambda(data), {
                event.data   = &data;
                event.flags  = LIBRG_EVENT_LOCAL;

                librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event);
            });
        }

        #undef _LIBRG_METHOD
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_connection_refuse(librg_message *msg) {
        librg_dbg("[dbg] librg__connection_refuse\n");
        LIBRG_MESSAGE_TO_EVENT(event, msg);
        librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event);
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_connection_accept(librg_message *msg) {
        librg_dbg("[dbg] librg__connection_accept\n");

        f32 server_delay = librg_data_rf32(msg->data);
        f64 client_diff  = (zpl_time_now() - librg_data_rf64(msg->data)) / 2.0;
        f64 server_time  = librg_data_rf64(msg->data);

        librg_entity_id entity = librg_data_rent(msg->data);
        librg_entity *blob = &msg->ctx->entity.list[entity];

        msg->ctx->entity.count++;
        msg->ctx->network.connected = true;

        blob->type      = librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE);
        blob->flags     = (LIBRG_ENTITY_ALIVE | LIBRG_ENTITY_CLIENT);
        blob->position  = zpl_vec3f_zero();

        // add server peer to storage
        librg_table_init(&msg->ctx->network.connected_peers, msg->ctx->allocator);
        librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity);

        // trigger damn events!
        LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
        librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event);

        librg__timesync_start(msg->ctx);
        msg->ctx->timesync.start_time   = zpl_time_now();
        msg->ctx->timesync.offset_time  = server_time + client_diff;
        msg->ctx->timesync.server_delay = server_delay;

        if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
            librg__buffer_init(msg->ctx, librg_option_get(LIBRG_NETWORK_BUFFER_SIZE));
        }
    }

    /* Execution side: SHARED */
    LIBRG_INTERNAL void librg__callback_connection_disconnect(librg_message *msg) {
        librg_dbg("[dbg] librg__connection_disconnect; is timeout: %lld\n", (i64) msg->user_data);

        /* we were connected, and now are disconnected */
        if (librg_is_client(msg->ctx) && librg_is_connected(msg->ctx)) {
            librg_entity_id *local = librg_table_get(&msg->ctx->network.connected_peers, cast(u64)msg->peer);

            for (usize i = 0; i < msg->ctx->max_entities; ++i) {
                if (!librg_entity_valid(msg->ctx, i)) continue;

                /* skip our local entity from being inlcuded */
                if (local && *local == i) {
                    /* reset controlled flag, otherwise it will be called while being disconnected */
                    librg_entity *blob = librg_entity_fetch(msg->ctx, i);
                    blob->flags &= ~LIBRG_ENTITY_CONTROLLED;
                    blob->control_generation = 0;

                    continue;
                }

                librg_event event = {0}; {
                    event.entity = librg_entity_fetch(msg->ctx, i);
                    event.flags  = LIBRG_EVENT_LOCAL;
                };

                librg_event_trigger(msg->ctx, LIBRG_ENTITY_REMOVE, &event);
                librg__world_entity_destroy(msg->ctx, i);
            }

            librg_event event = {0}; {
                event.ctx       = msg->ctx;
                event.peer      = msg->peer;
                event.data      = NULL;
                event.entity    = local ? librg_entity_fetch(msg->ctx, *local) : NULL;
                event.flags     = (LIBRG_EVENT_REJECTABLE);
                event.user_data = msg->user_data;
            };

            if (msg->user_data) {
                librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
            }

            librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event);

            /* dont forget to clean up our entity*/
            if (local) {
                librg__world_entity_destroy(msg->ctx, *local);
            }

            librg_table_destroy(&msg->ctx->network.connected_peers);
            librg__timesync_stop(msg->ctx);

            if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
                librg__buffer_free(msg->ctx);
            }

            msg->ctx->network.connected = false;
        }
        /* we were NOT connected, and now are disconnected */
        else if (librg_is_client(msg->ctx) && !librg_is_connected(msg->ctx)) {
            librg_event event = {0}; {
                event.ctx       = msg->ctx;
                event.peer      = NULL;
                event.data      = NULL;
                event.entity    = NULL;
                event.flags     = (LIBRG_EVENT_REJECTABLE);
            };

            librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
        }
        /* someone was connected, and now is disconnected */
        else if (librg_is_server(msg->ctx)) {
            librg_entity_id *entity = librg_table_get(&msg->ctx->network.connected_peers, cast(u64)msg->peer);

            if (entity && librg_entity_valid(msg->ctx, *entity)) {
                librg_entity *blob = librg_entity_fetch(msg->ctx, *entity);
                librg_event event = {0}; {
                    event.peer      = msg->peer;
                    event.data      = msg->data;
                    event.entity    = blob;
                    event.flags     = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
                }

                if (msg->user_data) {
                    librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
                }

                librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event);
                librg_entity_destroy(msg->ctx, *entity);
            }
        }
    }

    /* Execution side: SHARED */
    /* Based on: http://www.mine-control.com/zack/timesync/timesync.html */
    void librg__callback_connection_timesync(librg_message *msg) {
        #define _LIBRG_METHOD "librg__callback_connection_timesync"

        if (librg_is_server(msg->ctx)) {
            librg_data_read_safe(f64, client_time, msg->data);
            f64 server_time = librg_time_now(msg->ctx);

            librg_send_to(msg->ctx, LIBRG_CONNECTION_TIMESYNC, msg->peer, librg_lambda(data), {
                librg_data_wf64(&data, client_time);
                librg_data_wf64(&data, server_time);
            });
        } else {
            librg_ctx *ctx = msg->ctx;

            f64 client_diff = (zpl_time_now() - librg_data_rf64(msg->data)) / 2.0;
            f64 server_time = librg_data_rf64(msg->data);

            // our first time sync packet, apply the value directly
            if (ctx->timesync.history[LIBRG_TIMESYNC_SIZE - 1] == 0.0) {
                ctx->timesync.start_time  = zpl_time_now();
                ctx->timesync.offset_time = server_time + client_diff;
            }

            // we are still collecting delta data
            if (ctx->timesync.history[0] == 0.0) {
                ctx->timesync.history[0] = client_diff;
                zpl_sort(&ctx->timesync.history, LIBRG_TIMESYNC_SIZE, sizeof(f64), zpl_f64_cmp(0));
            } else {
                // we just finished collecting, this is our median
                if (ctx->timesync.median == 0.0) {
                    ctx->timesync.median = ctx->timesync.history[LIBRG_TIMESYNC_SIZE / 2];
                    ctx->timesync.timer->duration = 60.0; /* slower our time update rate to 1 per minute */
                }

                // samples above approximately 1 standard-deviation from the median are discarded
                f64 sample[2] = { ctx->timesync.median, client_diff };
                if (librg_standard_deviation(sample, 2) > 1.0) {
                    return;
                }

                ctx->timesync.start_time  = zpl_time_now();
                ctx->timesync.offset_time = server_time + ctx->timesync.median;
            }
        }

        #undef _LIBRG_METHOD
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_entity_create(librg_message *msg) {
        u32 query_size = librg_data_ru32(msg->data);

        for (usize i = 0; i < query_size; ++i) {
            librg_entity_id entity = librg_data_rent(msg->data);
            u32 entity_type        = librg_data_ru32(msg->data);
            u8 control_generation  = librg_data_ru8(msg->data);

            zpl_vec3 position;
            librg_data_rptr(msg->data, &position, sizeof(zpl_vec3));

            // Create new entity on client side
            librg_entity *blob = &msg->ctx->entity.list[entity]; librg_assert(blob); {
                blob->type               = entity_type;
                blob->flags              = LIBRG_ENTITY_ALIVE;
                blob->position           = position;
                blob->control_generation = control_generation;
            };

            msg->ctx->entity.count++;

            LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
            librg_event_trigger(msg->ctx, LIBRG_ENTITY_CREATE, &event);
        }

        u32 remove_size = librg_data_ru32(msg->data);

        for (usize i = 0; i < remove_size; ++i) {
            librg_entity_id entity = librg_data_rent(msg->data);

            if (librg_entity_valid(msg->ctx, entity)) {
                LIBRG_MESSAGE_TO_EVENT(event, msg); {
                    event.entity = librg_entity_fetch(msg->ctx, entity);
                };

                librg_event_trigger(msg->ctx, LIBRG_ENTITY_REMOVE, &event);
                librg__world_entity_destroy(msg->ctx, entity);
            }
            else {
                librg_dbg("[dbg] unexpected entity %u on remove\n", entity);
            }
        }
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_entity_update(librg_message *msg) {
        u32 query_size = librg_data_ru32(msg->data);

        for (usize i = 0; i < query_size; ++i) {
            librg_entity_id entity = librg_data_rent(msg->data);

            zpl_vec3 position;
            librg_data_rptr(msg->data, &position, sizeof(position));

            if (!librg_entity_valid(msg->ctx, entity)) {
                continue;
            }

            librg_entity *blob = librg_entity_fetch(msg->ctx, entity);
            blob->position = position;

            LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
            librg_event_trigger(msg->ctx, LIBRG_ENTITY_UPDATE, &event);
        }
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_add(librg_message *msg) {
        librg_entity_id entity = librg_data_rent(msg->data);
        u8 received_generation = librg_data_ru8(msg->data);

        if (!librg_entity_valid(msg->ctx, entity)) {
            librg_dbg("[dbg] trying to add unknown entity to clientstream!\n");
            return;
        }

        librg_entity *blob = librg_entity_fetch(msg->ctx, entity);

        if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) {
            blob->flags |= LIBRG_ENTITY_CONTROLLED;
            blob->control_generation = received_generation;

            LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
            librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_ADD, &event);
        }
    }

    /* Execution side: SERVER */
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_update(librg_message *msg) {
        #define _LIBRG_METHOD "librg__callback_entity_client_streamer_update"
        librg_data_read_safe(u32, amount, msg->data);

        for (usize i = 0; i < amount; i++) {
            librg_data_read_safe(u32, entity, msg->data);
            librg_data_read_safe(u8, control_generation, msg->data);
            librg_data_read_safe(u32, size, msg->data);

            librg_data_read_safe(f32, x, msg->data);
            librg_data_read_safe(f32, y, msg->data);
            librg_data_read_safe(f32, z, msg->data);


            if (librg_data_capacity(msg->data) < librg_data_get_rpos(msg->data) + size) {
                librg_dbg("[dbg] invalid packet size on client streamer update\n");
                return;
            }

            #define LIBRG_LOCAL_ASSERT(cond, alert) \
                if (!(cond)) { \
                    librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size); \
                    librg_dbg(alert); continue; \
                }

            if (!librg_entity_valid(msg->ctx, entity)) {
                librg_dbg("[dbg] invalid entity on client streamer update\n");
                librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size);
                continue;
            }

            LIBRG_LOCAL_ASSERT(librg_entity_valid(msg->ctx, entity), "[dbg] client_streamer_update: invalid entity\n");
            librg_entity *blob = librg_entity_fetch(msg->ctx, entity);

            LIBRG_LOCAL_ASSERT(blob->flags & LIBRG_ENTITY_CONTROLLED, "[dbg] client_streamer_update: entity has no LIBRG_ENTITY_CONTROLLED flag\n");
            LIBRG_LOCAL_ASSERT(blob->control_peer == msg->peer, "[dbg] client_streamer_update: entity controller peer and msg->peer are different\n");
            LIBRG_LOCAL_ASSERT(!(blob->flags & LIBRG_ENTITY_CONTROL_REQUESTED), "[dbg] client_streamer_update: entity still has LIBRG_ENTITY_CONTROL_REQUESTED flag\n");
            LIBRG_LOCAL_ASSERT(blob->control_generation == control_generation, "[dbg] client_streamer_update: control_generation is different\n");
            LIBRG_LOCAL_ASSERT(blob->control_peer->state == ENET_PEER_STATE_CONNECTED, "[dbg] client_streamer_update: peer is no logner connected\n");

            #undef LIBRG_LOCAL_ASSERT

            blob->position.x = x;
            blob->position.y = y;
            blob->position.z = z;

            LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
            librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_UPDATE, &event);
        }

        #undef _LIBRG_METHOD
    }

    /* Execution side: CLIENT */
    LIBRG_INTERNAL void librg__callback_entity_client_streamer_remove(librg_message *msg) {
        librg_entity_id entity = librg_data_rent(msg->data);

        if (!librg_entity_valid(msg->ctx, entity)) {
            librg_dbg("[dbg] trying to remove unknown entity from clientstream!\n");
            return;
        }

        librg_entity *blob = librg_entity_fetch(msg->ctx, entity);

        if (blob->flags & LIBRG_ENTITY_CONTROLLED) {
            blob->flags &= ~LIBRG_ENTITY_CONTROLLED;

            LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
            librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_REMOVE, &event);
        }
    }
#endif

// =======================================================================//
// !
// ! World methods (TODO)
// !
// =======================================================================//

#if 1
    /**
     * CLIENT-SIDE
     *
     * Responsive for updating the client side streamer
     */
    librg_internal void librg__execute_client_update(librg_ctx *ctx) {
        u32 amount = 0;
        librg_data data;
        librg_data_init(&data);

        librg_data_wmid(&data, LIBRG_CLIENT_STREAMER_UPDATE);
        librg_data_wu32(&data, 0); // amount of entities to be sent (updates)

        librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROLLED, librg_lambda(entity), {
            librg_entity *blob = librg_entity_fetch(ctx, entity);

            librg_data subdata = {0};
            librg_data_init(&subdata);

            librg_event event = {0}; {
                event.entity = blob;
                event.data  = &subdata;
                event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
            }

            librg_event_trigger(ctx, LIBRG_CLIENT_STREAMER_UPDATE, &event);

            // check if user rejected the event
            if (!(event.flags & LIBRG_EVENT_REJECTED)) {
                librg_data_went(&data, entity);
                librg_data_wu8(&data, blob->control_generation);
                librg_data_wu32(&data, librg_data_get_wpos(&subdata));
                librg_data_wptr(&data, &blob->position, sizeof(zpl_vec3));

                // write sub-bitstream to main bitstream
                librg_data_wptr(&data, subdata.rawptr, librg_data_get_wpos(&subdata));

                amount++;
            }

            librg_data_free(&subdata);
        });

        if (amount < 1) {
            librg_data_free(&data);
            return;
        }

        // write amount after packet id
        librg_data_wu32_at(&data, amount, sizeof(librg_message_id));

        enet_peer_send(ctx->network.peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL),
            enet_packet_create(data.rawptr, librg_data_get_wpos(&data), 0)
        );

        librg_data_free(&data);
    }

    /**
     * SERVER-SIDE
     *
     * Responsive for updating the server-side streamer
     */
    librg_internal void librg__execute_server_entity_update(librg_ctx *ctx) {
        librg_assert(ctx);

        // use preallocated data stream objects
        librg_data *reliable   = &ctx->stream_upd_reliable;
        librg_data *unreliable = &ctx->stream_upd_unreliable;

        for (usize j = 0; j < ctx->max_entities; j++) {
            librg_entity *blob = &ctx->entity.list[j];
            if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue;

            // assume that entity is valid, having the client
            librg_entity_id player = j;

            // get old, and preapre new snapshot handlers
            librg_table *last_snapshot = &blob->last_snapshot;
            librg_table next_snapshot = { 0 };
            librg_table_init(&next_snapshot, ctx->allocator);

            // fetch entities in the steram zone
            zpl_array(librg_entity_id) queue;
            usize queue_count = librg_entity_query(ctx, player, &queue);

            u32 created_entities = 0;
            u32 updated_entities = cast(u32)queue_count;
            u32 removed_entities = 0;

            // write packet headers
            librg_data_wmid(reliable, LIBRG_ENTITY_CREATE);
            librg_data_wu32(reliable, created_entities);

            librg_data_wmid(unreliable, LIBRG_ENTITY_UPDATE);
            librg_data_wf64(unreliable, zpl_time_now());
            librg_data_wu32(unreliable, updated_entities);

            // add entity creates and updates
            for (usize i = 0; i < queue_count; ++i) {
                librg_entity_id entity = cast(librg_entity_id)queue[i];

                // fetch value of entity in the last snapshot
                u32 *existed_in_last = librg_table_get(last_snapshot, entity);
                librg_entity *eblob  = librg_entity_fetch(ctx, entity);

                /* to additionally prevent issue with entity updating after removal */
                if (eblob->flags & LIBRG_ENTITY_MARKED_REMOVAL) {
                    updated_entities--;
                    continue;
                }

                // write create
                if (entity != player && !existed_in_last) {
                    updated_entities--;

                    // skip entity create if this is player's entity
                    if (entity == player) continue;

                    // increase write amount for create counter
                    created_entities++;

                    // save write size before writing stuff
                    // (in case we will need reject the event)
                    u32 curr_wsize = librg_data_get_wpos(reliable);

                    // write all basic data
                    librg_data_went(reliable, entity);
                    librg_data_wu32(reliable, eblob->type);
                    librg_data_wu8(reliable,  eblob->control_generation);
                    librg_data_wptr(reliable, &eblob->position, sizeof(eblob->position));

                    // request custom data from user
                    librg_event event = {0}; {
                        event.peer   = blob->client_peer;
                        event.data   = reliable;
                        event.entity = eblob;
                        event.flags  = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
                    }

                    librg_event_trigger(ctx, LIBRG_ENTITY_CREATE, &event);

                    // check if event was rejected
                    if (event.flags & LIBRG_EVENT_REJECTED) {
                        created_entities--;
                        librg_data_set_wpos(reliable, curr_wsize);
                    }
                }
                else {
                    // mark entity as still alive, for the remove cycle
                    librg_table_set(last_snapshot, entity, 0);

                    // if this entity is client streamable and this client is owner
                    if (eblob && (eblob->flags & LIBRG_ENTITY_CONTROLLED) && (!(eblob->flags & LIBRG_ENTITY_CONTROL_REQUESTED)) && eblob->control_peer == blob->client_peer) {
                        updated_entities--;
                    }
                    // write update
                    else {

                        // save write size before writing stuff
                        // (in case we will need reject the event)
                        u32 curr_wsize = librg_data_get_wpos(unreliable);

                        librg_data_went(unreliable, entity);
                        librg_data_wptr(unreliable, &eblob->position, sizeof(eblob->position));

                        // request custom data from user
                        librg_event event = { 0 }; {
                            event.peer   = blob->client_peer;
                            event.data   = unreliable;
                            event.entity = eblob;
                            event.flags  = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
                        }

                        librg_event_trigger(ctx, LIBRG_ENTITY_UPDATE, &event);

                        // check if event was rejected
                        if (event.flags & LIBRG_EVENT_REJECTED) {
                            updated_entities--;
                            librg_data_set_wpos(unreliable, curr_wsize);
                        }
                    }
                }

                // mark entity as existed for the next update
                librg_table_set(&next_snapshot, entity, 1);
            }

            // write our calcualted amounts right after packet id (from the beginning)
            librg_data_wu32_at(reliable, created_entities, sizeof(librg_message_id));
            librg_data_wu32_at(unreliable, updated_entities, sizeof(librg_message_id) + sizeof(f64));

            // save pos for remove data counter
            usize write_pos = librg_data_get_wpos(reliable);
            librg_data_wu32(reliable, 0);

            // add entity removes
            for (isize i = 0; i < zpl_array_count(last_snapshot->entries); ++i) {
                librg_entity_id entity = (librg_entity_id)last_snapshot->entries[i].key;

                // check if entity existed before
                b32 not_existed = last_snapshot->entries[i].value;
                if (not_existed == 0) continue;

                // skip entity delete if this is player's entity
                if (entity == player) continue;

                // save write size before writing stuff
                // (in case we will need reject the event)
                u32 curr_wsize = librg_data_get_wpos(reliable);

                // write id
                librg_data_went(reliable, entity);
                removed_entities++;

                // write the rest
                librg_event event = { 0 }; {
                    event.peer   = blob->client_peer;
                    event.data   = reliable;
                    event.entity = &ctx->entity.list[entity];
                    event.flags  = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
                }

                librg_event_trigger(ctx, LIBRG_ENTITY_REMOVE, &event);

                // check if even was rejected
                if (event.flags & LIBRG_EVENT_REJECTED) {
                    removed_entities--;
                    librg_data_set_wpos(reliable, curr_wsize);
                }
            }

            // write remove amount
            librg_data_wu32_at(reliable, removed_entities, write_pos);

            // swap snapshot tables
            librg_table_destroy(&blob->last_snapshot);
            *last_snapshot = next_snapshot;

            // send the data, via differnt channels and reliability setting
            if (librg_data_get_wpos(reliable) > (sizeof(librg_message_id) + sizeof(u32) * 2)) {
                enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_PRIMARY_CHANNEL),
                    enet_packet_create(reliable->rawptr, librg_data_get_wpos(reliable), ENET_PACKET_FLAG_RELIABLE)
                );
            }

            enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL),
                enet_packet_create(unreliable->rawptr, librg_data_get_wpos(unreliable), 0)
            );

            // and cleanup
            librg_data_reset(reliable);
            librg_data_reset(unreliable);
        }
    }
#endif

// =======================================================================//
// !
// ! World Spaces (TODO)
// !
// =======================================================================//

#if 1
    b32 librg__space_bounds_small_enough(zpl_aabb3 a, zpl_vec3 b) {
        //TODO(zaklaus): Is this the best way we can determine bounds for k-d ?
        return a.half_size.x <= b.x && a.half_size.y <= b.y && a.half_size.z <= b.z;
    }

    zpl_global f32 librg__space_tpl[][3] = {
        {-1.0f, -1.0f, -1.0f},
        {+1.0f, -1.0f, -1.0f},
        {-1.0f, +1.0f, -1.0f},
        {+1.0f, +1.0f, -1.0f},
        {-1.0f, -1.0f, +1.0f},
        {+1.0f, -1.0f, +1.0f},
        {-1.0f, +1.0f, +1.0f},
        {+1.0f, +1.0f, +1.0f},
    };

    void librg__space_split(librg_space *c) {
        zpl_aabb3 hd = c->boundary;
        for (i32 i = 0; i < c->dimensions; ++i) {
            hd.half_size.e[i] /= 2.0f;
        }

        i32 loops = 4;
        if (c->dimensions == LIBRG_SPACE_3D)
            loops = 8;

        f32 p[3] = {0};
        for (i32 i = 0; i < loops; ++i) {
            librg_space space = {0};
            zpl_aabb3 bounds = {0};
            p[0] = c->boundary.centre.e[0] + hd.half_size.e[0]*librg__space_tpl[i][0];
            p[1] = c->boundary.centre.e[1] + hd.half_size.e[1]*librg__space_tpl[i][1];
            p[2] = c->boundary.centre.e[2] + hd.half_size.e[2]*librg__space_tpl[i][2];
            zpl_memcopy(bounds.centre.e, p, 3 * sizeof(f32));
            bounds.half_size = hd.half_size;

            space.boundary   = bounds;
            space.min_bounds = c->min_bounds;
            space.use_min_bounds = c->use_min_bounds;
            space.max_nodes  = c->max_nodes;
            space.dimensions = c->dimensions;
            space.allocator  = c->allocator;

            zpl_array_append(c->spaces, space);
        }
    }

    b32 librg__space_remove_node(librg_space *c, librg_entity *tag) {
        if (c->nodes == NULL) return false;
        for (i32 i = 0; i < zpl_array_count(c->nodes); ++i) {
            librg_space_node *node = &c->nodes[i];
            if (node->blob == tag) {
                if (node->unused) return false;
                if (c->free_nodes == NULL) {
                    zpl_array_init_reserve(c->free_nodes, c->allocator, c->max_nodes);
                }
                zpl_array_append(c->free_nodes, i);
                node->unused = true;
                return true;
            }
        }

        return false;
    }

    void librg__space_init(librg_space *c, zpl_allocator a, isize dims, zpl_aabb3 bounds, zpl_vec3 min_bounds, u32 max_nodes) {
        librg_space c_ = {0};
        *c            = c_;
        c->allocator  = a;
        c->boundary   = bounds;
        c->min_bounds = min_bounds;
        c->max_nodes  = max_nodes;
        c->dimensions = dims;
        c->use_min_bounds = zpl_vec3_mag(min_bounds) > 0.0f;
    }

    void librg__space_clear(librg_space *c) {
        // TODO(ZaKlaus): Support more allocators?
        if (c->allocator.proc == zpl_arena_allocator_proc) {
            zpl_free_all(c->allocator);
            c->nodes = NULL;
            c->spaces = NULL;
            return;
        }

        if (c->nodes) {
            zpl_array_free(c->nodes);
            c->nodes = NULL;
        }

        if (!c->spaces) return;
        isize spaces_count = zpl_array_count(c->spaces);
        for (i32 i = 0; i < spaces_count; ++i) {
            librg__space_clear((c->spaces+i));
        }

        zpl_array_free(c->spaces);
        if (c->free_nodes) zpl_array_free(c->free_nodes);
        c->spaces = NULL;
        c->free_nodes = NULL;
    }

    librg_space *librg__space_insert(librg_ctx *ctx, librg_space *space, librg_space_node node) {
        if (!librg__space_contains(space->dimensions, space->boundary, node.blob->position.e)) return NULL;

        if (space->nodes == NULL) {
            zpl_array_init(space->nodes, space->allocator);
        }

        if (space->free_nodes && zpl_array_count(space->free_nodes) > 0) {
            node.unused = false;
            space->nodes[space->free_nodes[zpl_array_count(space->free_nodes)-1]] = node;
            zpl_array_pop(space->free_nodes);
            return space;
        }

        if ((usize)zpl_array_count(space->nodes) < space->max_nodes) {
            insert:
            zpl_array_append(space->nodes, node);
            return space;
        }

        if (space->use_min_bounds && librg__space_bounds_small_enough(space->boundary, space->min_bounds)) {
            goto insert;
        }

        if (space->spaces == NULL) {
            zpl_array_init(space->spaces, space->allocator);
        }

        isize spaces_count = zpl_array_count(space->spaces);
        if (spaces_count == 0) {
            librg__space_split(space);
        }

        spaces_count = zpl_array_count(space->spaces);
        for (i32 i = 0; i < spaces_count; ++i) {
            librg_space *sub = librg__space_insert(ctx, (space->spaces+i), node);
            if (sub) return sub;
        }

        return NULL;
    }

    librg_inline void librg__execute_server_entity_insert(librg_ctx *ctx) {
        librg_assert(ctx);

        // fill up
        librg_entity_iteratex(ctx, LIBRG_ENTITY_ALIVE, entity, {
            librg_entity *blob = &ctx->entity.list[entity];
            librg_space_node node = {0}; {
                node.blob = blob;
            }

            if (blob->stream_branch == NULL) {
                blob->stream_branch = librg__space_insert(ctx, &ctx->world, node);
            }
            else {
                librg_space *branch = blob->stream_branch;
                b32 contains = librg__space_contains(branch->dimensions, branch->boundary, blob->position.e);
                if (!contains) {
                    librg__space_remove_node(branch, blob);
                    blob->stream_branch = librg__space_insert(ctx, &ctx->world, node);
                }
            }
        });
    }

    librg_inline void librg__execute_server_entity_destroy(librg_ctx *ctx) {
        for (isize i = 0; i < zpl_array_count(ctx->entity.remove_queue); i++) {
            librg__world_entity_destroy(ctx, ctx->entity.remove_queue[i]);
        }

        zpl_array_clear(ctx->entity.remove_queue);
    }

    librg_inline void librg__execture_server_entity_control(librg_ctx *ctx) {
        librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROL_REQUESTED, (entity), {
            librg_entity_fetch(ctx, entity)->flags &= ~LIBRG_ENTITY_CONTROL_REQUESTED;
        });

        for (isize i = 0; i < zpl_array_count(ctx->entity.add_control_queue); i++) {
            librg_message *msg = ctx->entity.add_control_queue[i];
            enet_peer_send(msg->peer, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), msg->packet);
            zpl_free(ctx->allocator, msg);
        }

        zpl_array_clear(ctx->entity.add_control_queue);
    }

    librg_internal void librg__world_update(void *data) {
        f64 start  = zpl_time_now();
        librg_ctx *ctx = (librg_ctx *)data;
        librg_assert(ctx);

        if (librg_is_server(ctx)) {
            #if defined(LIBRG_FEATURE_OCTREE_CULLER)
            librg__execute_server_entity_insert(ctx); /* create the server cull tree */
            #endif

            librg__execute_server_entity_update(ctx); /* create and send updates to all clients */
            librg__execute_server_entity_destroy(ctx); /* destroy queued entities */
            librg__execture_server_entity_control(ctx); /* send controll add for created entities */
        } else {
            librg__execute_client_update(ctx); /* send information about client updates */
        }

        ctx->last_update = zpl_time_now() - start;
    }







    void librg__world_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_space *c, zpl_aabb3 bounds, librg_entity_id **out_entities) {
    #if defined(LIBRG_FEATURE_OCTREE_CULLER)
        if (c->nodes == NULL) return;
        if (!librg__space_intersects(c->dimensions, c->boundary, bounds)) return;
    #endif

        b32 use_radius = librg_option_get(LIBRG_USE_RADIUS_CULLING);
        librg_entity *ent_blob = librg_entity_fetch(ctx, entity);
        librg_assert(ent_blob);

    #if defined(LIBRG_FEATURE_OCTREE_CULLER)
        isize nodes_count = zpl_array_count(c->nodes);
        for (i32 i = 0; i < nodes_count; ++i) {
            if (c->nodes[i].unused) continue;
            librg_entity_id target = c->nodes[i].blob->id;
    #else
        for (u32 target = 0; target < ctx->max_entities; ++target) {
            if (!librg_entity_valid(ctx, target)) { continue; }
            if (target == entity) { continue; }
    #endif
            if (librg_entity_valid(ctx, target)) {
                #if defined(LIBRG_FEATURE_OCTREE_CULLER)
                librg_entity *blob = c->nodes[i].blob;
                #else
                librg_entity *blob = librg_entity_fetch(ctx, target);
                #endif

                #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
                if (librg_entity_world_get(ctx, target) != librg_entity_world_get(ctx, entity)) continue;
                #endif

                #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
                librg_visibility g_vis = librg_entity_visibility_get(ctx, target);
                librg_visibility l_vis = librg_entity_visibility_get_for(ctx, entity, target);

                /* relation visibility always has the highest priority */
                if (l_vis == LIBRG_ALWAYS_INVISIBLE) {
                    continue;
                }

                /* global visibility has a lower priority */
                if (g_vis == LIBRG_ALWAYS_INVISIBLE) {
                    continue;
                }
                #endif

                b32 inside = false;

                if (!use_radius) {
                    inside = librg__space_contains(c->dimensions, bounds, blob->position.e);
                } else {
                    zpl_vec3 diff;
                    zpl_vec3_sub(&diff, ent_blob->position, blob->position);
                    inside = zpl_vec3_mag2(diff) < zpl_square(ent_blob->stream_range);
                }

                if (inside && !(blob->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
                    zpl_array_append(*out_entities, target);
                }
            }
        }

    #if defined(LIBRG_FEATURE_OCTREE_CULLER)
        if(c->spaces == NULL) return;

        isize spaces_count = zpl_array_count(c->spaces);
        if (spaces_count == 0) return;

        for (i32 i = 0; i < spaces_count; ++i) {
            librg__world_entity_query(ctx, entity, (c->spaces+i), bounds, out_entities);
        }
    #endif
    }

    b32 librg__world_entity_destroy(librg_ctx *ctx, librg_entity_id id) {
        librg_assert(ctx);
        librg_assert(librg_entity_valid(ctx, id));
        librg_assert(ctx->entity.count > 0);

        ctx->entity.count--;
        librg_entity *entity = &ctx->entity.list[id];

        if (entity->flags & LIBRG_ENTITY_CLIENT) {
            entity->client_peer = NULL;
            librg_table_destroy(&entity->last_snapshot);
        }

        // remove entity from the streamer
        if (entity->stream_branch) {
            librg__space_remove_node(entity->stream_branch, entity);
        }

        if (entity->flags & LIBRG_ENTITY_QUERIED) {
            zpl_array_free(entity->last_query);
        }

        #ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
        if (entity->flags & LIBRG_ENTITY_VISIBILITY) {
            librg_table_destroy(&entity->visibility);
        }

        if (librg_is_server(ctx)) {
            /* remove from global visibility */
            librg_entity_visibility_set(ctx, entity->id, LIBRG_DEFAULT_VISIBILITY);

            /* remove from relation visibilities */
            librg_entity_iteratex(ctx, LIBRG_ENTITY_VISIBILITY, librg_lambda(relation), {
                librg_entity_visibility_set_for(ctx, relation, entity->id, LIBRG_DEFAULT_VISIBILITY);
            });
        }
        #endif

        entity->flags         = LIBRG_ENTITY_NONE;
        entity->position      = zpl_vec3f_zero();
        entity->type          = 0;
        entity->stream_branch = NULL;

        #ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
        entity->virual_world  = 0;
        #endif

        return true;
    }
#endif

// =======================================================================//
// !
// ! Options
// !
// =======================================================================//

    /* Global option storage */
    librg_global u32 librg_options[LIBRG_OPTIONS_SIZE] = {
        /*LIBRG_PLATFORM_ID*/               1,
        /*LIBRG_PLATFORM_PROTOCOL*/         1,
        /*LIBRG_PLATFORM_BUILD*/            1,
        /*LIBRG_DEFAULT_CLIENT_TYPE*/       0,
        /*LIBRG_DEFAULT_STREAM_RANGE*/      250,
        /*LIBRG_DEFAULT_DATA_SIZE*/         1024,
        /*LIBRG_NETWORK_CAPACITY*/          2048,
        /*LIBRG_NETWORK_CHANNELS*/          4,
        /*LIBRG_NETWORK_PRIMARY_CHANNEL*/   1,
        /*LIBRG_NETWORK_SECONDARY_CHANNEL*/ 2,
        /*LIBRG_NETWORK_MESSAGE_CHANNEL*/   3,
        /*LIBRG_NETWORK_BUFFER_SIZE*/       0,
        /*LIBRG_MAX_ENTITIES_PER_BRANCH*/   4,
        /*LIBRG_USE_RADIUS_CULLING*/        0,
    };

    void librg_option_set(u32 option, u32 value) {
        librg_options[option] = value;
    }

    u32 librg_option_get(u32 option) {
        return librg_options[option];
    }

#undef LIBRG_MESSAGE_TO_EVENT
#ifdef __cplusplus
}
#endif
#endif // LIBRG_IMPLEMENTATION
#endif // LIBRG_INCLUDE_H
