#include <stdio.h>
#include <string.h>
#include "cache.h"

/*--------------------------------------------------------- */
size_t lub_heap_cache__get_max_free(lub_heap_cache_t * this)
{
	size_t size = 0;
	/* get the size from the cache */
	lub_heap_cache_bucket_t **ptr;

	for (ptr = this->m_bucket_start; ptr < this->m_bucket_end; ++ptr) {
		lub_blockpool_stats_t stats;
		lub_blockpool__get_stats(&(*ptr)->m_blockpool, &stats);
		if (stats.free_blocks
		    && ((stats.block_size - sizeof(lub_heap_cache_bucket_t *)) >
			size)) {
			size =
			    stats.block_size -
			    sizeof(lub_heap_cache_bucket_t *);
		}
	}
	return size;
}

/*--------------------------------------------------------- */
static lub_heap_cache_bucket_t
    *_lub_heap_cache_find_bucket_from_size(lub_heap_cache_t * this, size_t size)
{
	lub_heap_cache_bucket_t **ptr = this->m_bucket_start;
	/* leave space to put the bucket reference into place */
	size += sizeof(lub_heap_cache_bucket_t *);
	/* place powers of two into the correct bucket */
	--size;
	/* ignore sizes of 1,2 and 4 bytes */
	size >>= 3;
	for (; (ptr < this->m_bucket_end) && size; ++ptr) {
		size >>= 1;
	}
	return (ptr == this->m_bucket_end) ? 0 : *ptr;
}

/*--------------------------------------------------------- */
static size_t lub_heap_cache_num_buckets(lub_heap_align_t max_block_size)
{
	unsigned num_buckets = 0;

	/* count the number of buckets to be created */
	max_block_size >>= 3;	/* ignore buckets of size 1,2 and 4 bytes */
	while (max_block_size) {
		++num_buckets;
		max_block_size >>= 1;
	}
	return num_buckets;
}

/*--------------------------------------------------------- */
size_t
lub_heap_cache_overhead_size(lub_heap_align_t max_block_size,
			     size_t num_max_blocks)
{
	size_t overhead = 0;
	if (num_max_blocks && max_block_size) {
		/* 
		 * account for the cache overheads
		 */
		size_t num_buckets = lub_heap_cache_num_buckets(max_block_size);

		/* cache control block */
		overhead += sizeof(lub_heap_cache_t);

		/* array of bucket pointers (-1 because one is contained in control block) */
		overhead +=
		    sizeof(lub_heap_cache_bucket_t *) * (num_buckets - 1);

		/* fast size lookup array */
		overhead += sizeof(lub_heap_cache_bucket_t *) * max_block_size;

		/* buckets themselves */
		overhead +=
		    num_buckets * offsetof(lub_heap_cache_bucket_t,
					   m_memory_start);
	}
	return overhead;
}

/*--------------------------------------------------------- */
size_t
lub_heap_overhead_size(lub_heap_align_t max_block_size, size_t num_max_blocks)
{
	/* heap control block */
	size_t overhead = sizeof(lub_heap_t);

	/* need at least one free blocks worth */
	overhead += sizeof(lub_heap_free_block_t);

	if (max_block_size && num_max_blocks) {
		size_t num_buckets = lub_heap_cache_num_buckets(max_block_size);
		size_t bucket_size = max_block_size * num_max_blocks;

		/* now add any cache overhead contribution */
		overhead +=
		    lub_heap_cache_overhead_size(max_block_size,
						 num_max_blocks);

		/* add the bucket contents */
		overhead += (num_buckets * bucket_size);
	}
	return overhead;
}

