/*
 * kmscon - Fixed unifont font
 *
 * Copyright (c) 2012 Ted Kotz <ted@kotz.us>
 * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * SECTION:font_unifont.c
 * @short_description: Fixed unifont font
 * @include: font.h
 *
 * This is a fixed font renderer backend that supports just one font which is
 * statically compiled into the file. This bitmap font has 8x16 and 16x16
 * glyphs. This can statically compile in any font defined as a unifont style
 * hex format. This font is from the GNU unifont project available at:
 *   http://unifoundry.com/unifont.html
 *
 * This file is heavily based on font_8x16.c
 */

#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <libtsm.h>
#include "font.h"
#include "shl_hashtable.h"
#include "shl_log.h"
#include "uterm_video.h"
#include "font_unifont_data.bin.h"

#define LOG_SUBSYSTEM "font_unifont"

/*
 * Glyph data is linked to the binary externally as binary data. The data layout
 * is a size-byte followed by 32 data bytes. The data bytes are padded with 0 if
 * the size is smaller than 32.
 * Sizes bigger than 32 are not used.
 */

struct unifont_data {
	uint8_t len;
	uint8_t data[32];
} __attribute__((__packed__));

/*
 * Global glyph cache
 * The linked binary glyph data cannot be directly passed to the caller as it
 * has the wrong format. Hence, use a glyph-cache with all used glyphs and add
 * new ones as soon as they are used.
 */

static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct shl_hashtable *cache;
static unsigned long cache_refnum;

static void cache_ref(void)
{
	pthread_mutex_lock(&cache_mutex);
	++cache_refnum;
	pthread_mutex_unlock(&cache_mutex);
}

static void cache_unref(void)
{
	pthread_mutex_lock(&cache_mutex);
	if (!--cache_refnum) {
		shl_hashtable_free(cache);
		cache = NULL;
	}
	pthread_mutex_unlock(&cache_mutex);
}

static void free_glyph(void *data)
{
	struct kmscon_glyph *g = data;

	free(g->buf.data);
	free(g);
}

static void unfold(uint8_t *dst, uint8_t val)
{
	*dst = 0xff * !!val;
}

static int find_glyph(uint32_t ch, const struct kmscon_glyph **out)
{
	struct kmscon_glyph *g;
	int ret;
	bool res;
	const struct unifont_data *start, *end, *d;
	unsigned int i, w;

	pthread_mutex_lock(&cache_mutex);

	if (!cache) {
		ret = shl_hashtable_new(&cache, shl_direct_hash,
					shl_direct_equal, free_glyph);
		if (ret) {
			log_error("cannot create unifont hashtable: %d", ret);
			goto out_unlock;
		}
	} else {
		res = shl_hashtable_find(cache, (void**)out, ch);
		if (res) {
			ret = 0;
			goto out_unlock;
		}
	}

	if (ch > 0xffff) {
		ret = -ERANGE;
		goto out_unlock;
	}

	start = (const struct unifont_data*)_binary_font_unifont_data_start;
	end = (const struct unifont_data*)_binary_font_unifont_data_end;
	d = &start[ch];

	if (d >= end) {
		ret = -ERANGE;
		goto out_unlock;
	}

	switch (d->len) {
	case 16:
		w = 1;
		break;
	case 32:
		w = 2;
		break;
	default:
		ret = -EFAULT;
		goto out_unlock;
	}

	g = malloc(sizeof(*g));
	if (!g) {
		ret = -ENOMEM;
		goto out_unlock;
	}
	memset(g, 0, sizeof(*g));
	g->width = w;
	g->buf.width = w * 8;
	g->buf.height = 16;
	g->buf.stride = w * 8;
	g->buf.format = UTERM_FORMAT_GREY;

	g->buf.data = malloc(g->buf.stride * g->buf.height);
	if (!g->buf.data) {
		ret = -ENOMEM;
		goto err_free;
	}

	for (i = 0; i < d->len; ++i) {
		unfold(&g->buf.data[i * 8 + 0], d->data[i] & 0x80);
		unfold(&g->buf.data[i * 8 + 1], d->data[i] & 0x40);
		unfold(&g->buf.data[i * 8 + 2], d->data[i] & 0x20);
		unfold(&g->buf.data[i * 8 + 3], d->data[i] & 0x10);
		unfold(&g->buf.data[i * 8 + 4], d->data[i] & 0x08);
		unfold(&g->buf.data[i * 8 + 5], d->data[i] & 0x04);
		unfold(&g->buf.data[i * 8 + 6], d->data[i] & 0x02);
		unfold(&g->buf.data[i * 8 + 7], d->data[i] & 0x01);
	}

	ret = shl_hashtable_insert(cache, ch, g);
	if (ret) {
		log_error("cannot insert glyph into glyph-cache: %d", ret);
		goto err_data;
	}

	*out = g;
	ret = 0;
	goto out_unlock;

err_data:
	free(g->buf.data);
err_free:
	free(g);
out_unlock:
	pthread_mutex_unlock(&cache_mutex);
	return ret;
}

static int kmscon_font_unifont_init(struct kmscon_font *out,
				    const struct kmscon_font_attr *attr)
{
	static const char name[] = "static-unifont";

	log_debug("loading static unifont font");

	if (_binary_font_unifont_data_size == 0) {
		log_error("unifont glyph information not found in binary");
		return -EFAULT;
	}


	memset(&out->attr, 0, sizeof(out->attr));
	memcpy(out->attr.name, name, sizeof(name));
	out->attr.bold = false;
	out->attr.italic = false;
	out->attr.width = 8;
	out->attr.height = 16;
	kmscon_font_attr_normalize(&out->attr);
	out->baseline = 4;

	cache_ref();
	return 0;
}

static void kmscon_font_unifont_destroy(struct kmscon_font *font)
{
	log_debug("unloading static unifont font");
	cache_unref();
}

static int kmscon_font_unifont_render(struct kmscon_font *font, uint64_t id,
				      const uint32_t *ch, size_t len,
				      const struct kmscon_glyph **out)
{
	if (len > 1)
		return -ERANGE;

	return find_glyph(id & TSM_UCS4_MAX, out);
}

static int kmscon_font_unifont_render_inval(struct kmscon_font *font,
					    const struct kmscon_glyph **out)
{
	return find_glyph(0xfffd, out);
}

static int kmscon_font_unifont_render_empty(struct kmscon_font *font,
					    const struct kmscon_glyph **out)
{
	return find_glyph(' ', out);
}

struct kmscon_font_ops kmscon_font_unifont_ops = {
	.name = "unifont",
	.owner = NULL,
	.init = kmscon_font_unifont_init,
	.destroy = kmscon_font_unifont_destroy,
	.render = kmscon_font_unifont_render,
	.render_empty = kmscon_font_unifont_render_empty,
	.render_inval = kmscon_font_unifont_render_inval,
	.get_overflow = NULL,
};
