/*
 * kmscon - Text Renderer
 *
 * Copyright (c) 2012 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:text
 * @short_description: Text Renderer
 * @include: text.h
 *
 * TODO
 */

#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include "shl_log.h"
#include "shl_misc.h"
#include "shl_register.h"
#include "text.h"
#include "uterm_video.h"

#define LOG_SUBSYSTEM "text"

static struct shl_register text_reg = SHL_REGISTER_INIT(text_reg);

static inline void kmscon_text_destroy(void *data)
{
	const struct kmscon_text_ops *ops = data;

	shl_module_unref(ops->owner);
}

/**
 * kmscon_text_register:
 * @ops: Text operations and name for new backend
 *
 * This register a new text backend with operations set to @ops. The name
 * @ops->name must be valid.
 *
 * The first font that is registered automatically becomes the default and
 * fallback. So make sure you register a safe fallback as first backend.
 * If this is unregistered, the next in the list becomes the default
 * and fallback.
 *
 * Returns: 0 on success, negative error code on failure
 */
SHL_EXPORT
int kmscon_text_register(const struct kmscon_text_ops *ops)
{
	int ret;

	if (!ops)
		return -EINVAL;

	log_debug("register text backend %s", ops->name);

	ret = shl_register_add_cb(&text_reg, ops->name, (void *)ops, kmscon_text_destroy);
	if (ret) {
		log_error("cannot register text backend %s: %d", ops->name, ret);
		return ret;
	}

	shl_module_ref(ops->owner);
	return 0;
}

/**
 * kmscon_text_unregister:
 * @name: Name of backend
 *
 * This unregisters the text-backend that is registered with name @name. If
 * @name is not found, nothing is done.
 */
SHL_EXPORT
void kmscon_text_unregister(const char *name)
{
	log_debug("unregister backend %s", name);
	shl_register_remove(&text_reg, name);
}

static int new_text(struct kmscon_text *text, const char *backend, enum Orientation orientation)
{
	struct shl_register_record *record;
	const char *name = backend ? backend : "<default>";
	int ret;

	memset(text, 0, sizeof(*text));
	text->ref = 1;

	if (backend)
		record = shl_register_find(&text_reg, backend);
	else
		record = shl_register_first(&text_reg);

	if (!record) {
		log_error("requested backend '%s' not found", name);
		return -ENOENT;
	}

	text->record = record;
	text->ops = record->data;
	text->orientation = orientation;

	if (text->ops->init)
		ret = text->ops->init(text);
	else
		ret = 0;

	if (ret) {
		log_warning("backend %s cannot create renderer", name);
		shl_register_record_unref(record);
		return ret;
	}

	return 0;
}

/**
 * kmscon_text_new:
 * @out: A pointer to the new text-renderer is stored here
 * @backend: Backend to use or NULL for default backend
 * @rotate: Orientation ("normal", "upside-down", "right" or "left") to use for output
 *
 * Returns: 0 on success, error code on failure
 */
int kmscon_text_new(struct kmscon_text **out, const char *backend, const char *rotate)
{
	struct kmscon_text *text;
	int ret;

	if (!out)
		return -EINVAL;

	text = malloc(sizeof(*text));
	if (!text) {
		log_error("cannot allocate memory for new text-renderer");
		return -ENOMEM;
	}

	text->orientation = OR_NORMAL;

	if (rotate) {
		if (strncmp(rotate, "normal", 6) == 0) {
			text->orientation = OR_NORMAL;
			log_debug("using: orientation: normal");
		} else if (strncmp(rotate, "right", 5) == 0) {
			text->orientation = OR_RIGHT;
			log_debug("using: orientation: right");
		} else if (strncmp(rotate, "upside-down", 8) == 0) {
			text->orientation = OR_UPSIDE_DOWN;
			log_debug("using: orientation: upside-down");
		} else if (strncmp(rotate, "left", 4) == 0) {
			text->orientation = OR_LEFT;
			log_debug("using: orientation: left");
		}
	}

	ret = new_text(text, backend, text->orientation);
	if (ret) {
		if (backend)
			ret = new_text(text, NULL, text->orientation);
		if (ret)
			goto err_free;
	}

	log_debug("using: be: %s", text->ops->name);
	*out = text;
	return 0;

err_free:
	free(text);
	return ret;
}

/**
 * kmscon_text_ref:
 * @text: Valid text-renderer object
 *
 * This increases the reference count of @text by one.
 */
void kmscon_text_ref(struct kmscon_text *text)
{
	if (!text || !text->ref)
		return;

	++text->ref;
}

/**
 * kmscon_text_unref:
 * @text: Valid text-renderer object
 *
 * This decreases the reference count of @text by one. If it drops to zero, the
 * object is freed.
 */