/*--------------------------------------------------------- */
lub_heap_status_t
lub_heap_cache_init(lub_heap_t * this,
		    lub_heap_align_t max_block_size, size_t num_max_blocks)
{
	lub_heap_status_t status = LUB_HEAP_FAILED;
	do {
		size_t bucket_size = (max_block_size * num_max_blocks);
		unsigned num_buckets =
		    lub_heap_cache_num_buckets(max_block_size);
		size_t block_size = 8;
		lub_heap_cache_t *cache;
		lub_heap_cache_bucket_t **ptr;
		int i;
		/* cannot call this multiple times */
		if (this->cache)
			break;

		/* allocate a cache control block */
		cache = this->cache = lub_heap_static_alloc(this,
							    sizeof
							    (lub_heap_cache_t) +
							    sizeof
							    (lub_heap_cache_bucket_t
							     *) * (num_buckets -
								   1));
		if (!cache)
			break;

		cache->m_max_block_size = max_block_size;
		cache->m_num_max_blocks = num_max_blocks;
		cache->m_num_buckets = num_buckets;
		cache->m_bucket_size = bucket_size;
		cache->m_misses = 0;
		cache->m_bucket_end = &cache->m_bucket_start[num_buckets];

		/* allocate each bucket for the cache */
		for (ptr = cache->m_bucket_start;
		     ptr < cache->m_bucket_end; ++ptr) {
			*ptr = lub_heap_static_alloc(this,
						     bucket_size +
						     offsetof
						     (lub_heap_cache_bucket_t,
						      m_memory_start));
			if (!*ptr)
				break;
			/* set up each bucket */
			lub_heap_cache_bucket_init(*ptr,
						   cache,
						   block_size, bucket_size);
			block_size <<= 1;
		}
		if (ptr != cache->m_bucket_end)
			break;

		/* now create a fast lookup for resolving size to bucket */
		cache->m_bucket_lookup =
		    lub_heap_static_alloc(this,
					  sizeof(lub_heap_cache_bucket_t *) *
					  max_block_size);
		if (!cache->m_bucket_lookup)
			break;

		for (i = 0; i < cache->m_max_block_size; ++i) {
			cache->m_bucket_lookup[i] =
			    _lub_heap_cache_find_bucket_from_size(cache, i + 1);
		}
		status = LUB_HEAP_OK;
	} while (0);

	return status;
}

/*--------------------------------------------------------- */
lub_heap_cache_bucket_t
    *lub_heap_cache_find_bucket_from_address(lub_heap_cache_t * this,
					     const void *address)
{
	lub_heap_cache_bucket_t **bucket = (void *)address;

	/* get the bucket reference */
	--bucket;

	if ((*bucket > this->m_bucket_start[0])
	    || (*bucket < this->m_bucket_end[-1])) {
		bucket = 0;
	}
	return bucket ? *bucket : 0;
}

/*--------------------------------------------------------- */
lub_heap_cache_bucket_t *lub_heap_cache_find_bucket_from_size(lub_heap_cache_t *
							      this, size_t size)
{
	lub_heap_cache_bucket_t *bucket = 0;

	/* simply get the result from the fast lookup table */
	if (size > 0) {
		if (size <= this->m_max_block_size) {
			bucket = this->m_bucket_lookup[size - 1];
		} else {
			++this->m_misses;
		}
	}
	return bucket;
}

/*--------------------------------------------------------- */
lub_heap_status_t
lub_heap_cache_realloc(lub_heap_t * this, char **ptr, size_t size)
{
	lub_heap_status_t status = LUB_HEAP_FAILED;
	lub_heap_cache_bucket_t *old_bucket = 0;
	lub_heap_cache_bucket_t *new_bucket =
	    size ? lub_heap_cache_find_bucket_from_size(this->cache, size) : 0;
	char *new_ptr = 0;

	do {
		char *old_ptr = *ptr;
		if (old_ptr) {
			old_bucket =
			    lub_heap_cache_find_bucket_from_address(this->cache,
								    old_ptr);
		} else if (size == 0) {
			/* nothing to do */
			status = LUB_HEAP_OK;
			break;
		}
		if (old_bucket) {
	    /********************************
             * old from CACHE, new from CACHE
             ******************************** */
			if (new_bucket) {
				if (old_bucket == new_bucket) {
					/* keep the existing cache block which is big enough */
					new_ptr = old_ptr;
					status = LUB_HEAP_OK;
					break;
				}
				/* try and get memory from the cache */
				new_ptr =
				    lub_heap_cache_bucket_alloc(new_bucket);
				if (new_ptr) {
					if (old_ptr) {
						/* copy the old details across */
						memcpy(new_ptr, old_ptr, size);

						/* release the old cache block */
						status =
						    lub_heap_cache_bucket_free
						    (old_bucket, old_ptr);
						if (LUB_HEAP_OK != status) {
							break;
						}
					}
					/* all done */
					status = LUB_HEAP_OK;
					break;
				}
			}
	    /********************************
             * old from CACHE, new from HEAP
             ******************************** */
			if (size) {
				/* get new memory from the dynamic heap */
				status =
				    lub_heap_raw_realloc(this, &new_ptr, size,
							 LUB_HEAP_ALIGN_NATIVE);
				if (LUB_HEAP_OK != status) {
					break;
				}
				/* copy the old details across */
				memcpy(new_ptr, old_ptr, size);
			}
			/* release the old cache block */
			status =
			    lub_heap_cache_bucket_free(old_bucket, old_ptr);
		} else {
	    /********************************
             * old from HEAP, new from CACHE
             ******************************** */
			if (size) {
				if (new_bucket) {
					/* try and get memory from the cache */
					new_ptr =
					    lub_heap_cache_bucket_alloc
					    (new_bucket);
					if (new_ptr) {
						if (old_ptr) {
							/* copy the old details across */
							memcpy(new_ptr, old_ptr,
							       size);
							size = 0;
						} else {
							/* all done */
							status = LUB_HEAP_OK;
							break;
						}
					}
				}
			} else if (!old_ptr) {
				/* nothing to do */
				status = LUB_HEAP_OK;
				break;
			}
	    /********************************
             * old from HEAP, new from HEAP
             ******************************** */
			/* release old memory and potentially get new memory */
			status =
			    lub_heap_raw_realloc(this, &old_ptr, size,
						 LUB_HEAP_ALIGN_NATIVE);
			if (LUB_HEAP_OK != status) {
				break;
			} else if (size) {
				new_ptr = old_ptr;
			}
		}
	} while (0);

	if (LUB_HEAP_OK == status) {
		/* set up the new pointer value */
		*ptr = new_ptr;
	}
	return status;
}

