/*
 * Copyright (c) 2015,2017 Cray Inc. All rights reserved.
 *
 *  Created on: Apr 16, 2015
 *      Author: jswaro
 */

#include <stdlib.h>
#include <rdma/fi_errno.h>

#include "gnix_bitmap.h"

#ifdef HAVE_ATOMICS

#define __gnix_init_block(block) atomic_init(block, 0)
#define __gnix_set_block(bitmap, index, value) \
	atomic_store(&(bitmap)->arr[(index)], (value))
#define __gnix_load_block(bitmap, index) atomic_load(&(bitmap->arr[(index)]))
#define __gnix_set_bit(bitmap, bit) \
	atomic_fetch_or(&(bitmap)->arr[GNIX_BUCKET_INDEX(bit)], \
			GNIX_BIT_VALUE(bit))
#define __gnix_clear_bit(bitmap, bit) \
	atomic_fetch_and(&(bitmap)->arr[GNIX_BUCKET_INDEX(bit)], \
			~GNIX_BIT_VALUE(bit))
#define __gnix_test_bit(bitmap, bit) \
	((atomic_load(&(bitmap)->arr[GNIX_BUCKET_INDEX(bit)]) \
			& GNIX_BIT_VALUE(bit)) != 0)
#else

static inline void __gnix_init_block(gnix_bitmap_block_t *block)
{
	ofi_spin_init(&block->lock);
	block->val = 0llu;
}

static inline void __gnix_set_block(gnix_bitmap_t *bitmap, int index,
		uint64_t value)
{
	gnix_bitmap_block_t *block = &bitmap->arr[index];

	ofi_spin_lock(&block->lock);
	block->val = value;
	ofi_spin_unlock(&block->lock);
}

static inline uint64_t __gnix_load_block(gnix_bitmap_t *bitmap, int index)
{
	gnix_bitmap_block_t *block = &bitmap->arr[index];
	uint64_t ret;

	ofi_spin_lock(&block->lock);
	ret = block->val;
	ofi_spin_unlock(&block->lock);

	return ret;
}

static inline uint64_t __gnix_set_bit(gnix_bitmap_t *bitmap, int bit)
{
	gnix_bitmap_block_t *block = &bitmap->arr[GNIX_BUCKET_INDEX(bit)];
	uint64_t ret;

	ofi_spin_lock(&block->lock);
	ret = block->val;
	block->val |= GNIX_BIT_VALUE(bit);
	ofi_spin_unlock(&block->lock);

	return ret;
}

static inline uint64_t __gnix_clear_bit(gnix_bitmap_t *bitmap, int bit)
{
	gnix_bitmap_block_t *block = &bitmap->arr[GNIX_BUCKET_INDEX(bit)];
	uint64_t ret;

	ofi_spin_lock(&block->lock);
	ret = block->val;
	block->val &= ~GNIX_BIT_VALUE(bit);
	ofi_spin_unlock(&block->lock);

	return ret;
}

static inline int __gnix_test_bit(gnix_bitmap_t *bitmap, int bit)
{
	gnix_bitmap_block_t *block = &bitmap->arr[GNIX_BUCKET_INDEX(bit)];
	int ret;

	ofi_spin_lock(&block->lock);
	ret = (block->val & GNIX_BIT_VALUE(bit)) != 0;
	ofi_spin_unlock(&block->lock);

	return ret;
}
#endif

int _gnix_test_bit(gnix_bitmap_t *bitmap, uint32_t index)
{
	return __gnix_test_bit(bitmap, index);
}

void _gnix_set_bit(gnix_bitmap_t *bitmap, uint32_t index)
{
	__gnix_set_bit(bitmap, index);
}

void _gnix_clear_bit(gnix_bitmap_t *bitmap, uint32_t index)
{
	__gnix_clear_bit(bitmap, index);
}

int _gnix_test_and_set_bit(gnix_bitmap_t *bitmap, uint32_t index)
{
	return (__gnix_set_bit(bitmap, index) & GNIX_BIT_VALUE(index)) != 0;
}

int _gnix_test_and_clear_bit(gnix_bitmap_t *bitmap, uint32_t index)
{
	return (__gnix_clear_bit(bitmap, index) & GNIX_BIT_VALUE(index)) != 0;
}

int _gnix_bitmap_full(gnix_bitmap_t *bitmap)
{
	return _gnix_find_first_zero_bit(bitmap) == -EAGAIN;
}

int _gnix_bitmap_empty(gnix_bitmap_t *bitmap)
{
	return _gnix_find_first_set_bit(bitmap) == -FI_EAGAIN;
}