void kmscon_text_unref(struct kmscon_text *text)
{
	if (!text || !text->ref || --text->ref)
		return;

	log_debug("freeing text renderer");
	kmscon_text_unset(text);

	if (text->ops->destroy)
		text->ops->destroy(text);
	shl_register_record_unref(text->record);
	free(text);
}

/**
 * kmscon_text_set:
 * @txt: Valid text-renderer object
 * @font: font object
 * @disp: display object
 *
 * This makes the text-renderer @txt use the font @font and screen @screen. You
 * can drop your reference to both after calling this.
 * This calls kmscon_text_unset() first to remove all previous associations.
 * None of the arguments can be NULL!
 * If this function fails then you must assume that no font/screen will be set
 * and the object is invalid.
 *
 * Returns: 0 on success, negative error code on failure.
 */
int kmscon_text_set(struct kmscon_text *txt, struct kmscon_font *font, struct uterm_display *disp)
{
	int ret;

	if (!txt || !font || !disp)
		return -EINVAL;

	kmscon_text_unset(txt);

	txt->font = font;
	txt->disp = disp;

	if (txt->ops->set) {
		ret = txt->ops->set(txt);
		if (ret) {
			txt->font = NULL;
			txt->disp = NULL;
			return ret;
		}
	}

	kmscon_font_ref(txt->font);
	uterm_display_ref(txt->disp);

	return 0;
}

/**
 * kmscon_text_unset():
 * @txt: text renderer
 *
 * This redos kmscon_text_set() by dropping the internal references to the font
 * and screen and invalidating the object. You need to call kmscon_text_set()
 * again to make use of this text renderer.
 * This is automatically called when the text renderer is destroyed.
 */
void kmscon_text_unset(struct kmscon_text *txt)
{
	if (!txt || !txt->disp || !txt->font)
		return;

	if (txt->ops->unset)
		txt->ops->unset(txt);

	kmscon_font_unref(txt->font);
	uterm_display_unref(txt->disp);
	txt->font = NULL;
	txt->disp = NULL;
	txt->cols = 0;
	txt->rows = 0;
	txt->rendering = false;
}

/**
 * kmscon_text_resize:
 * @txt: valid text renderer
 * @cols: number of columns
 * @rows: number of rows
 *
 * After setting the arguments with kmscon_text_set(), the renderer will compute
 * the number of columns/rows of the console that it can display on the screen.
 * But in case of multi-screen setup the actual size of the terminal might be
 * smaller, so call this function to set the current terminal size.
 */
void kmscon_text_resize(struct kmscon_text *txt, unsigned int cols, unsigned int rows)
{
	if (!txt || !cols || cols > txt->max_cols || !rows || rows > txt->max_rows)
		return;
	if (txt->ops->resize)
		txt->ops->resize(txt, cols, rows);
}

/**
 * kmscon_text_get_cols:
 * @txt: valid text renderer
 *
 * After setting the arguments with kmscon_text_set(), the renderer will compute
 * the number of columns/rows of the console that it can display on the screen.
 * You can retrieve these values via these functions.
 * If kmscon_text_set() hasn't been called, this will return 0.
 *
 * Returns: Number of columns or 0 if @txt is invalid
 */
unsigned int kmscon_text_get_cols(struct kmscon_text *txt)
{
	if (!txt)
		return 0;

	return txt->max_cols;
}

/**
 * kmscon_text_get_rows:
 * @txt: valid text renderer
 *
 * After setting the arguments with kmscon_text_set(), the renderer will compute
 * the number of columns/rows of the console that it can display on the screen.
 * You can retrieve these values via these functions.
 * If kmscon_text_set() hasn't been called, this will return 0.
 *
 * Returns: Number of rows or 0 if @txt is invalid
 */
unsigned int kmscon_text_get_rows(struct kmscon_text *txt)
{
	if (!txt)
		return 0;

	return txt->max_rows;
}

/**
 * kmscon_text_get_orientation:
 * @txt: valid text renderer
 *
 * With a valid @txt passed it, this returns the currently active orientation
 * of the output/screen. Possible values are:
 *
 *   - OR_NORMAL
 *   - OR_RIGHT
 *   - OR_UPSIDE_DOWN
 *   - OR_LEFT
 *
 * Returns: Current orientation enum or OR_NORMAL if @txt is invalid
 */
enum Orientation kmscon_text_get_orientation(struct kmscon_text *txt)
{
	if (!txt)
		return OR_NORMAL;

	return txt->orientation;
}

/**
 * kmscon_text_rotate:
 * @txt: valid text renderer
 * @orientation: enum value representing the desired output-orientation
 *
 * Update the rotation/orientation of the text. It can be one of:
 *
 *   - OR_NORMAL
 *   - OR_RIGHT
 *   - OR_UPSIDE_DOWN
 *   - OR_LEFT
 *
 * Returns: 0 on success, negative error code on failure.
 */
