#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <stdint.h>

#include "spvm_runtime_allocator.h"
#include "spvm_runtime_api.h"
#include "spvm_util_allocator.h"
#include "spvm_memory_pool.h"
#include "spvm_dynamic_array.h"
#include "spvm_runtime.h"
#include "spvm_constant_pool.h"
#include "spvm_api.h"
#include "spvm_object.h"

SPVM_RUNTIME_ALLOCATOR* SPVM_RUNTIME_ALLOCATOR_new(SPVM_RUNTIME* runtime) {
  (void)runtime;
  
  SPVM_RUNTIME_ALLOCATOR* allocator = SPVM_UTIL_ALLOCATOR_safe_malloc_zero(sizeof(SPVM_RUNTIME_ALLOCATOR));

  // use memory pool max reference byte size
  allocator->object_max_byte_size_use_memory_pool = 0xFFFF;
  
  // Memory pool
  allocator->memory_pool = SPVM_MEMORY_POOL_new(allocator->object_max_byte_size_use_memory_pool);
  
  // Free lists
  int64_t allocator_freelists_byte_size = (int64_t)16 * (int64_t)sizeof(SPVM_DYNAMIC_ARRAY);
  allocator->freelists = SPVM_UTIL_ALLOCATOR_safe_malloc_zero(allocator_freelists_byte_size);
  
  // Initialize free list
  {
    int32_t i;
    for (i = 0; i < 16; i++) {
      allocator->freelists[i] = SPVM_DYNAMIC_ARRAY_new(0);
    }
  }
  
  return allocator;
}

int32_t SPVM_RUNTIME_ALLOCATOR_get_freelist_index(SPVM_API* api, SPVM_RUNTIME_ALLOCATOR* allocator, int64_t byte_size) {
  (void)api;
  (void)allocator;
  
  assert(byte_size > 1);
  
  // byte_size = 128;
  
  // To 2 ^ n
  // This algorizm is from http://ideone.com/EStSRd
  int64_t N = byte_size;
  int64_t _N1 = N-1;
  int64_t _N2 = _N1 | (_N1 >>  1);
  int64_t _N3 = _N2 | (_N2 >>  2);
  int64_t _N4 = _N3 | (_N3 >>  4);
  int64_t _N5 = _N4 | (_N4 >>  8);
  int64_t _N6 = _N5 | (_N5 >> 16);
  int64_t _N7 = _N6 | (_N6 >> 32);
  int64_t Value = _N7 + 1;
  
  // Category
  int64_t div_byte_size = Value;
  int32_t index = -1;
  while (1) {
    div_byte_size = div_byte_size / 2;
    if (div_byte_size == 0) {
      break;
    }
    else {
      index++;
    }
  }
  assert(index >= 0);
  
  return index;
}

void* SPVM_RUNTIME_ALLOCATOR_malloc_zero(SPVM_API* api, SPVM_RUNTIME_ALLOCATOR* allocator, int64_t byte_size) {
  SPVM_RUNTIME* runtime = SPVM_RUNTIME_API_get_runtime(api);
  
  assert(byte_size > 0);
  int32_t index = SPVM_RUNTIME_ALLOCATOR_get_freelist_index(api, allocator, byte_size);
  int32_t alloc_byte_size = pow(2, index + 1);
  
  void* block;
  if (alloc_byte_size > allocator->object_max_byte_size_use_memory_pool) {
    block = SPVM_UTIL_ALLOCATOR_safe_malloc_zero(alloc_byte_size);
  }
  else {
    void* free_address = SPVM_DYNAMIC_ARRAY_pop(allocator->freelists[index]);
    if (free_address) {
      block = free_address;
    }
    else {
      block = SPVM_MEMORY_POOL_alloc(allocator->memory_pool, alloc_byte_size);
    }
    memset(block, 0, alloc_byte_size);
  }
  
  assert(block);
  
  runtime->objects_count++;
  
#ifdef DEBUG
  fprintf(stderr, "MALLOC OBJECT COUNT %d\n", runtime->objects_count);
#endif
  
  // Address first bit must be 0 for weaken reference
  assert(((intptr_t)block & 1) == 0);
  
  return block;
}

void SPVM_RUNTIME_ALLOCATOR_free_object(SPVM_API* api, SPVM_RUNTIME_ALLOCATOR* allocator, SPVM_OBJECT* object) {
  SPVM_RUNTIME* runtime = SPVM_RUNTIME_API_get_runtime(api);
  
  if (object == NULL) {
    return;
  }
  else {
    // Byte size
    int32_t byte_size = SPVM_RUNTIME_API_calcurate_object_byte_size(api, object);
    
    assert(byte_size > 0);
    
    runtime->objects_count--;
    assert(runtime->objects_count >= 0);
    
#ifdef DEBUG
    fprintf(stderr, "FREE OBJECT COUNT %d\n", runtime->objects_count);
#endif
    
    if (byte_size > allocator->object_max_byte_size_use_memory_pool) {
      free(object);
    }
    else {
      // Freelist index
      int32_t freelist_index = SPVM_RUNTIME_ALLOCATOR_get_freelist_index(api, allocator, byte_size);
      
      // Push free address
      SPVM_DYNAMIC_ARRAY_push(allocator->freelists[freelist_index], object);
    }
  }
}

void SPVM_RUNTIME_ALLOCATOR_free(SPVM_RUNTIME* runtime, SPVM_RUNTIME_ALLOCATOR* allocator) {
  (void)runtime;
  
  // Free memory pool */
  SPVM_MEMORY_POOL_free(allocator->memory_pool);
  
  free(allocator->freelists);
  
  free(allocator);
}
