#ifndef BLOB_H
#define BLOB_H

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// -----------------------------------------------------------------------------
// A simple blob structure that can be in two states:
//   - antestone (mutable): writing is allowed
//   - stone (immutable): reading is allowed
//
// The blob is stored as an array of bits in memory, but for simplicity here,
// we store them in a dynamic byte array with a length and capacity in bits.
// -----------------------------------------------------------------------------

typedef struct blob {
  uint8_t *data;
  // The total number of bits currently in use (the "length" of the blob).
  size_t length;
  size_t capacity;

  // 0 = antestone (mutable)
  // 1 = stone (immutable)
  int is_stone;
} blob;

// Create a new blob with specified bit capacity
blob *blob_new(size_t capacity);

// Create a blob from another blob (copy bits from 'from' to 'to')
blob *blob_new_from_blob(const blob *src, size_t from, size_t to);

// Create a blob with length bits, filled with logical value or random function
blob *blob_new_with_fill(size_t length_bits, int logical_value);

// Destroy blob and free memory
void blob_destroy(blob *b);

// Turn a blob into stone (immutable)
void blob_make_stone(blob *b);

// Write operations (only work on antestone blobs)
int blob_write_bit(blob *b, int bit_val);
int blob_write_blob(blob *b, const blob *src);
int blob_write_dec64(blob *b, double d);
int blob_write_int64(blob *b, int64_t i);
int blob_write_fit(blob *b, int64_t value, int len);
int blob_write_pad(blob *b, int block_size);
int blob_write_text(blob *b, const char *text);

// Read operations (only work on stone blobs)
int blob_read_bit(const blob *b, size_t pos, int *out_bit);
blob *blob_read_blob(const blob *b, size_t from, size_t to);
int blob_read_dec64(const blob *b, size_t from, double *out_value);
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value);
int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value);
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_pad_check(const blob *b, size_t from, int block_size);

// Utility functions
int kim_length_for_fit(int64_t value);

#ifdef BLOB_IMPLEMENTATION

// Helper to ensure capacity for writing
static int blob_ensure_capacity(blob *b, size_t new_bits) {
  size_t need_bits = b->length + new_bits;
  if (need_bits <= b->capacity) return 0;

  // Grow strategy: double until enough
  size_t new_capacity = b->capacity == 0 ? 64 : b->capacity * 2;
  while (new_capacity < need_bits) new_capacity *= 2;

  // Round up to multiple of 8 bits
  if (new_capacity % 8) new_capacity += 8 - (new_capacity % 8);

  size_t new_size_bytes = new_capacity / 8;
  uint8_t *new_ptr = realloc(b->data, new_size_bytes);
  if (!new_ptr) return -1;

  // Zero-fill newly allocated area
  size_t old_size_bytes = b->capacity / 8;
  if (new_size_bytes > old_size_bytes)
    memset(new_ptr + old_size_bytes, 0, new_size_bytes - old_size_bytes);

  b->data = new_ptr;
  b->capacity = new_capacity;
  return 0;
}

// A simple bit-level memcpy (may handle unaligned bits at both ends)
void bitcpy(unsigned char *dst, int dst_bit_offset,
            unsigned char *src, int src_bit_offset, int bit_length)
{
  int dst_byte_offset = dst_bit_offset / 8;
  int src_byte_offset = src_bit_offset / 8;
  int dst_bit = dst_bit_offset % 8;
  int src_bit = src_bit_offset % 8;
  int remaining_bits = bit_length;

  if (dst_bit != 0 || src_bit != 0)
  {
    unsigned char mask = (1 << dst_bit) - 1;
    unsigned char src_mask = (1 << (8 - src_bit)) - 1;
    unsigned char src_byte = src[src_byte_offset] & (src_mask << src_bit);
    dst[dst_byte_offset] = (dst[dst_byte_offset] & ~mask) |
                          (src_byte >> (src_bit - dst_bit));
    remaining_bits -= (8 - dst_bit);
    dst_byte_offset++;
    src_byte_offset += (remaining_bits <= 0) ? 0 : 1;
  }

  int whole_bytes = remaining_bits / 8;
  for (int i = 0; i < whole_bytes; i++)
    dst[dst_byte_offset + i] = src[src_byte_offset + i];
  remaining_bits %= 8;
  dst_byte_offset += whole_bytes;
  src_byte_offset += whole_bytes;

  if (remaining_bits > 0)
  {
    unsigned char mask = (1 << remaining_bits) - 1;
    unsigned char src_byte = src[src_byte_offset] & mask;
    dst[dst_byte_offset] = (dst[dst_byte_offset] & ~mask) | src_byte;
  }
}

