/*
 * Copyright (c) 2016 ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

#include <aos/aos.h>
#include "region_pool.h"
#include "dqi_debug.h"

#define INIT_POOL_SIZE 16

STATIC_ASSERT((INIT_POOL_SIZE & (INIT_POOL_SIZE - 1)) == 0, "must be a power of two");

struct region_pool {

    // IDs are inserted and may have to increase size at some point
    uint16_t size;
    // number of regions in pool
    uint16_t num_regions;

    // random offset where regions ids start from
    uint64_t region_offset;
    
    // if we have to serach for a slot, need an offset
    uint16_t last_offset;

    //region_alloc
    struct slab_allocator region_alloc;

    // structure to store regions
    struct region** pool;
};


#define INIT_SIZE 128
struct buffer {
    bufferid_t id;
    struct buffer* next;
};

struct region {
    // ID of the region
    regionid_t id;
    // Base address of the region
    lpaddr_t base_addr;
    // Capability of the region
    struct capref* cap;
    // Lenght of the memory region
    size_t len;
};


/**
 * @brief initialized a region from which only fixed size buffers are used
 *
 * @param region                Return pointer to the region
 * @param region_id             The ID of the region,
 * @param cap                   Capability of the memory region
 *
 * @returns error on failure or SYS_ERR_OK on success
 */

/*
static errval_t region_init(struct region* region,
                             regionid_t region_id,
                             lpaddr_t base_addr,
                             size_t len,
                             struct capref* cap)
{
    if (region == NULL) {
        return LIB_ERR_MALLOC_FAIL;
    }

    region->id = region_id;
    region->cap = cap;
    regoin->base_addr = base_addr;
    regon->len = len;
    
    DQI_DEBUG_REGION("Initialize Region size=%ld addr=%16lx\n",
              region->len, region->base_addr);

    return SYS_ERR_OK;
}
*/

/**
 * @brief free up a region
 *
 * @param region                The region to free up
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
/*
static errval_t region_destroy(struct region* region)
{
    free(region);
    return SYS_ERR_OK;
}
*/


/**
 * @brief initialized a pool of regions
 *
 * @param pool          Return pointer to the region pool
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
errval_t region_pool_init(struct region_pool** pool)
{
    // Allocate pool struct itself including pointers to region
    (*pool) = calloc(1, sizeof(struct region_pool));
    if (*pool == NULL) {
        DQI_DEBUG_REGION("Allocationg inital pool failed \n");
        return LIB_ERR_MALLOC_FAIL;
    }

    (*pool)->num_regions = 0;

    srand(rdtsc());

    // Initialize region id offset
    (*pool)->region_offset = (rand() >> 12) ;
    (*pool)->size = INIT_POOL_SIZE;

    (*pool)->pool = calloc(INIT_POOL_SIZE, sizeof(struct region*));
    if ((*pool)->pool == NULL) {
        free(*pool);
        DQI_DEBUG_REGION("Allocationg inital pool failed \n");
        return LIB_ERR_MALLOC_FAIL;
    }


    slab_init(&(*pool)->region_alloc, sizeof(struct region),
              slab_default_refill);

    DQI_DEBUG_REGION("Init region pool size=%d addr=%p\n", INIT_POOL_SIZE, *pool);
    return SYS_ERR_OK;
}

/**

 * @brief freeing region pool
 *
 * @param pool          The region pool to free
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
errval_t region_pool_destroy(struct region_pool* pool)
{
    errval_t err;
    struct capref cap;
    // Check if there are any regions left
    if (pool->num_regions == 0) {
        free(pool->pool);
        free(pool);
        return SYS_ERR_OK;
    } else {
        // There are regions left -> remove them
        for (int i = 0; i < pool->size; i++) {
            if ((void*) pool->pool[i] != NULL) {
                err = region_pool_remove_region(pool, pool->pool[i]->id,
                                                &cap);
                if (err_is_fail(err)){
                    printf("Region pool has regions that are still used,"
                           " can not free them \n");
                    return err;
                }
            }
        }
        free(pool->pool);
        free(pool);
    }
   
    return SYS_ERR_OK;
}

/**
 * @brief increase the region pool size by a factor of 2
 *
 * @param pool       the regin pool that has not enough region slots
 *
 * @returns error on failure or SYS_ERR_OK on success
 */


static errval_t region_pool_grow(struct region_pool* pool)
{
    struct region** tmp;

    uint16_t new_size = (pool->size)*2;
    // Allocate new pool twice the size
    tmp = calloc(new_size, sizeof(struct region*));
    if (tmp == NULL) {
        DQI_DEBUG_REGION("Allocationg larger pool failed \n");
        return LIB_ERR_MALLOC_FAIL;
    }

    // Copy all the pointers
    for (int i = 0; i < new_size; i++) {
        tmp[i] = NULL;
    }

    struct region* region;
    for (int i = 0; i < pool->size; i++) {
        region = pool->pool[i];
        uint16_t index =  region->id & (new_size - 1);
        tmp[index] = pool->pool[i];
    }

    free(pool->pool);

    pool->pool = tmp;
    pool->size = new_size;
    pool->last_offset = 0;

    return SYS_ERR_OK;
}