int kmscon_text_rotate(struct kmscon_text *txt, enum Orientation orientation)
{
	if (txt->ops->rotate)
		return txt->ops->rotate(txt, orientation);
	return 0;
}

/**
 * kmscon_text_prepare:
 * @txt: valid text renderer
 * @attr: glyph attributes
 *
 * This starts a rendering-round. When rendering a console via a text renderer,
 * you have to call this first, then render all your glyphs via
 * kmscon_text_draw() and finally use kmscon_text_render(). If you modify this
 * renderer during rendering or if you activate different OpenGL contexts in
 * between, you need to restart rendering by calling kmscon_text_prepare() again
 * and redoing everything from the beginning.
 *
 * Returns: 0 on success, negative error code on failure.
 */
int kmscon_text_prepare(struct kmscon_text *txt, struct tsm_screen_attr *attr)
{
	int ret = 0;

	if (!txt || !txt->font || !txt->disp)
		return -EINVAL;

	txt->rendering = true;
	if (txt->ops->prepare)
		ret = txt->ops->prepare(txt, attr);
	if (ret)
		txt->rendering = false;

	return ret;
}

/**
 * kmscon_text_draw:
 * @txt: valid text renderer
 * @id: a unique ID that identifies @ch globally
 * @ch: ucs4 symbol you want to draw
 * @len: length of @ch or 0 for empty cell
 * @width: cell-width of character
 * @posx: X-position of the glyph
 * @posy: Y-position of the glyph
 * @attr: glyph attributes
 *
 * This draws a single glyph at the requested position. The position is a
 * console position, not a pixel position! You must precede this call with
 * kmscon_text_prepare(). Use this function to feed all glyphs into the
 * rendering pipeline and finally call kmscon_text_render().
 *
 * Returns: 0 on success or negative error code if this glyph couldn't be drawn.
 */
int kmscon_text_draw(struct kmscon_text *txt, uint64_t id, const uint32_t *ch, size_t len,
		     unsigned int width, unsigned int posx, unsigned int posy,
		     const struct tsm_screen_attr *attr)
{
	if (!txt || !txt->rendering)
		return -EINVAL;
	if (posx >= txt->cols || posy >= txt->rows || !attr)
		return -EINVAL;

	return txt->ops->draw(txt, id, ch, len, width, posx, posy, attr);
}

/**
 * kmscon_text_draw_pointer:
 * @txt: valid text renderer
 * @x: X-position of the center of the pointer in pixel
 * @y: Y-position of the center of the pointer in pixel
 *
 * This draws a single I glyph at the requested position. The position is a
 * a pixel position! You must precede this call with kmscon_text_prepare().
 * Use this function to feed the mouse pointer into the rendering pipeline
 * and finally call kmscon_text_render().
 *
 * Returns: 0 on success or negative error code if it couldn't be drawn.
 */
int kmscon_text_draw_pointer(struct kmscon_text *txt, unsigned int x, unsigned int y)
{
	if (!txt || !txt->rendering || !txt->ops->draw_pointer)
		return -EINVAL;

	return txt->ops->draw_pointer(txt, x, y);
}

/**
 * kmscon_text_render:
 * @txt: valid text renderer
 *
 * This does the final rendering round after kmscon_text_prepare() has been
 * called and all glyphs were sent to the renderer via kmscon_text_draw().
 *
 * Returns: 0 on success, negative error on failure.
 */
int kmscon_text_render(struct kmscon_text *txt)
{
	int ret = 0;

	if (!txt || !txt->rendering)
		return -EINVAL;

	if (txt->ops->render)
		ret = txt->ops->render(txt);
	txt->rendering = false;

	return ret;
}

/**
 * kmscon_text_abort:
 * @txt: valid text renderer
 *
 * If you called kmscon_text_prepare() but you want to abort rendering instead
 * of finishing it with kmscon_text_render(), you can safely call this to reset
 * internal state. It is optional to call this or simply restart rendering.
 * Especially if the other renderers return an error, then they probably already
 * aborted rendering and it is not required to call this.
 */
void kmscon_text_abort(struct kmscon_text *txt)
{
	if (!txt || !txt->rendering)
		return;

	if (txt->ops->abort)
		txt->ops->abort(txt);
	txt->rendering = false;
}

int kmscon_text_draw_cb(struct tsm_screen *con, uint64_t id, const uint32_t *ch, size_t len,
			unsigned int width, unsigned int posx, unsigned int posy,
			const struct tsm_screen_attr *attr, tsm_age_t age, void *data)
{
	return kmscon_text_draw(data, id, ch, len, width, posx, posy, attr);
}