#include <euler/syscall.hpp>
#include <euler/heap.hpp>

namespace euler {

  static uint64_t *last_chunk_info_page = 0;

  void add_record(uint64_t start, uint64_t length) {
    uint64_t dual_start = start ^ length;
    for (uint64_t *cip = last_chunk_info_page; cip; cip = (uint64_t *)cip[510])
      for (int i = 0; i < 255; ++i)
        if (cip[i * 2] == length && cip[i * 2 + 1] == dual_start) {
          cip[i * 2] = length * 2;
          cip[i * 2 + 1] &= start;
          return;
        }
    for (uint64_t *cip = last_chunk_info_page; cip; cip = (uint64_t *)cip[510])
      for (int i = 0; i < 255; ++i)
        if (cip[i * 2] == 0) {
          cip[i * 2] = length;
          cip[i * 2 + 1] = start;
          return;
        }
    uint64_t *new_cip = (uint64_t *)__euler_get_new_pages(1);
    new_cip[0] = length;
    new_cip[1] = start;
    for (int i = 1; i < 255; ++i)
      new_cip[i * 2] = 0;
    new_cip[510] = (uint64_t)last_chunk_info_page;
    last_chunk_info_page = new_cip;
  }

  void *alloc(uint64_t bytes) {
    for (uint64_t *cip = last_chunk_info_page; cip; cip = (uint64_t *)cip[510])
      for (int i = 0; i < 255; ++i)
        if (cip[i * 2] >= bytes) {
          uint64_t start = cip[i * 2 + 1];
          uint64_t extra_length = cip[i * 2] - bytes;
          cip[i * 2] = 0;
          if (extra_length > 0)
            dealloc((void *)(start + bytes), extra_length);
          return (void *)start;
        }
    uint64_t pages_needed = (bytes - 1) / 4096 + 1;
    uint64_t start = (uint64_t)__euler_get_new_pages(pages_needed);
    if (bytes != pages_needed * 4096)
      dealloc((void *)(start + bytes), pages_needed * 4096 - bytes);
    return (void *)start;
  }

  //this implementation is a little lazy
  void dealloc(void *start, uint64_t bytes) {
    uint64_t s = (uint64_t)start;
    while (bytes > 0) {
      uint64_t l = 1;
      do
        l *= 2;
      while (l <= bytes && s % l == 0);
      l /= 2;
      add_record(s, l);
      s += l;
      bytes -= l;
    }
  }

}