/**
 * @brief add a memory region to the region pool
 *
 * @param pool          The pool to add the region to
 * @param cap           The cap of the region
 * @param region_id     Return pointer to the region id
 *                      that is assigned by the pool
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
errval_t region_pool_add_region(struct region_pool* pool,
                                struct capref cap,
                                regionid_t* region_id)
{
    errval_t err;
    struct region* region;
    struct frame_identity id;

    err = frame_identify(cap, &id);
    if (err_is_fail(err)) {
        return err;
    }

    // for now just loop over all entries
    for (int i = 0; i < pool->size; i++) {
        struct region* tmp;
        tmp = pool->pool[i];
   
        if (tmp == NULL) {
            continue;
        }

        // check if region is already registered
        if (tmp->base_addr == id.base) {
            return DEVQ_ERR_INVALID_REGION_ARGS;
        }

        /* if region if entierly before other region or
           entierly after region, otherwise there is an overlap
         */
        if (!((id.base + id.bytes <= tmp->base_addr) ||
            (tmp->base_addr + tmp->len <= id.base))) {
            return DEVQ_ERR_INVALID_REGION_ARGS;
        }

    }

    // Check if pool size is large enough
    if (!(pool->num_regions < pool->size)) {
        DQI_DEBUG_REGION("Increasing pool size to %d \n", pool->size*2);
        err = region_pool_grow(pool);
        if (err_is_fail(err)) {
            DQI_DEBUG_REGION("Increasing pool size failed\n");
            return err;
        }
    }

    pool->num_regions++;
    uint16_t offset = pool->last_offset;
    uint16_t index = 0;

    // find slot
    while (true) {
        index = (pool->region_offset + pool->num_regions + offset) & (pool->size - 1);
        DQI_DEBUG_REGION("Trying insert index %d \n", index);
        if (pool->pool[index] == NULL) {
           break;
        } else {
            offset++;
        }
    }

    pool->last_offset = offset;

    region = slab_alloc(&pool->region_alloc);
    if (region == NULL) {
        return LIB_ERR_MALLOC_FAIL;
    }

    region->id = pool->region_offset + pool->num_regions + offset;
    region->cap = &cap;
    region->base_addr = id.base;
    region->len = id.bytes;

    // insert into pool
    pool->pool[region->id & (pool->size - 1)] = region;
    *region_id = region->id;
    DQI_DEBUG_REGION("Inserting region into pool at %d \n", region->id % pool->size);
    return err;
}

/**
 * @brief add a memory region to the region pool using an already
 *        existing id
 *
 * @param pool          The pool to add the region to
 * @param cap           The cap of the region
 * @param region_id     The region id to add to the pool
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
errval_t region_pool_add_region_with_id(struct region_pool* pool,
                                        struct capref cap,
                                        regionid_t region_id)
{
    errval_t err;
    // Check if pool size is large enough
    if (!(pool->num_regions < pool->size)) {
        DQI_DEBUG_REGION("Increasing pool size to %d \n", pool->size*2);
        err = region_pool_grow(pool);
        if (err_is_fail(err)) {
            DQI_DEBUG_REGION("Increasing pool size failed\n");
            return err;
        }
    }

    struct region* region = pool->pool[region_id & (pool->size - 1)];
    if (region != NULL) {
        return DEVQ_ERR_INVALID_REGION_ID;
    } else {
        struct frame_identity id;

        err = frame_identify(cap, &id);
        if (err_is_fail(err)) {
            return err;
        }

        region = slab_alloc(&pool->region_alloc);
        if (region == NULL) {
            return LIB_ERR_MALLOC_FAIL;
        }

        region->id = region_id;
        region->cap = &cap;
        region->base_addr = id.base;
        region->len = id.bytes;

        pool->pool[region_id & (pool->size - 1)] = region;
    }

    pool->num_regions++;
    return SYS_ERR_OK;
}

/**
 * @brief remove a memory region from the region pool
 *
 * @param pool          The pool to remove the region from
 * @param region_id     The id of the region to remove
 * @param cap           Return pointer to the cap of the removed region
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
errval_t region_pool_remove_region(struct region_pool* pool,
                                   regionid_t region_id,
                                   struct capref* cap)
{
    //errval_t err;
    struct region* region;
    region = pool->pool[region_id & (pool->size - 1)];
    if (region == NULL) {
        return DEVQ_ERR_INVALID_REGION_ID;
    }

    cap = region->cap;
  
    slab_free(&pool->region_alloc, region);
    /*
    err = region_destroy(region);
    if (err_is_fail(err)) {
        DQI_DEBUG_REGION("Failed to destroy region, some buffers might still be in use \n");
        return err;
    }
    */
    DQI_DEBUG_REGION("Removing slot %d \n", region_id % pool->size);
    pool->pool[region_id & (pool->size - 1)] = NULL;

    pool->num_regions--;
    return SYS_ERR_OK;
}


/**
 * @brief get memory region from pool
 *
 * @param pool          The pool to get the region from
 * @param region_id     The id of the region to get
 * @param region        Return pointer to the region
 *
 * @returns error on failure or SYS_ERR_OK on success
 */
/*
static errval_t region_pool_get_region(struct region_pool* pool,
                                       regionid_t region_id,
                                       struct region** region)
{
    *region = pool->pool[region_id % pool->size];
    if (*region == NULL) {
        return DEVQ_ERR_INVALID_REGION_ID;
    }

    return SYS_ERR_OK;
}
*/

/**
 * @brief check if buffer is valid
 *
 * @param pool          The pool to get the region from
 * @param region_id     The id of the region
 * @param offset        offset into the region
 * @param length        length of the buffer
 * @param valid_data    offset into the buffer
 * @param valid_length  length of the valid_data
 *
 * @returns true if the buffer is valid otherwise false
 */
bool region_pool_buffer_check_bounds(struct region_pool* pool,
                                     regionid_t region_id,
                                     genoffset_t offset,
                                     genoffset_t length,
                                     genoffset_t valid_data,
                                     genoffset_t valid_length)
{
    struct region* region;
    region = pool->pool[region_id & (pool->size - 1)];
    if (region == NULL) {
        return false;
    }

    // check validity of buffer within region
    // and check validity of valid data values
    if ((length + offset > region->len) ||
         (valid_data + valid_length > length)) {
        return false;
    }

    return true;
}
