#include "fitz-internal.h"

#define MAX_FONT_SIZE 1000
#define MAX_GLYPH_SIZE 256
#define MAX_CACHE_SIZE (1024*1024)

typedef struct fz_glyph_key_s fz_glyph_key;

struct fz_glyph_cache_s
{
	int refs;
	fz_hash_table *hash;
	int total;
};

struct fz_glyph_key_s
{
	fz_font *font;
	int a, b;
	int c, d;
	unsigned short gid;
	unsigned char e, f;
	int aa;
};

void
fz_new_glyph_cache_context(fz_context *ctx)
{
	fz_glyph_cache *cache;

	cache = fz_malloc_struct(ctx, fz_glyph_cache);
	fz_try(ctx)
	{
		cache->hash = fz_new_hash_table(ctx, 509, sizeof(fz_glyph_key), FZ_LOCK_GLYPHCACHE);
	}
	fz_catch(ctx)
	{
		fz_free(ctx, cache);
		fz_rethrow(ctx);
	}
	cache->total = 0;
	cache->refs = 1;

	ctx->glyph_cache = cache;
}

/* The glyph cache lock is always held when this function is called. */
static void
fz_evict_glyph_cache(fz_context *ctx)
{
	fz_glyph_cache *cache = ctx->glyph_cache;
	fz_glyph_key *key;
	fz_pixmap *pixmap;
	int i;

	for (i = 0; i < fz_hash_len(ctx, cache->hash); i++)
	{
		key = fz_hash_get_key(ctx, cache->hash, i);
		if (key->font)
			fz_drop_font(ctx, key->font);
		pixmap = fz_hash_get_val(ctx, cache->hash, i);
		if (pixmap)
			fz_drop_pixmap(ctx, pixmap);
	}

	cache->total = 0;

	fz_empty_hash(ctx, cache->hash);
}

void
fz_drop_glyph_cache_context(fz_context *ctx)
{
	if (!ctx->glyph_cache)
		return;

	fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
	ctx->glyph_cache->refs--;
	if (ctx->glyph_cache->refs == 0)
	{
		fz_evict_glyph_cache(ctx);
		fz_free_hash(ctx, ctx->glyph_cache->hash);
		fz_free(ctx, ctx->glyph_cache);
		ctx->glyph_cache = NULL;
	}
	fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
}

fz_glyph_cache *
fz_keep_glyph_cache(fz_context *ctx)
{
	fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
	ctx->glyph_cache->refs++;
	fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
	return ctx->glyph_cache;
}

fz_pixmap *
fz_render_stroked_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix trm, fz_matrix ctm, fz_stroke_state *stroke)
{
	if (font->ft_face)
		return fz_render_ft_stroked_glyph(ctx, font, gid, trm, ctm, stroke);
	return fz_render_glyph(ctx, font, gid, trm, NULL);
}

fz_pixmap *
fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix ctm, fz_colorspace *model)
{
	fz_glyph_cache *cache;
	fz_glyph_key key;
	fz_pixmap *val;
	float size = fz_matrix_expansion(ctm);

	cache = ctx->glyph_cache;

	if (size > MAX_FONT_SIZE)
	{
		/* TODO: this case should be handled by rendering glyph as a path fill */
		fz_warn(ctx, "font size too large (%g), not rendering glyph", size);
		return NULL;
	}

	memset(&key, 0, sizeof key);
	key.font = font;
	key.gid = gid;
	key.a = ctm.a * 65536;
	key.b = ctm.b * 65536;
	key.c = ctm.c * 65536;
	key.d = ctm.d * 65536;
	key.e = (ctm.e - floorf(ctm.e)) * 256;
	key.f = (ctm.f - floorf(ctm.f)) * 256;
	key.aa = fz_aa_level(ctx);

	fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
	val = fz_hash_find(ctx, cache->hash, &key);
	if (val)
	{
		fz_keep_pixmap(ctx, val);
		fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
		return val;
	}

	ctm.e = floorf(ctm.e) + key.e / 256.0f;
	ctm.f = floorf(ctm.f) + key.f / 256.0f;

	fz_try(ctx)
	{
		if (font->ft_face)
		{
			val = fz_render_ft_glyph(ctx, font, gid, ctm, key.aa);
		}
		else if (font->t3procs)
		{
			/* We drop the glyphcache here, and execute the t3
			 * glyph code. The danger here is that some other
			 * thread will come along, and want the same glyph
			 * too. If it does, we may both end up rendering
			 * pixmaps. We cope with this later on, by ensuring
			 * that only one gets inserted into the cache. If
			 * we insert ours to find one already there, we
			 * abandon ours, and use the one there already.
			 */
			fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
			val = fz_render_t3_glyph(ctx, font, gid, ctm, model);
			fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
		}
		else
		{
			fz_warn(ctx, "assert: uninitialized font structure");
			val = NULL;
		}
	}
	fz_catch(ctx)
	{
		fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
		fz_rethrow(ctx);
	}

	if (val)
	{
		if (val->w < MAX_GLYPH_SIZE && val->h < MAX_GLYPH_SIZE)
		{
			if (cache->total + val->w * val->h > MAX_CACHE_SIZE)
				fz_evict_glyph_cache(ctx);
			fz_try(ctx)
			{
				fz_pixmap *pix = fz_hash_insert(ctx, cache->hash, &key, val);
				if (pix)
				{
					fz_drop_pixmap(ctx, val);
					val = pix;
				}
				else
					fz_keep_font(ctx, key.font);
				val = fz_keep_pixmap(ctx, val);
			}
			fz_catch(ctx)
			{
				fz_warn(ctx, "Failed to encache glyph - continuing");
			}
			cache->total += val->w * val->h;
		}
	}

	fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
	return val;
}
