/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define C_LUCY_MEMORYPOOL
#include "Lucy/Util/ToolSet.h"

#include "Lucy/Util/MemoryPool.h"

static void
S_init_arena(MemoryPool *self, MemoryPoolIVARS *ivars, size_t amount);

#define DEFAULT_BUF_SIZE 0x100000 // 1 MiB

// Enlarge amount so pointers will always be aligned.
#define INCREASE_TO_WORD_MULTIPLE(_amount) \
    do { \
        const size_t _remainder = _amount % sizeof(void*); \
        if (_remainder) { \
            _amount += sizeof(void*); \
            _amount -= _remainder; \
        } \
    } while (0)

MemoryPool*
MemPool_new(uint32_t arena_size) {
    MemoryPool *self = (MemoryPool*)Class_Make_Obj(MEMORYPOOL);
    return MemPool_init(self, arena_size);
}

MemoryPool*
MemPool_init(MemoryPool *self, uint32_t arena_size) {
    MemoryPoolIVARS *const ivars = MemPool_IVARS(self);
    ivars->arena_size = arena_size == 0 ? DEFAULT_BUF_SIZE : arena_size;
    ivars->arenas     = Vec_new(16);
    ivars->tick       = -1;
    ivars->buf        = NULL;
    ivars->limit      = NULL;
    ivars->consumed   = 0;

    return self;
}

void
MemPool_Destroy_IMP(MemoryPool *self) {
    MemoryPoolIVARS *const ivars = MemPool_IVARS(self);
    DECREF(ivars->arenas);
    SUPER_DESTROY(self, MEMORYPOOL);
}

static void
S_init_arena(MemoryPool *self, MemoryPoolIVARS *ivars, size_t amount) {
    UNUSED_VAR(self);
    ByteBuf *bb;

    // Indicate which arena we're using at present.
    ivars->tick++;

    if (ivars->tick < (int32_t)Vec_Get_Size(ivars->arenas)) {
        // In recycle mode, use previously acquired memory.
        bb = (ByteBuf*)Vec_Fetch(ivars->arenas, (size_t)ivars->tick);
        if (amount >= BB_Get_Size(bb)) {
            BB_Grow(bb, amount);
            BB_Set_Size(bb, amount);
        }
    }
    else {
        // In add mode, get more mem from system.
        size_t buf_size = (amount + 1) > ivars->arena_size
                          ? (amount + 1)
                          : ivars->arena_size;
        char *ptr = (char*)MALLOCATE(buf_size);
        bb = BB_new_steal_bytes(ptr, buf_size - 1, buf_size);
        Vec_Push(ivars->arenas, (Obj*)bb);
    }

    // Recalculate consumption to take into account blocked off space.
    ivars->consumed = 0;
    for (int32_t i = 0; i < ivars->tick; i++) {
        ByteBuf *bb = (ByteBuf*)Vec_Fetch(ivars->arenas, (size_t)i);
        ivars->consumed += BB_Get_Size(bb);
    }

    ivars->buf   = BB_Get_Buf(bb);
    ivars->limit = ivars->buf + BB_Get_Size(bb);
}

size_t
MemPool_Get_Consumed_IMP(MemoryPool *self) {
    return MemPool_IVARS(self)->consumed;
}

void*
MemPool_Grab_IMP(MemoryPool *self, size_t amount) {
    MemoryPoolIVARS *const ivars = MemPool_IVARS(self);
    INCREASE_TO_WORD_MULTIPLE(amount);
    ivars->last_buf = ivars->buf;

    // Verify that we have enough stocked up, otherwise get more.
    ivars->buf += amount;
    if (ivars->buf >= ivars->limit) {
        // Get enough mem from system or die trying.
        S_init_arena(self, ivars, amount);
        ivars->last_buf = ivars->buf;
        ivars->buf += amount;
    }

    // Track bytes we've allocated from this pool.
    ivars->consumed += amount;

    return ivars->last_buf;
}

void
MemPool_Resize_IMP(MemoryPool *self, void *ptr, size_t new_amount) {
    MemoryPoolIVARS *const ivars = MemPool_IVARS(self);
    const size_t last_amount = (size_t)(ivars->buf - ivars->last_buf);
    INCREASE_TO_WORD_MULTIPLE(new_amount);

    if (ptr != ivars->last_buf) {
        THROW(ERR, "Not the last pointer allocated.");
    }
    else {
        if (new_amount <= last_amount) {
            const size_t difference = last_amount - new_amount;
            ivars->buf      -= difference;
            ivars->consumed -= difference;
        }
        else {
            THROW(ERR, "Can't resize to greater amount: %u64 > %u64",
                  (uint64_t)new_amount, (uint64_t)last_amount);
        }
    }
}

void
MemPool_Release_All_IMP(MemoryPool *self) {
    MemoryPoolIVARS *const ivars = MemPool_IVARS(self);
    DECREF(ivars->arenas);
    ivars->arenas   = Vec_new(16);
    ivars->tick     = -1;
    ivars->buf      = NULL;
    ivars->last_buf = NULL;
    ivars->limit    = NULL;
    ivars->consumed = 0;
}

char*
MemPool_Get_Buf_IMP(MemoryPool *self) {
    return MemPool_IVARS(self)->buf;
}

