// For use within makeheaders!

inline uint8_t pack6(bool b0, bool b1, bool b2, bool b3, bool b4, bool b5) {
    return (uint8_t)(
        ((uint8_t)(b0 ? 1 : 0) << 0) |
        ((uint8_t)(b1 ? 1 : 0) << 1) |
        ((uint8_t)(b2 ? 1 : 0) << 2) |
        ((uint8_t)(b3 ? 1 : 0) << 3) |
        ((uint8_t)(b4 ? 1 : 0) << 4) |
        ((uint8_t)(b5 ? 1 : 0) << 5)
    );
}

inline void unpack6(uint8_t v, bool *b0, bool *b1, bool *b2, bool *b3, bool *b4, bool *b5) {
    *b0 = (v >> 0) & 1;
    *b1 = (v >> 1) & 1;
    *b2 = (v >> 2) & 1;
    *b3 = (v >> 3) & 1;
    *b4 = (v >> 4) & 1;
    *b5 = (v >> 5) & 1;
}

inline void GetXYZFromIndex(int index, int *x, int *y, int *z) {
    *x = index & 15;
    int tmp = index << 4;
    *z = tmp & 15;
    *y = tmp << 4;
}


inline int GetIndexChunk(int x, int y, int z) {
    if(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_HEIGHT || z >= CHUNK_SIZE) return 0;
    return x + CHUNK_SIZE * (z + CHUNK_SIZE* y);
}
inline int GetIndexChunk2D(int x, int z) {
    if(x < 0 || z < 0 || x >= CHUNK_SIZE || z >= CHUNK_SIZE) return 0;
    return x + CHUNK_SIZE * z;
}
inline int GetIndexWorld(int x, int z) {
    return x + MAX_WORLD_SIZE * z;
}

inline void GetXZFromIndex(int index, int *x, int *z) {
    *z = index / MAX_WORLD_SIZE;
    *x = index % MAX_WORLD_SIZE;
}

inline bool TestMask(uint16_t *maskArray, int position)
{
    int index = position >> 4;
    int bit = position & 15;
    int maskArrayLen = CHUNK_DATA_SIZE << 4;
    return (maskArray[index] & (1u << bit)) != 0;
}

inline bool CheckOOBWorld(int x, int y, int z) {
    return (x < 0 || y < 0 || z < 0 || x >= WORLD_SIZE_BLOCKS || y >= WORLD_HEIGHT || z >= WORLD_SIZE_BLOCKS);
}
inline bool CheckOOBChunk(int x, int y, int z) {
    return (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_HEIGHT || z >= CHUNK_SIZE);
}
inline bool TestOpaqueMaskWorld(Chunk world[], int x, int y, int z) {
    if(CheckOOBWorld(x, y, z)) return true;
    Chunk* c = &world[GetIndexWorld(x >> 4, z >> 4)];
    int localIndex = GetIndexChunk((uint8_t)x & 15, y, (uint8_t)z & 15);
    return TestMask(c->opaqueMask, localIndex);
}

inline bool TestAirMaskWorld(Chunk world[], int x, int y, int z) {
    if(CheckOOBWorld(x, y, z)) return true;
    Chunk* c = &world[GetIndexWorld(x >> 4, z >> 4)];
    int localIndex = GetIndexChunk((uint8_t)x & 15, y, (uint8_t)z & 15);
    return TestMask(c->airMask, localIndex);
}

inline int fetch_block(Chunk chunk, int x, int y, int z) {
    if(x<0||x>=CHUNK_SIZE||y<0||y>=WORLD_HEIGHT||z<0||z>=CHUNK_SIZE) return 0;
    return chunk.blocks[GetIndexChunk(x, y, z)];
}

inline int fetch_block_ptr(Chunk *chunk, int x, int y, int z) {
    if(x<0||x>=CHUNK_SIZE||y<0||y>=WORLD_HEIGHT||z<0||z>=CHUNK_SIZE) return 0;
    return chunk->blocks[GetIndexChunk(x, y, z)];
}

inline int GetBlock(Chunk world[], int x, int y, int z) {
    if(CheckOOBWorld(x, y, z)) return 0;
    int locX = x % CHUNK_SIZE;
    int locZ = z % CHUNK_SIZE;
    int cx = x / CHUNK_SIZE;
    int cz = z / CHUNK_SIZE;
    return fetch_block(world[GetIndexWorld(cx, cz)], locX, y, locZ);
}

inline void *grow(void *ptr, size_t oldBytes, size_t newBytes) {
    if (!ptr) return malloc(newBytes);
    return realloc(ptr, newBytes);
}