// Fast bit-copy for arbitrary bit ranges (inclusive) from src → dest
void copy_bits_fast(const void *src, void *dest,
                    int n, /* start bit in src (inclusive) */
                    int m, /* end bit in src (inclusive) */
                    int x) /* start bit in dest */
{
  const uint8_t *s = (const uint8_t*)src;
  uint8_t       *d = (uint8_t*)dest;

  int total_bits = m - n + 1;
  if (total_bits <= 0) return;

  int src_bit  = n;
  int dst_bit  = x;
  int src_byte = src_bit >> 3;
  int dst_byte = dst_bit >> 3;
  int src_off  = src_bit & 7;
  int dst_off  = dst_bit & 7;

  // 1) Leading partial byte to align dest
  if (dst_off != 0) {
    int chunk = 8 - dst_off;
    if (chunk > total_bits) chunk = total_bits;

    uint8_t dst_mask = (((1u << chunk) - 1u) << dst_off);
    uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
    uint8_t bits = (uint8_t)((wb >> src_off) & ((1u << chunk) - 1u));
    bits <<= dst_off;
    d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);

    total_bits -= chunk;
    src_bit    += chunk;
    dst_bit    += chunk;
    src_byte   = src_bit >> 3;
    dst_byte   = dst_bit >> 3;
    src_off    = src_bit & 7;
    dst_off    = dst_bit & 7; // now zero
  }

  // 2) Copy full bytes
  if (total_bits >= 8) {
    if (src_off == 0) {
      int num_bytes = total_bits >> 3;
      memcpy(&d[dst_byte], &s[src_byte], (size_t)num_bytes);
      total_bits -= num_bytes << 3;
      src_byte   += num_bytes;
      dst_byte   += num_bytes;
    } else {
      int num_bytes = total_bits >> 3;
      for (int i = 0; i < num_bytes; i++) {
        uint16_t wb = (uint16_t)s[src_byte + i] |
                      ((uint16_t)s[src_byte + i + 1] << 8);
        d[dst_byte + i] = (uint8_t)((wb >> src_off) & 0xFFu);
      }
      total_bits -= num_bytes << 3;
      src_byte   += num_bytes;
      dst_byte   += num_bytes;
    }
  }

  // 3) Trailing bits (< 8)
  if (total_bits > 0) {
    uint8_t dst_mask = (1u << total_bits) - 1u;
    uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
    uint8_t bits = (uint8_t)((wb >> src_off) & dst_mask);
    d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
  }
}

// Copy bits [from..to) from src buffer into the blob at its current length
// (i.e. 'to' is exclusive). Advance blob->length by (to - from). Return 0.
static int blob_copy_bits(blob *dest, const void *src, size_t from, size_t to) {
  size_t bits = to - from;
  if (blob_ensure_capacity(dest, bits) < 0) return -1;
  int n = (int)from;
  int m = (int)(to - 1);
  copy_bits_fast(src, dest->data, n, m, (int)dest->length);
  dest->length += bits;
  return 0;
}

blob *blob_new(size_t capacity) {
  if (capacity < 0) capacity = 0;
  blob *b = calloc(1, sizeof(blob));
  if (!b) return NULL;
  if (blob_ensure_capacity(b, capacity) < 0) {
    free(b);
    return NULL;
  }
  return b;
}