/*--------------------------------------------------------- */
void lub_heap_cache_show(lub_heap_cache_t * this)
{
	lub_heap_cache_bucket_t **ptr;

	printf
	    ("       size num blocks    current  high-tide cumulative     misses\n");
	printf
	    (" ---------- ---------- ---------- ---------- ---------- ----------\n");
	for (ptr = this->m_bucket_start; ptr < this->m_bucket_end; ++ptr) {
		lub_blockpool_t *blockpool = &(*ptr)->m_blockpool;
		lub_blockpool_stats_t stats;

		lub_blockpool__get_stats(blockpool, &stats);

		printf(" %10" SIZE_FMT " %10" SIZE_FMT " %10" SIZE_FMT " %10"
		       SIZE_FMT " %10" SIZE_FMT " %10" SIZE_FMT "\n",
		       stats.block_size, stats.num_blocks, stats.alloc_blocks,
		       stats.alloc_hightide_blocks, stats.alloc_total_blocks,
		       stats.alloc_failures);
	}

	printf(" %10u %10s %10s %10s %10s %10" SIZE_FMT "\n",
	       this->m_max_block_size, "-", "-", "-", "-", this->m_misses);
}

/*--------------------------------------------------------- */
void
lub_heap_cache__get_stats(lub_heap_cache_t * this, lub_heap_stats_t * stats)
{
	lub_heap_cache_bucket_t **ptr;

	memset(stats, 0, sizeof(lub_heap_stats_t));
	stats->free_overhead =
	    lub_heap_cache_overhead_size(this->m_max_block_size,
					 this->m_num_max_blocks);

	for (ptr = this->m_bucket_start; ptr < this->m_bucket_end; ++ptr) {
		lub_blockpool_t *blockpool = &(*ptr)->m_blockpool;
		lub_blockpool_stats_t bucket_stats;
		size_t block_size;
		size_t block_overhead = sizeof(lub_heap_cache_bucket_t *);	/* used for fast lookup from address */

		lub_blockpool__get_stats(blockpool, &bucket_stats);
		block_size = (bucket_stats.block_size - block_overhead);

		stats->free_blocks += bucket_stats.free_blocks;
		stats->free_bytes += block_size * bucket_stats.free_blocks;
		stats->free_overhead +=
		    block_overhead * bucket_stats.free_blocks;

		stats->alloc_blocks += bucket_stats.alloc_blocks;
		stats->alloc_bytes += block_size * bucket_stats.alloc_blocks;
		stats->alloc_overhead +=
		    block_overhead * bucket_stats.alloc_blocks;

		stats->alloc_total_blocks += bucket_stats.alloc_total_blocks;
		stats->alloc_total_bytes +=
		    block_size * bucket_stats.alloc_total_blocks;
	}

}

/*--------------------------------------------------------- */