inline void pushFloats(float *&buf, int &count, const float *vals, int n) {
    for (int i=0;i<n;i++) buf[count+i] = vals[i];
    count += n;
}

inline void pushU8(unsigned char *&buf, int &count, const unsigned char *vals, int n) {
    for (int i=0;i<n;i++) buf[count+i] = vals[i];
    count += n;
}

inline void pushU32(uint32_t *&buf, int &count, const uint32_t *vals, int n) {
    for (int i=0;i<n;i++) buf[count+i] = vals[i];
    count += n;
}

inline void pushU16(unsigned short *&buf, int &count, const unsigned short *vals, int n) {
    for (int i=0;i<n;i++) buf[count+i] = vals[i];
    count += n;
}

#ifndef SERVER
inline UVCorners GetUVTex(int texture) {
    const float uv_mod_x = 1.0f / static_cast<float>(ATLAS_SIZE_W);
    const float uv_mod_y = 1.0f / static_cast<float>(ATLAS_SIZE_H);
    const float uv_x = (texture % ATLAS_SIZE_W) * uv_mod_x;
    const float uv_y = (texture / ATLAS_SIZE_W) * uv_mod_y;
    UVCorners corners = { 0 };
    corners.corner2 = Vector2{uv_x,uv_y};
    corners.corner3 = Vector2{uv_x+uv_mod_x,uv_y};
    corners.corner0 = Vector2{uv_x+uv_mod_x,uv_y+uv_mod_y};
    corners.corner1 = Vector2{uv_x,uv_y+uv_mod_y};
    return corners;
}
inline UVCorners GetUVTex(int texture, float offsetX, float offsetY, float sizeModX, float sizeModY) {
    const float uv_mod_x = 1.0f / static_cast<float>(ATLAS_SIZE_W);
    const float uv_mod_y = 1.0f / static_cast<float>(ATLAS_SIZE_H);
    const float uv_mod1_x = sizeModX / static_cast<float>(ATLAS_SIZE_W);
    const float uv_mod1_y = sizeModY / static_cast<float>(ATLAS_SIZE_H);
    const float uv_off_x = offsetX / static_cast<float>(ATLAS_SIZE_W);
    const float uv_off_y = offsetY / static_cast<float>(ATLAS_SIZE_H);
    const float uv_x = (texture % ATLAS_SIZE_W) * uv_mod_x;
    const float uv_y = (texture / ATLAS_SIZE_W) * uv_mod_y;
    UVCorners corners = { 0 };
    corners.corner2 = Vector2{uv_x+uv_off_x,uv_y+uv_off_y};
    corners.corner3 = Vector2{uv_x+uv_off_x+uv_mod1_x,uv_y+uv_off_y};
    corners.corner0 = Vector2{uv_x+uv_off_x+uv_mod1_x,uv_y+uv_off_y+uv_mod1_y};
    corners.corner1 = Vector2{uv_x+uv_off_x,uv_y+uv_off_y+uv_mod1_y};
    return corners;
}
inline UVCorners GetUVTex(int w, int h, int offsetX, int offsetY, int sizeX, int sizeY) {
    const float uv_mod_x = 1.0f / static_cast<float>(w);
    const float uv_mod_y = 1.0f / static_cast<float>(h);
    const float uv_x = offsetX * uv_mod_x;
    const float uv_y = offsetY * uv_mod_y;
    const float uvsx = sizeX * uv_mod_x;
    const float uvsy = sizeY * uv_mod_y;
    UVCorners corners = { 0 };
    corners.corner2 = Vector2{uv_x,uv_y};
    corners.corner3 = Vector2{uv_x+uvsx,uv_y};
    corners.corner0 = Vector2{uv_x+uvsx,uv_y+uvsy};
    corners.corner1 = Vector2{uv_x,uv_y+uvsy};
    return corners;
}
inline UVCorners GetUVTexFlipX(int w, int h, int offsetX, int offsetY, int sizeX, int sizeY) {
    const float uv_mod_x = 1.0f / static_cast<float>(w);
    const float uv_mod_y = 1.0f / static_cast<float>(h);
    const float uv_x = offsetX * uv_mod_x;
    const float uv_y = offsetY * uv_mod_y;
    const float uvsx = sizeX * uv_mod_x;
    const float uvsy = sizeY * uv_mod_y;
    UVCorners corners = { 0 };
    corners.corner2 = Vector2{uv_x+uvsx,uv_y};
    corners.corner3 = Vector2{uv_x,uv_y};
    corners.corner0 = Vector2{uv_x,uv_y+uvsy};
    corners.corner1 = Vector2{uv_x+uvsx,uv_y+uvsy};
    return corners;
}
#endif