blob *blob_new_from_blob(const blob *src, size_t from, size_t to) {
  if (!src) return NULL;
  if (to > src->length) to = src->length;
  if (from >= to) return blob_new(0);

  size_t copy_len = to - from;
  blob *b = blob_new(copy_len);
  if (!b) return NULL;

  b->length = 0; // will be set by blob_copy_bits
  blob_copy_bits(b, src->data, from, to);
  return b;
}

blob *blob_new_with_fill(size_t length_bits, int logical_value) {
  blob *b = blob_new(length_bits);
  if (!b) return NULL;

  b->length = length_bits;
  if (logical_value) {
    size_t bytes = b->capacity / 8;
    memset(b->data, 0xFF, bytes);
    size_t used_bits = length_bits & 7;
    if (used_bits && bytes > 0) {
      uint8_t mask = (1 << used_bits) - 1;
      b->data[bytes - 1] &= mask;
    }
  }
  return b;
}


void blob_destroy(blob *b) {
  if (!b) return;
  if (b->data) {
    free(b->data);
    b->data = NULL;
    b->length = 0;
    b->capacity = 0;
  }
  free(b);
}

void blob_make_stone(blob *b) {
  if (!b) return;
  b->is_stone = 1;
  // shrink buffer to exactly length bits
  if (b->capacity > b->length) {
    size_t size_bytes = (b->length + 7) >> 3;
    if (size_bytes) {
      uint8_t *new_ptr = realloc(b->data, size_bytes);
      if (new_ptr) b->data = new_ptr;
    } else {
      free(b->data);
      b->data = NULL;
    }
    b->capacity = b->length;
  }
}

int blob_write_bit(blob *b, int bit_val) {
  if (!b || b->is_stone) return -1;
  if (blob_ensure_capacity(b, 1) < 0) return -1;

  size_t bit_index = b->length;
  size_t byte_index = bit_index >> 3;
  size_t offset_in_byte = bit_index & 7;

  if (bit_val)
    b->data[byte_index] |= (1 << offset_in_byte);
  else
    b->data[byte_index] &= ~(1 << offset_in_byte);

  b->length++;
  return 0;
}

int blob_write_blob(blob *b, const blob *src) {
  if (!b || !src || b->is_stone) return -1;
  return blob_copy_bits(b, src->data, 0, src->length);
}

int blob_write_dec64(blob *b, double d) {
  if (!b || b->is_stone) return -1;
  if (blob_ensure_capacity(b, 64) < 0) return -1;
  copy_bits_fast(&d, b->data, 0, 64 - 1, (int)b->length);
  b->length += 64;
  return 0;
}

int blob_write_int64(blob *b, int64_t i) {
  if (!b || b->is_stone) return -1;
  if (blob_ensure_capacity(b, 64) < 0) return -1;
  copy_bits_fast(&i, b->data, 0, 64 - 1, (int)b->length);
  b->length += 64;
  return 0;
}

int blob_write_fit(blob *b, int64_t value, int len) {
  if (!b || b->is_stone) return -1;
  if (len < 1 || len > 64) return -1;
  
  // Check if value fits in len bits with sign
  if (len < 64) {
    int64_t max = (1LL << (len - 1)) - 1;
    int64_t min = -(1LL << (len - 1));
    if (value < min || value > max) return -1;
  }
  
  if (blob_ensure_capacity(b, len) < 0) return -1;
  copy_bits_fast(&value, b->data, 0, len - 1, (int)b->length);
  b->length += len;
  return 0;
}

int blob_write_pad(blob *b, int block_size) {
  if (!b || b->is_stone) return -1;
  if (block_size <= 0) return -1;

  if (blob_write_bit(b, 1) < 0) return -1;

  size_t current = b->length;
  size_t rem = current % block_size;
  if (rem > 0) {
    size_t zeros = block_size - rem;
    for (size_t i = 0; i < zeros; i++)
      if (blob_write_bit(b, 0) < 0) return -1;
  }
  return 0;
}

