/*
 * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2006 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart, 
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * $COPYRIGHT$
 * 
 * Additional copyrights may follow
 * 
 * $HEADER$
 */

#ifndef OPAL_FREE_LIST_H
#define OPAL_FREE_LIST_H

#include "opal_config.h"

#include "opal/class/opal_list.h"
#include "opal/threads/threads.h"
#include "opal/threads/condition.h"
#include "opal/constants.h"

#if defined(c_plusplus) || defined(__cplusplus)
extern "C" {
#endif

struct opal_free_list_t
{
    opal_list_t super;
    size_t fl_max_to_alloc;
    size_t fl_num_allocated;
    size_t fl_num_per_alloc;
    size_t fl_num_waiting;
    size_t fl_elem_size;
    opal_class_t* fl_elem_class;
    opal_mutex_t fl_lock;
    opal_condition_t fl_condition; 
    opal_list_t fl_allocations;
};
typedef struct opal_free_list_t opal_free_list_t;
OPAL_DECLSPEC OBJ_CLASS_DECLARATION(opal_free_list_t);

struct opal_free_list_item_t
{ 
    opal_list_item_t super; 
}; 
typedef struct opal_free_list_item_t opal_free_list_item_t; 
OPAL_DECLSPEC OBJ_CLASS_DECLARATION(opal_free_list_item_t);

/**
 * Initialize a free list.
 *
 * @param free_list (IN)           Free list.
 * @param element_size (IN)        Size of each element.
 * @param element_class (IN)       opal_class_t of element - used to initialize allocated elements.
 * @param num_elements_to_alloc    Initial number of elements to allocate.
 * @param max_elements_to_alloc    Maximum number of elements to allocate.
 * @param num_elements_per_alloc   Number of elements to grow by per allocation.
 */
 
OPAL_DECLSPEC int opal_free_list_init(
    opal_free_list_t *free_list, 
    size_t element_size,
    opal_class_t* element_class,
    int num_elements_to_alloc,
    int max_elements_to_alloc,
    int num_elements_per_alloc);

OPAL_DECLSPEC int opal_free_list_grow(opal_free_list_t* flist, size_t num_elements) __opal_attribute_nonnull__(1);
    
/**
 * Attemp to obtain an item from a free list. 
 *
 * @param fl (IN)        Free list.
 * @param item (OUT)     Allocated item.
 * @param rc (OUT)       OPAL_SUCCESS or error status on failure.
 *
 * If the requested item is not available the free list is grown to 
 * accomodate the request - unless the max number of allocations has 
 * been reached.  If this is the case - an out of resource error is 
 * returned to the caller.
 */
 
#define OPAL_FREE_LIST_GET(fl, item, rc)                                \
{                                                                       \
    if(opal_using_threads()) {                                          \
        opal_mutex_lock(&((fl)->fl_lock));                              \
        item = (opal_free_list_item_t*)                                 \
            opal_list_remove_first(&((fl)->super));                     \
        if(NULL == item) {                                              \
            opal_free_list_grow((fl), (fl)->fl_num_per_alloc);          \
            item = (opal_free_list_item_t*)                             \
                opal_list_remove_first(&((fl)->super));                 \
        }                                                               \
        opal_mutex_unlock(&((fl)->fl_lock));                            \
    } else {                                                            \
        item = (opal_free_list_item_t*)                                 \
            opal_list_remove_first(&((fl)->super));                     \
        if(NULL == item) {                                              \
            opal_free_list_grow((fl), (fl)->fl_num_per_alloc);          \
            item = (opal_free_list_item_t*)                             \
                opal_list_remove_first(&((fl)->super));                 \
        }                                                               \
    }                                                                   \
    rc = (NULL == item) ?  OPAL_ERR_TEMP_OUT_OF_RESOURCE : OPAL_SUCCESS; \
}

/**
 * Blocking call to obtain an item from a free list.
 *
 * @param fl (IN)        Free list.
 * @param item (OUT)     Allocated item.
 * @param rc (OUT)       OPAL_SUCCESS or error status on failure.
 *
 * If the requested item is not available the free list is grown to 
 * accomodate the request - unless the max number of allocations has 
 * been reached. In this case the caller is blocked until an item
 * is returned to the list.
 */
 

#define OPAL_FREE_LIST_WAIT(fl, item, rc)                                  \
{                                                                          \
    OPAL_THREAD_LOCK(&((fl)->fl_lock));                                    \
    while( NULL == (item = (opal_free_list_item_t*) opal_list_remove_first(&((fl)->super))) ) { \
        if((fl)->fl_max_to_alloc <= (fl)->fl_num_allocated) {              \
            (fl)->fl_num_waiting++;                                        \
            opal_condition_wait(&((fl)->fl_condition), &((fl)->fl_lock));  \
            (fl)->fl_num_waiting--;                                        \
        } else {                                                           \
            opal_free_list_grow((fl), (fl)->fl_num_per_alloc);             \
        }                                                                  \
    }                                                                      \
    OPAL_THREAD_UNLOCK(&((fl)->fl_lock));                                  \
    rc = (NULL == item) ?  OPAL_ERR_OUT_OF_RESOURCE : OPAL_SUCCESS;        \
} 


/**
 * Return an item to a free list. 
 *
 * @param fl (IN)        Free list.
 * @param item (OUT)     Allocated item.
 *
 */
 
#define OPAL_FREE_LIST_RETURN(fl, item)                                    \
{                                                                          \
    OPAL_THREAD_LOCK(&(fl)->fl_lock);                                      \
    opal_list_prepend(&((fl)->super), ((opal_list_item_t*) item));      \
    if((fl)->fl_num_waiting > 0) {                                         \
        opal_condition_signal(&((fl)->fl_condition));                      \
    }                                                                      \
    OPAL_THREAD_UNLOCK(&(fl)->fl_lock);                                    \
}
#if defined(c_plusplus) || defined(__cplusplus)
}
#endif
#endif