#include "fitz.h"
#include "mupdf.h"

typedef struct pdf_item_s pdf_item;

struct pdf_item_s
{
	void *dropfunc;
	fz_obj *key;
	void *val;
	int age;
	pdf_item *next;
};

struct refkey
{
	void *dropfunc;
	int num;
	int gen;
};

struct pdf_store_s
{
	fz_hashtable *hash;	/* hash for num/gen keys */
	pdf_item *root;		/* linked list for everything else */
};

pdf_store *
pdf_newstore(void)
{
	pdf_store *store;
	store = fz_malloc(sizeof(pdf_store));
	store->hash = fz_newhash(4096, sizeof(struct refkey));
	store->root = nil;
	return store;
}

void
pdf_storeitem(pdf_store *store, void *keepfunc, void *dropfunc, fz_obj *key, void *val)
{
	pdf_item *item;

	if (!store)
		return;

	item = fz_malloc(sizeof(pdf_item));
	item->dropfunc = dropfunc;
	item->key = fz_keepobj(key);
	item->val = ((void*(*)(void*))keepfunc)(val);
	item->age = 0;
	item->next = nil;

	if (fz_isindirect(key))
	{
		struct refkey refkey;
		pdf_logrsrc("store item (%d %d R) ptr=%p\n", fz_tonum(key), fz_togen(key), val);
		refkey.dropfunc = dropfunc;
		refkey.num = fz_tonum(key);
		refkey.gen = fz_togen(key);
		fz_hashinsert(store->hash, &refkey, item);
	}
	else
	{
		pdf_logrsrc("store item (...) = %p\n", val);
		item->next = store->root;
		store->root = item;
	}
}

void *
pdf_finditem(pdf_store *store, void *dropfunc, fz_obj *key)
{
	struct refkey refkey;
	pdf_item *item;

	if (!store)
		return nil;

	if (key == nil)
		return nil;

	if (fz_isindirect(key))
	{
		refkey.dropfunc = dropfunc;
		refkey.num = fz_tonum(key);
		refkey.gen = fz_togen(key);
		item = fz_hashfind(store->hash, &refkey);
		if (item)
		{
			item->age = 0;
			return item->val;
		}
	}
	else
	{
		for (item = store->root; item; item = item->next)
		{
			if (item->dropfunc == dropfunc && !fz_objcmp(item->key, key))
			{
				item->age = 0;
				return item->val;
			}
		}
	}

	return nil;
}

void
pdf_removeitem(pdf_store *store, void *dropfunc, fz_obj *key)
{
	struct refkey refkey;
	pdf_item *item, *prev, *next;

	if (fz_isindirect(key))
	{
		refkey.dropfunc = dropfunc;
		refkey.num = fz_tonum(key);
		refkey.gen = fz_togen(key);
		item = fz_hashfind(store->hash, &refkey);
		if (item)
		{
			fz_hashremove(store->hash, &refkey);
			((void(*)(void*))item->dropfunc)(item->val);
			fz_dropobj(item->key);
			fz_free(item);
		}
	}
	else
	{
		prev = NULL;
		for (item = store->root; item; item = next)
		{
			next = item->next;
			if (item->dropfunc == dropfunc && !fz_objcmp(item->key, key))
			{
				if (!prev)
					store->root = next;
				else
					prev->next = next;
				((void(*)(void*))item->dropfunc)(item->val);
				fz_dropobj(item->key);
				fz_free(item);
			}
			else
				prev = item;
		}
	}
}

void
pdf_agestore(pdf_store *store, int maxage)
{
	struct refkey *refkey;
	pdf_item *item, *prev, *next;
	int i;

	for (i = 0; i < fz_hashlen(store->hash); i++)
	{
		refkey = fz_hashgetkey(store->hash, i);
		item = fz_hashgetval(store->hash, i);
		if (item && ++item->age > maxage)
		{
			fz_hashremove(store->hash, refkey);
			((void(*)(void*))item->dropfunc)(item->val);
			fz_dropobj(item->key);
			fz_free(item);
			i--; /* items with same hash may move into place */
		}
	}

	prev = NULL;
	for (item = store->root; item; item = next)
	{
		next = item->next;
		if (++item->age > maxage)
		{
			if (!prev)
				store->root = next;
			else
				prev->next = next;
			((void(*)(void*))item->dropfunc)(item->val);
			fz_dropobj(item->key);
			fz_free(item);
		}
		else
			prev = item;
	}
}

void
pdf_freestore(pdf_store *store)
{
	pdf_agestore(store, 0);
	fz_freehash(store->hash);
	fz_free(store);
}

void
pdf_debugstore(pdf_store *store)
{
	pdf_item *item;
	pdf_item *next;
	struct refkey *refkey;
	int i;

	printf("-- resource store contents --\n");

	for (i = 0; i < fz_hashlen(store->hash); i++)
	{
		refkey = fz_hashgetkey(store->hash, i);
		item = fz_hashgetval(store->hash, i);
		if (item)
			printf("store[%d] (%d %d R) = %p\n", i, refkey->num, refkey->gen, item->val);
	}

	for (item = store->root; item; item = next)
	{
		next = item->next;
		printf("store[*] ");
		fz_debugobj(item->key);
		printf(" = %p\n", item->val);
	}
}
