#include <panda/lib/memory.h>
#include <string.h>
using namespace panda::lib;

static const int START_SIZE = 16;

THREAD_LOCAL ObjectAllocator* ObjectAllocator::_thr_instance;
#ifdef _WIN32
THREAD_LOCAL std::vector<MemoryPool*>* MemoryPool::threaded_static_pools;
#else
pthread_key_t ObjectAllocator::_thr_key;
pthread_once_t ObjectAllocator::_thr_once = PTHREAD_ONCE_INIT;
#endif

void MemoryPool::grow () {
    size_t pools_cnt = pools.size();
    if (pools_cnt) {
        pools.resize(pools_cnt+1);
        pools[pools_cnt].size = pools[pools_cnt-1].size*2;
    } else {
        pools.resize(1);
        pools[0].size = START_SIZE;
    }
    Pool* pool = &pools.back();
    pool->len = pool->size * blocksize;
    char* elem = pool->list = new char[pool->len];
    char* end  = elem + pool->len;
    while (elem < end) {
        *((void**)elem) = elem + blocksize; // set next free for each free element
        elem += blocksize;
    }
    *((void**)(elem-blocksize)) = NULL; // last element has no next free
    first_free = pool->list;
}

bool MemoryPool::is_mine (void* elem) {
    Pool* first = &pools.front();
    Pool* pool  = &pools.back();
    while (pool >= first) { // from last to first, because most possibility that elem is in latest pools
        if (elem >= pool->list && elem < pool->list + pool->len) return true;
        pool--;
    }
    return false;
}

MemoryPool::~MemoryPool () {
    if (!pools.size()) return;
    Pool* pool = &pools.front();
    Pool* last = &pools.back();
    while (pool <= last) {
        delete[] pool->list;
        pool++;
    }
}

ObjectAllocator::ObjectAllocator () {
    small_pools  = new MemoryPool*[POOLS_CNT];
    medium_pools = new MemoryPool*[POOLS_CNT];
    big_pools    = new MemoryPool*[POOLS_CNT];
    memset(small_pools,  0, POOLS_CNT*sizeof(MemoryPool*));
    memset(medium_pools, 0, POOLS_CNT*sizeof(MemoryPool*));
    memset(big_pools,    0, POOLS_CNT*sizeof(MemoryPool*));
    small_pools[0] = small_pools[1] = new MemoryPool(8); // min bytes = 8, make 4-byte and 8-byte requests shared
}

ObjectAllocator::~ObjectAllocator () {
    for (int i = 0; i < POOLS_CNT; ++i) {
        if (i) delete small_pools[i];
        delete medium_pools[i];
        delete big_pools[i];
    }
    delete[] small_pools;
    delete[] medium_pools;
    delete[] big_pools;
}

#ifdef _WIN32

void MemoryPool::_thr_instance_add (MemoryPool* pool) {
    if (!threaded_static_pools) threaded_static_pools = new std::vector<MemoryPool*>();
    threaded_static_pools->push_back(pool);
}

void MemoryPool::threaded_instances_free () {
    if (!threaded_static_pools) return;
    size_t size = threaded_static_pools->size();
    for (size_t i = 0; i < size; ++i) delete (*threaded_static_pools)[i];
    delete threaded_static_pools;
    threaded_static_pools = NULL;
}

void ObjectAllocator::_thr_init () {
    _thr_instance = new ObjectAllocator();
}

void ObjectAllocator::threaded_instance_free () {
    if (!_thr_instance) return;
    delete _thr_instance;
    _thr_instance = NULL;
}

#else

void ObjectAllocator::_thr_once_init () {
    int err = pthread_key_create(&_thr_key, _thr_delete);
    if (err) { throw std::logic_error("[ObjectAllocator._thr_once_init]: pthread_key_create error"); }
}

void ObjectAllocator::_thr_init () {
    _thr_instance = new ObjectAllocator();
    pthread_once(&_thr_once, _thr_once_init);
    int err = pthread_setspecific(_thr_key, _thr_instance);
    if (err) throw std::logic_error("[ObjectAllocator.threaded_instance]: pthread_setspecific error");
}

void ObjectAllocator::_thr_delete (void* allocator) {
    delete static_cast<ObjectAllocator*>(allocator);
}

#endif // _WIN32