int blob_write_text(blob *b, const char *text) {
  if (!b || !text || b->is_stone) return -1;

  size_t len = strlen(text);
  // Ensure capacity for 64-bit length + len*8 bits of text
  if (blob_ensure_capacity(b, 64 + (len * 8)) < 0) return -1;

  // 1) Write length prefix
  if (blob_write_int64(b, (int64_t)len) < 0) return -1;

  // 2) Write raw text bytes (len * 8 bits)
  if (len > 0) {
    copy_bits_fast(text, b->data, 0, (int)(len * 8 - 1), (int)b->length);
    b->length += (len * 8);
  }
  return 0;
}

int blob_read_bit(const blob *b, size_t pos, int *out_bit) {
  if (!b || !b->is_stone || !out_bit) return -1;
  if (pos >= b->length) return -1;

  size_t byte_index = pos >> 3;
  size_t offset_in_byte = pos & 7;
  *out_bit = (b->data[byte_index] & (1 << offset_in_byte)) ? 1 : 0;
  return 0;
}

blob *blob_read_blob(const blob *b, size_t from, size_t to) {
  if (!b || !b->is_stone) return NULL;
  return blob_new_from_blob(b, from, to);
}

int blob_read_dec64(const blob *b, size_t from, double *out_value) {
  if (!b || !b->is_stone || !out_value) return -1;
  if (from + 64 > b->length) return -1;
  if (from < 0) return -1;
  copy_bits_fast(b->data, out_value, (int)from, (int)(from + 64 - 1), 0);
  return 0;
}

int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value) {
  if (!b || !b->is_stone || !out_value) return -1;
  if (length < 0 || length > 64) return -1;
  if (from + (size_t)length > b->length) return -1;
  copy_bits_fast(b->data, out_value, (int)from, (int)(from + length - 1), 0);
  return 0;
}

int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value) {
  if (!b || !b->is_stone || !out_value) return -1;
  if (len < 1 || len > 64) return -1;
  if (from + (size_t)len > b->length) return -1;
  
  *out_value = 0;
  copy_bits_fast(b->data, out_value, (int)from, (int)(from + len - 1), 0);
  
  // Sign extend if necessary (if the high bit is set and len < 64)
  if (len < 64 && (*out_value & (1LL << (len - 1)))) {
    // Set all bits above len to 1 for negative numbers
    int64_t mask = ~((1LL << len) - 1);
    *out_value |= mask;
  }
  
  return 0;
}

int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) {
  if (!b || !b->is_stone || !out_text || !bits_read) return -1;
  // Need at least 64 bits for length prefix
  if (from + 64 > b->length) return -1;

  int64_t raw_len = 0;
  // Read 64 bits of length
  copy_bits_fast(b->data, &raw_len, (int)from, (int)(from + 64 - 1), 0);
  if (raw_len < 0) return -1;
  size_t len = (size_t)raw_len;

  // Check that there are len bytes following
  size_t after_len = from + 64;
  size_t needed_end = after_len + (len * 8);
  if (needed_end > b->length) return -1;

  // Allocate (len + 1) bytes for C-string
  char *str = malloc(len + 1);
  if (!str) return -1;

  // Copy exactly len*8 bits into str[0..len-1]
  copy_bits_fast(b->data, str, (int)after_len, (int)(after_len + (len * 8) - 1), 0);
  str[len] = '\0';

  *out_text = str;
  *bits_read = 64 + (len * 8);
  return 0;
}

int blob_pad_check(const blob *b, size_t from, int block_size) {
  if (!b || !b->is_stone) return 0;
  if (block_size <= 0) return 0;

  if (b->length % block_size != 0) return 0;
  int64_t diff = (int64_t)b->length - (int64_t)from;
  if (diff <= 0 || diff > block_size) return 0;

  int bit;
  if (blob_read_bit(b, from, &bit) < 0 || bit != 1) return 0;
  for (size_t i = from + 1; i < b->length; i++) {
    if (blob_read_bit(b, i, &bit) < 0 || bit != 0) return 0;
  }
  return 1;
}

#endif /* BLOB_IMPLEMENTATION */

#endif /* BLOB_H */
