#include <libk/bm.h>
#include <libk/std.h>
#include <libk/string.h>

void bm_init (struct bm* bm, uint8_t* base, size_t nbits) {
  bm->base = base;
  bm->nbits = nbits;
  memset (bm->base, 0, (nbits + 7) / 8);
}

/*
 * Set a bit in a bitmap.
 */
void bm_set (struct bm* bm, size_t k) {
  if (k >= bm->nbits)
    return;

  uint8_t* b = (uint8_t*)((uintptr_t)bm->base + (k / 8));
  *b = ((*b) | (1 << (k % 8)));
}

/*
 * Clear a bit in a bitmap.
 */
void bm_clear (struct bm* bm, size_t k) {
  if (k >= bm->nbits)
    return;

  uint8_t* b = (uint8_t*)((uintptr_t)bm->base + (k / 8));
  *b = ((*b) & ~(1 << (k % 8)));
}

/*
 * Test (true/false) a bit in a bitmap.
 */
bool bm_test (struct bm* bm, size_t k) {
  if (k >= bm->nbits)
    return false;

  uint8_t* b = (uint8_t*)((uintptr_t)bm->base + (k / 8));
  return (*b) & (1 << (k % 8));
}

/*
 * Set a range of bits in a bitmap. if starting bit is out of range, we fail.
 */
bool bm_set_region (struct bm* bm, size_t k, size_t m) {
  if (((k + m) > bm->nbits) || (k + m) < k)
    return false;

  for (size_t i = k; i < (k + m); i++) {
    bool taken = bm_test (bm, i);
    if (taken)
      return false;
  }

  for (size_t i = k; i < (k + m); i++)
    bm_set (bm, i);

  return true;
}

/*
 * Clear a range of bits in a bitmap. starting bit must be in range.
 */
void bm_clear_region (struct bm* bm, size_t k, size_t m) {
  if (((k + m) > bm->nbits) || (k + m) < k)
    return;

  for (size_t i = k; i < (k + m); i++)
    bm_clear (bm, i);
}

/*
 * Test a range if bits in a bitmap. Return true if at least one bit
 * is set, else return false (all bits clear). For convenience, if k or m
 * are out of range, act as if the bits are set / bitmap is full - this is
 * useful for implementing the physical memory manager algorithm.
 */
bool bm_test_region (struct bm* bm, size_t k, size_t m) {
  if (((k + m) > bm->nbits) || (k + m) < k)
    return true;

  for (size_t i = k; i < (k + m); i++) {
    bool test = bm_test (bm, i);
    if (test)
      return true;
  }

  return false;
}
