/*
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * In addition to the permissions in the GNU General Public License,
 * the authors give you unlimited permission to link the compiled
 * version of this file into combinations with other programs,
 * and to distribute those combinations without any restriction
 * coming from the use of this file.  (The General Public License
 * restrictions do apply in other respects; for example, they cover
 * modification of the file, and distribution when not linked into
 * a combined executable.)
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "common.h"
#include "repository.h"
#include "commit.h"
#include "thread-utils.h"
#include "cache.h"

#define GIT_CACHE_OPENADR 3


void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
{
	size_t i;

	if (size < 8)
		size = 8;

	/* round up size to closest power of 2 */
	size--;
	size |= size >> 1;
	size |= size >> 2;
	size |= size >> 4;
	size |= size >> 8;
	size |= size >> 16;

	cache->size_mask = size;
	cache->lru_count = 0;
	cache->free_obj = free_ptr;

	cache->nodes = git__malloc((size + 1) * sizeof(cache_node));

	for (i = 0; i < (size + 1); ++i) {
		git_mutex_init(&cache->nodes[i].lock);
		cache->nodes[i].ptr = NULL;
		cache->nodes[i].lru = 0;
	}
}

void git_cache_free(git_cache *cache)
{
	size_t i;

	for (i = 0; i < (cache->size_mask + 1); ++i) {
		if (cache->nodes[i].ptr)
			git_cached_obj_decref(cache->nodes[i].ptr, cache->free_obj);

		git_mutex_free(&cache->nodes[i].lock);
	}

	free(cache->nodes);
}

void *git_cache_get(git_cache *cache, const git_oid *oid)
{
	const uint32_t *hash;
	size_t i, pos, found = 0;
	cache_node *node = NULL;

	hash = (const uint32_t *)oid->id;

	for (i = 0; !found && i < GIT_CACHE_OPENADR; ++i) {
		pos = hash[i] & cache->size_mask;
		node = &cache->nodes[pos];

		git_mutex_lock(&node->lock);
		{
			if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) {
				git_cached_obj_incref(node->ptr);
				node->lru = ++cache->lru_count;
				found = 1;
			}
		}
		git_mutex_unlock(&node->lock);
	}


	return found ? node->ptr : NULL;
}

void *git_cache_try_store(git_cache *cache, void *entry)
{
	cache_node *nodes[GIT_CACHE_OPENADR], *lru_node;
	const uint32_t *hash;
	const git_oid *oid;
	size_t i;

	oid = &((git_cached_obj*)entry)->oid;
	hash = (const uint32_t *)oid->id;

	/* increase the refcount on this object, because
	 * the cache now owns it */
	git_cached_obj_incref(entry);

	for (i = 0; i < GIT_CACHE_OPENADR; ++i) {
		size_t pos = hash[i] & cache->size_mask;

		nodes[i] = &cache->nodes[pos];
		git_mutex_lock(&nodes[i]->lock);
	}

	lru_node = nodes[0];

	for (i = 0; i < GIT_CACHE_OPENADR; ++i) {

		if (nodes[i]->ptr == NULL) {
			nodes[i]->ptr = entry;
			nodes[i]->lru = ++cache->lru_count;
			break;
		} else if (git_cached_obj_compare(nodes[i]->ptr, oid) == 0) {
			git_cached_obj_decref(entry, cache->free_obj);
			entry = nodes[i]->ptr;
			nodes[i]->lru = ++cache->lru_count;
			break;
		}

		if (nodes[i]->lru < lru_node->lru)
			lru_node = nodes[i];
	}

	if (i == GIT_CACHE_OPENADR) {
		void *old_entry = lru_node->ptr;
		assert(old_entry);

		git_cached_obj_decref(old_entry, cache->free_obj);
		lru_node->ptr = entry;
		lru_node->lru = ++cache->lru_count;
	}

	/* increase the refcount again, because we are
	 * returning it to the user */
	git_cached_obj_incref(entry);

	for (i = 0; i < GIT_CACHE_OPENADR; ++i)
		git_mutex_unlock(&nodes[i]->lock);

	return entry;
}
