/*
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed 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.
 */
#include <aerospike/as_buffer_pool.h>
#include <citrusleaf/alloc.h>
#include <limits.h>

/******************************************************************************
 * Functions
 *****************************************************************************/

int
as_buffer_pool_init(as_buffer_pool* pool, uint32_t header_size, uint32_t buffer_size)
{
	// Initialize empty queue.
	pool->queue = cf_queue_create(sizeof(void*), true);
	pool->header_size = header_size;
	pool->buffer_size = buffer_size;
	return (pool->queue)? 0 : -1;
}

int
as_buffer_pool_pop(as_buffer_pool* pool, uint32_t size, as_buffer_result* buffer)
{
	size += pool->header_size;
	
	if (size > pool->buffer_size) {
		// Requested size is greater than fixed buffer size.
		// Allocate new buffer, but don't put back into pool.
		buffer->data = cf_malloc(size);
		
		if (! buffer->data) {
			return -1;
		}
		
		buffer->capacity = size - pool->header_size;
		return 2;
	}
	
	// Pop existing buffer from queue.
	int rc = cf_queue_pop(pool->queue, &buffer->data, CF_QUEUE_NOWAIT);
	
	if (rc == CF_QUEUE_OK) {
		buffer->capacity = pool->buffer_size - pool->header_size;
		return 0;
	}
	
	if (rc == CF_QUEUE_EMPTY) {
		// Queue is empty.  Create new buffer.  Queue can grow indefinitely.
		buffer->data = cf_malloc(pool->buffer_size);
		
		if (! buffer->data) {
			return -1;
		}
		
		buffer->capacity = pool->buffer_size - pool->header_size;
		return 1;
	}
	// Queue failure.
	return -2;
}

int
as_buffer_pool_push(as_buffer_pool* pool, void* buffer, uint32_t capacity)
{
	if (capacity + pool->header_size <= pool->buffer_size) {
		// Put buffer back into pool.
		if (cf_queue_push(pool->queue, &buffer) == CF_QUEUE_OK) {
			return 0;
		}
		else {
			cf_free(buffer);
			return -1;
		}
	}
	else {
		// Do not put large buffers back into pool.
		cf_free(buffer);
		return -2;
	}
}

int
as_buffer_pool_push_limit(as_buffer_pool* pool, void* buffer, uint32_t capacity, uint32_t max_buffers)
{
	if (capacity + pool->header_size <= pool->buffer_size) {
		// Put buffer back into pool up to a limit.
		if (cf_queue_push_limit(pool->queue, &buffer, max_buffers)) {
			// Success.
			return 0;
		}
		else {
			cf_free(buffer);
			return -1;
		}
	}
	else {
		// Do not put large buffers back into pool.
		cf_free(buffer);
		return -2;
	}
}

int
as_buffer_pool_drop_buffers(as_buffer_pool* pool, int buffer_count)
{
	// Number of buffers in pool may grow too large if burst of concurrent buffer usage happens.
	// Delete buffer_count buffers from pool.
	void* buffer;
	int count = 0;
	
	while (count < buffer_count) {
		if (cf_queue_pop(pool->queue, &buffer, CF_QUEUE_NOWAIT) == CF_QUEUE_OK) {
			cf_free(buffer);
			count++;
		}
		else {
			break;
		}
	}
	return count;
}

void
as_buffer_pool_destroy(as_buffer_pool* pool)
{
	// Empty and destroy queue.
	void* buffer;
	while (cf_queue_pop(pool->queue, &buffer, CF_QUEUE_NOWAIT) == CF_QUEUE_OK) {
		cf_free(buffer);
	}
	cf_queue_destroy(pool->queue);
}