int _gnix_find_first_zero_bit(gnix_bitmap_t *bitmap)
{
	int i, pos;
	gnix_bitmap_value_t value;

	for (i = 0, pos = 0;
			i < GNIX_BITMAP_BLOCKS(bitmap->length);
			++i, pos += GNIX_BITMAP_BUCKET_LENGTH) {
		/* invert the bits to check for first zero bit */
		value = ~(__gnix_load_block(bitmap, i));

		if (value != 0) {
			/* no need to check for errors because we have
			   established there is an unset bit */
			pos += ffsll(value) - 1;

			if (pos < bitmap->length)
				return pos;
			else
				return -FI_EAGAIN;
		}
	}

	return -FI_EAGAIN;
}

int _gnix_find_first_set_bit(gnix_bitmap_t *bitmap)
{
	int i, pos;
	gnix_bitmap_value_t value;

	for (i = 0, pos = 0;
			i < GNIX_BITMAP_BLOCKS(bitmap->length);
			++i, pos += GNIX_BITMAP_BUCKET_LENGTH) {
		value = __gnix_load_block(bitmap, i);

		if (value != 0) {
			/* no need to check for errors because we have
			   established there is a set bit */
			pos += ffsll(value) - 1;

			if (pos < bitmap->length)
				return pos;
			else
				return -FI_EAGAIN;		}
	}

	return -FI_EAGAIN;
}

void _gnix_fill_bitmap(gnix_bitmap_t *bitmap, uint64_t value)
{
	int i;
	gnix_bitmap_value_t fill_value = (value != 0) ? ~0 : 0;

	for (i = 0; i < GNIX_BITMAP_BLOCKS(bitmap->length); ++i) {
		__gnix_set_block(bitmap, i, fill_value);
	}
}

int _gnix_alloc_bitmap(gnix_bitmap_t *bitmap, uint32_t nbits, void *addr)
{
	int i;

	if (bitmap->state == GNIX_BITMAP_STATE_READY)
		return -FI_EINVAL;

	if (bitmap->length != 0 || nbits == 0)
		return -FI_EINVAL;

	if (!addr) {
		bitmap->arr = calloc(GNIX_BITMAP_BLOCKS(nbits),
				sizeof(gnix_bitmap_block_t));
		bitmap->internal_buffer_allocation = 1;
	} else {
		bitmap->arr = addr;
		bitmap->internal_buffer_allocation = 0;
	}

	if (!bitmap->arr)
		return -FI_ENOMEM;

	bitmap->length = nbits;

	for (i = 0; i < GNIX_BITMAP_BLOCKS(bitmap->length); ++i)
		__gnix_init_block(&bitmap->arr[i]);

	bitmap->state = GNIX_BITMAP_STATE_READY;

	return 0;
}

int _gnix_realloc_bitmap(gnix_bitmap_t *bitmap, uint32_t nbits)
{
	gnix_bitmap_block_t *new_allocation;
	int blocks_to_allocate = GNIX_BITMAP_BLOCKS(nbits);
	int i;

	if (bitmap->state != GNIX_BITMAP_STATE_READY)
		return -FI_EINVAL;

	if (nbits == 0 || bitmap->arr == NULL)
		return -FI_EINVAL;

	if (!bitmap->internal_buffer_allocation)
		return -FI_EINVAL;

	new_allocation = realloc(bitmap->arr,
			(blocks_to_allocate *
					sizeof(gnix_bitmap_block_t)));

	if (!new_allocation)
		return -FI_ENOMEM;

	bitmap->arr = new_allocation;

	/* Did we increase the size of the bitmap?
	 * If so, initialize new blocks */
	if (blocks_to_allocate > GNIX_BITMAP_BLOCKS(bitmap->length)) {
		for (i = GNIX_BITMAP_BLOCKS(bitmap->length);
				i < blocks_to_allocate;
				++i) {
			__gnix_init_block(&bitmap->arr[i]);
		}
	}

	bitmap->length = nbits;

	return 0;
}

int _gnix_free_bitmap(gnix_bitmap_t *bitmap)
{
	if (bitmap->state != GNIX_BITMAP_STATE_READY)
		return -FI_EINVAL;

	bitmap->length = 0;
	if (bitmap->arr && bitmap->internal_buffer_allocation) {
		free(bitmap->arr);
		bitmap->arr = NULL;
	}

	bitmap->state = GNIX_BITMAP_STATE_FREE;

	return 0;
}
