/*
 *  opool.c
 *  Chatter
 *
 *  Created by Curtis Jones on 2009.03.26.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include "opool.h"
#include "atomic.h"
#include "logger.h"
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

static inline int opool_addblock (opool_t*);

#pragma mark -
#pragma mark structors

/**
 *
 *
 */
int
opool_init (opool_t *pool, uint64_t osize, uint64_t bsize, uint64_t bmax, char *name)
{
	int error;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	if (unlikely(osize == 0))
		LOG_ERROR_AND_RETURN(-2, "%s.. invalid object size (0)\n", __PRETTY_FUNCTION__);
	
	if (unlikely(bsize == 0))
		LOG_ERROR_AND_RETURN(-3, "%s.. invalid block size (0)\n", __PRETTY_FUNCTION__);
	
	if (unlikely(bmax == 0))
		LOG_ERROR_AND_RETURN(-4, "%s.. invalid max blocks (0)\n", __PRETTY_FUNCTION__);
	
	if (unlikely(name == NULL))
		LOG_ERROR_AND_RETURN(-5, "%s.. null name\n", __PRETTY_FUNCTION__);
	
	if (unlikely(0 != (error = semaphore_init(&pool->semaphore, NULL))))
		LOG_ERROR_AND_RETURN(-101, "%s.. failed to semaphore_init, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	if (unlikely(0 != (error = cobject_init(&pool->cobject, (cobject_destroy_func)opool_destroy, NULL))))
		LOG_ERROR_AND_RETURN(-102, "%s.. failed to cobject_init, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	// copy the name; but don't overflow our name buffer
	{
		uint32_t name_len = strlen(name);
		
		if (name_len >= sizeof(pool->name))
			name_len = sizeof(pool->name) - 1;
		
		memcpy(pool->name, name, name_len);
	}
	
	pool->osize = osize;
	pool->bsize = bsize;
	pool->bmax = bmax;
	pool->bcount = 0;
	pool->ocount = 0;
	pool->inuse = 0;
	
	if (unlikely(0 != (error = opool_addblock(pool))))
		LOG_ERROR_AND_RETURN(-102, "%s.. failed to opool_addblock, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	return 0;
}

/**
 *
 *
 */
int
opool_init2 (opool_t *pool, uint64_t osize, uint64_t bsize, uint64_t bmax, char *name, opool_object_init_func init)
{
	int error;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	if (unlikely(0 != (error = opool_init(pool, osize, bsize, bmax, name))))
		LOG_ERROR_AND_RETURN(-101, "%s.. failed to opool_init, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	pool->init = init;
	
	return 0;
}

/**
 *
 *
 */
int
opool_destroy (opool_t *pool)
{
	int error;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	
	
	// TODO: deallocate all of the blocks
	
	
	
	if (unlikely(0 != (error = semaphore_destroy(&pool->semaphore))))
		LOG_ERROR_AND_RETURN(-101, "%s.. failed to semaphore_destroy, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	if (unlikely(0 != (error = cobject_destroy(&pool->cobject))))
		LOG_ERROR_AND_RETURN(-102, "%s.. failed to cobject_destroy, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
	
	return 0;
}




#pragma mark -
#pragma mark accessors

/**
 *
 *
 */
inline int
opool_push (opool_t *pool, void *object)
{
	opool_item_t *item, *next;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	if (unlikely(object == NULL))
		LOG_ERROR_AND_RETURN(-2, "%s.. null value argument\n", __PRETTY_FUNCTION__);
	
	// move to the correct offset for the start of the item, which is
	// the position of the object less the size of the "next" pointer.
	item = object - sizeof(struct opool_item*);
	
	// push the item onto the stack; if the stack was previously empty,
	// signal to the semaphore that it is no longer empty, in case any
	// threads are waiting for memory to become available.
	while (1) {
		next = item->next = pool->items;
		
		if (ATOMIC_CASPTR_BARRIER(item->next, item, (void**)&pool->items)) {
			if (next == NULL)
				semaphore_post(&pool->semaphore);
			pool->inuse--;
			break;
		}
	}
	
	return 0;
}

/**
 *
 *
 */
inline int
opool_pop (opool_t *pool, void **object)
{
	int error = 0;
	opool_item_t *item, *next;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	if (unlikely(object == NULL))
		LOG_ERROR_AND_RETURN(-2, "%s.. null cobject_t\n", __PRETTY_FUNCTION__);
	
	// clear out the return value
	*object = NULL;
	
	while (1) {
		// get the next item; if it is null, try to add an additional
		// block of items. if that fails, return; otherwise re-loop.
		while (NULL == (item = pool->items))
			if (unlikely(0 != (error = opool_addblock(pool))))
				if (unlikely(0 != (error = semaphore_wait(&pool->semaphore))))
					LOG_ERROR_AND_RETURN(-101, "%s.. failed to semaphore_wait, 0x%08X [%d]\n", __PRETTY_FUNCTION__, error, error);
		
		next = item->next;
		
		if (ATOMIC_CASPTR_BARRIER(item, next, (void**)&pool->items)) {
			pool->inuse++;
			if (pool->inuse > pool->high)
				pool->high = pool->inuse;
			break;
		}
	}
	
	// copy to the return pointer the position of the start of the user
	// object, which starts after the "next" pointer in the opool item.
	*object = (void*)item + sizeof(item->next);
	
	// if the pool was giving an object initialization function pointer
	// then initialize the object we're about to return accordingly.
	if (pool->init != NULL)
		(*pool->init)(pool, *object);
	
	return 0;
}





#pragma mark -
#pragma mark cobject stuff

/**
 *
 *
 */
inline opool_t*
opool_retain (opool_t *opool)
{
	if (unlikely(opool == NULL))
		LOG_ERROR_AND_RETURN(NULL, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	return (opool_t*)cobject_retain(&opool->cobject);
}

/**
 *
 *
 */
inline void
opool_release (opool_t *opool)
{
	if (unlikely(opool == NULL))
		LOG_ERROR_AND_RETURN(, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	cobject_release(&opool->cobject);
}





#pragma mark -
#pragma mark static functions

/**
 *
 *
 */
inline static int
opool_addblock (opool_t *pool)
{
	//printf("%s.. [%s] ocount=%llu, bcount=%llu\n", __PRETTY_FUNCTION__, pool->name, pool->ocount, pool->bcount);
	
	uint64_t i, bcount, bytes;
	opool_block_t *block;
	opool_item_t *item;
	
	if (unlikely(pool == NULL))
		LOG_ERROR_AND_RETURN(-1, "%s.. null opool_t\n", __PRETTY_FUNCTION__);
	
	// make sure that we have not already met (or exceeded) the maximum
	// number of blocks. increment the number of blocks.
	while (1) {
		bcount = pool->bcount;
		
		if (bcount >= pool->bmax)
			LOG_ERROR_AND_RETURN(-101, "%s.. [%s] max blocks allocated (%llu)\n", __PRETTY_FUNCTION__, pool->name, pool->bcount);
		
		if (ATOMIC_CAS64_BARRIER(bcount, bcount+1, (int64_t*)&pool->bcount))
			break;
	}
	
	// figure out how many bytes we're going to allocate. this is equal
	// to the number of bytes necessary for the user-defined object,
	// plus the overhead associated with an opool_item_t (for each 
	// object in the block) plus the overhead associated with the 
	// opool_block_t.
	bytes = sizeof(opool_block_t) + ((pool->osize + sizeof(opool_item_t)) * pool->bsize);
	
	// allocate the block memory
	if (unlikely(NULL == (block = (opool_block_t*)malloc(bytes))))
		LOG_ERROR_AND_RETURN(-102, "%s.. [%s] failed to malloc, %s\n", __PRETTY_FUNCTION__, pool->name, strerror(errno));
	
	// zero out the memory so that we're starting fresh
	memset(block, 0, bytes);
	
	// for each opool_item_t in the block, atomically add it to the
	// list of items in this opool; increment the object count as we go
	// along.
	for (i = 0; i < pool->bsize; ++i) {
		item = ((void*)block + sizeof(block->next)) + ((pool->osize + sizeof(opool_item_t)) * i);
		
		while (1) {
			item->next = pool->items;
			
			if (ATOMIC_CASPTR_BARRIER(item->next, item, (void**)&pool->items))
				break;
		}
		
		ATOMIC_INC64_BARRIER((int64_t*)&pool->ocount);
	}
	
	// add the block to the block list
	while (1) {
		block->next = pool->blocks;
		
		if (ATOMIC_CASPTR_BARRIER(block->next, block, (void**)&pool->blocks))
			break;
	}
	
	return 0;
}
