// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2016 Google, Inc
 */

#include <common.h>
#include <dm.h>
#include <log.h>
#include <malloc.h>
#include <video.h>
#include <video_console.h>

/* Functions needed by stb_truetype.h */
static int tt_floor(double val)
{
	if (val < 0)
		return (int)(val - 0.999);

	return (int)val;
}

static int tt_ceil(double val)
{
	if (val < 0)
		return (int)val;

	return (int)(val + 0.999);
}

static double frac(double val)
{
	return val - tt_floor(val);
}

static double tt_fabs(double x)
{
	return x < 0 ? -x : x;
}

 /*
  * Simple square root algorithm. This is from:
  * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function
  * Written by Chihung Yu
  * Creative Commons license
  * http://creativecommons.org/licenses/by-sa/3.0/legalcode
  * It has been modified to compile correctly, and for U-Boot style.
  */
static double tt_sqrt(double value)
{
	double lo = 1.0;
	double hi = value;

	while (hi - lo > 0.00001) {
		double mid = lo + (hi - lo) / 2;

		if (mid * mid - value > 0.00001)
			hi = mid;
		else
			lo = mid;
	}

	return lo;
}

static double tt_fmod(double x, double y)
{
	double rem;

	if (y == 0.0)
		return 0.0;
	rem = x - (x / y) * y;

	return rem;
}

/* dummy implementation */
static double tt_pow(double x, double y)
{
	return 0;
}

/* dummy implementation */
static double tt_cos(double val)
{
	return 0;
}

/* dummy implementation */
static double tt_acos(double val)
{
	return 0;
}

#define STBTT_ifloor		tt_floor
#define STBTT_iceil		tt_ceil
#define STBTT_fabs		tt_fabs
#define STBTT_sqrt		tt_sqrt
#define STBTT_pow		tt_pow
#define STBTT_fmod		tt_fmod
#define STBTT_cos		tt_cos
#define STBTT_acos		tt_acos
#define STBTT_malloc(size, u)	((void)(u), malloc(size))
#define STBTT_free(size, u)	((void)(u), free(size))
#define STBTT_assert(x)
#define STBTT_strlen(x)		strlen(x)
#define STBTT_memcpy		memcpy
#define STBTT_memset		memset

#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"

/**
 * struct pos_info - Records a cursor position
 *
 * @xpos_frac:	Fractional X position in pixels (multiplied by VID_FRAC_DIV)
 * @ypos:	Y position (pixels from the top)
 */
struct pos_info {
	int xpos_frac;
	int ypos;
};

/*
 * Allow one for each character on the command line plus one for each newline.
 * This is just an estimate, but it should not be exceeded.
 */
#define POS_HISTORY_SIZE	(CONFIG_SYS_CBSIZE * 11 / 10)

/**
 * struct console_tt_metrics - Information about a font / size combination
 *
 * This caches various font metrics which are expensive to regenerate each time
 * the font size changes. There is one of these for each font / size combination
 * that is being used
 *
 * @font_name:	Name of the font
 * @font_size:	Vertical font size in pixels
 * @font_data:	Pointer to TrueType font file contents
 * @font:	TrueType font information for the current font
 * @baseline:	Pixel offset of the font's baseline from the cursor position.
 *		This is the 'ascent' of the font, scaled to pixel coordinates.
 *		It measures the distance from the baseline to the top of the
 *		font.
 * @scale:	Scale of the font. This is calculated from the pixel height
 *		of the font. It is used by the STB library to generate images
 *		of the correct size.
 */
struct console_tt_metrics {
	const char *font_name;
	int font_size;
	const u8 *font_data;
	stbtt_fontinfo font;
	int baseline;
	double scale;
};

/**
 * struct console_tt_priv - Private data for this driver
 *
 * @cur_met:	Current metrics being used
 * @metrics:	List metrics that can be used
 * @num_metrics:	Number of available metrics
 * @pos:	List of cursor positions for each character written. This is
 *		used to handle backspace. We clear the frame buffer between
 *		the last position and the current position, thus erasing the
 *		last character. We record enough characters to go back to the
 *		start of the current command line.
 * @pos_ptr:	Current position in the position history
 */
struct console_tt_priv {
	struct console_tt_metrics *cur_met;
	struct console_tt_metrics metrics[CONFIG_CONSOLE_TRUETYPE_MAX_METRICS];
	int num_metrics;
	struct pos_info pos[POS_HISTORY_SIZE];
	int pos_ptr;
};

static int console_truetype_set_row(struct udevice *dev, uint row, int clr)
{
	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met = priv->cur_met;
	void *end, *line;
	int ret;

	line = vid_priv->fb + row * met->font_size * vid_priv->line_length;
	end = line + met->font_size * vid_priv->line_length;

	switch (vid_priv->bpix) {
	case VIDEO_BPP8: {
		u8 *dst;

		if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
			for (dst = line; dst < (u8 *)end; ++dst)
				*dst = clr;
		}
		break;
	}
	case VIDEO_BPP16: {
		u16 *dst = line;

		if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
			for (dst = line; dst < (u16 *)end; ++dst)
				*dst = clr;
		}
		break;
	}
	case VIDEO_BPP32: {
		u32 *dst = line;

		if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
			for (dst = line; dst < (u32 *)end; ++dst)
				*dst = clr;
		}
		break;
	}
	default:
		return -ENOSYS;
	}
	ret = vidconsole_sync_copy(dev, line, end);
	if (ret)
		return ret;

	return 0;
}

static int console_truetype_move_rows(struct udevice *dev, uint rowdst,
				     uint rowsrc, uint count)
{
	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met = priv->cur_met;
	void *dst;
	void *src;
	int i, diff, ret;

	dst = vid_priv->fb + rowdst * met->font_size * vid_priv->line_length;
	src = vid_priv->fb + rowsrc * met->font_size * vid_priv->line_length;
	ret = vidconsole_memmove(dev, dst, src, met->font_size *
				 vid_priv->line_length * count);
	if (ret)
		return ret;

	/* Scroll up our position history */
	diff = (rowsrc - rowdst) * met->font_size;
	for (i = 0; i < priv->pos_ptr; i++)
		priv->pos[i].ypos -= diff;

	return 0;
}

static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
				    char ch)
{
	struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
	struct udevice *vid = dev->parent;
	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met = priv->cur_met;
	stbtt_fontinfo *font = &met->font;
	int width, height, xoff, yoff;
	double xpos, x_shift;
	int lsb;
	int width_frac, linenum;
	struct pos_info *pos;
	u8 *bits, *data;
	int advance;
	void *start, *end, *line;
	int row, ret;

	/* First get some basic metrics about this character */
	stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);

	/*
	 * First out our current X position in fractional pixels. If we wrote
	 * a character previously, using kerning to fine-tune the position of
	 * this character */
	xpos = frac(VID_TO_PIXEL((double)x));
	if (vc_priv->last_ch) {
		xpos += met->scale * stbtt_GetCodepointKernAdvance(font,
							vc_priv->last_ch, ch);
	}

	/*
	 * Figure out where the cursor will move to after this character, and
	 * abort if we are out of space on this line. Also calculate the
	 * effective width of this character, which will be our return value:
	 * it dictates how much the cursor will move forward on the line.
	 */
	x_shift = xpos - (double)tt_floor(xpos);
	xpos += advance * met->scale;
	width_frac = (int)VID_TO_POS(advance * met->scale);
	if (x + width_frac >= vc_priv->xsize_frac)
		return -EAGAIN;

	/* Write the current cursor position into history */
	if (priv->pos_ptr < POS_HISTORY_SIZE) {
		pos = &priv->pos[priv->pos_ptr];
		pos->xpos_frac = vc_priv->xcur_frac;
		pos->ypos = vc_priv->ycur;
		priv->pos_ptr++;
	}

	/*
	 * Figure out how much past the start of a pixel we are, and pass this
	 * information into the render, which will return a 8-bit-per-pixel
	 * image of the character. For empty characters, like ' ', data will
	 * return NULL;
	 */
	data = stbtt_GetCodepointBitmapSubpixel(font, met->scale, met->scale,
						x_shift, 0, ch, &width, &height,
						&xoff, &yoff);
	if (!data)
		return width_frac;

	/* Figure out where to write the character in the frame buffer */
	bits = data;
	start = vid_priv->fb + y * vid_priv->line_length +
		VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix);
	linenum = met->baseline + yoff;
	if (linenum > 0)
		start += linenum * vid_priv->line_length;
	line = start;

	/*
	 * Write a row at a time, converting the 8bpp image into the colour
	 * depth of the display. We only expect white-on-black or the reverse
	 * so the code only handles this simple case.
	 */
	for (row = 0; row < height; row++) {
		switch (vid_priv->bpix) {
		case VIDEO_BPP8:
			if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
				u8 *dst = line + xoff;
				int i;

				for (i = 0; i < width; i++) {
					int val = *bits;
					int out;

					if (vid_priv->colour_bg)
						val = 255 - val;
					out = val;
					if (vid_priv->colour_fg)
						*dst++ |= out;
					else
						*dst++ &= out;
					bits++;
				}
				end = dst;
			}
			break;
		case VIDEO_BPP16: {
			uint16_t *dst = (uint16_t *)line + xoff;
			int i;

			if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
				for (i = 0; i < width; i++) {
					int val = *bits;
					int out;

					if (vid_priv->colour_bg)
						val = 255 - val;
					out = val >> 3 |
						(val >> 2) << 5 |
						(val >> 3) << 11;
					if (vid_priv->colour_fg)
						*dst++ |= out;
					else
						*dst++ &= out;
					bits++;
				}
				end = dst;
			}
			break;
		}
		case VIDEO_BPP32: {
			u32 *dst = (u32 *)line + xoff;
			int i;

			if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
				for (i = 0; i < width; i++) {
					int val = *bits;
					int out;

					if (vid_priv->colour_bg)
						val = 255 - val;
					out = val | val << 8 | val << 16;
					if (vid_priv->colour_fg)
						*dst++ |= out;
					else
						*dst++ &= out;
					bits++;
				}
				end = dst;
			}
			break;
		}
		default:
			free(data);
			return -ENOSYS;
		}

		line += vid_priv->line_length;
	}
	ret = vidconsole_sync_copy(dev, start, line);
	if (ret)
		return ret;
	free(data);

	return width_frac;
}

/**
 * console_truetype_backspace() - Handle a backspace operation
 *
 * This clears the previous character so that the console looks as if it had
 * not been entered.
 *
 * @dev:	Device to update
 * Return: 0 if OK, -ENOSYS if not supported
 */
static int console_truetype_backspace(struct udevice *dev)
{
	struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct udevice *vid_dev = dev->parent;
	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
	struct pos_info *pos;
	int xend;

	/*
	 * This indicates a very strange error higher in the stack. The caller
	 * has sent out n character and n + 1 backspaces.
	 */
	if (!priv->pos_ptr)
		return -ENOSYS;

	/* Pop the last cursor position off the stack */
	pos = &priv->pos[--priv->pos_ptr];

	/*
	 * Figure out the end position for clearing. Normally it is the current
	 * cursor position, but if we are clearing a character on the previous
	 * line, we clear from the end of the line.
	 */
	if (pos->ypos == vc_priv->ycur)
		xend = VID_TO_PIXEL(vc_priv->xcur_frac);
	else
		xend = vid_priv->xsize;

	video_fill_part(vid_dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
			xend, pos->ypos + vc_priv->y_charsize,
			vid_priv->colour_bg);

	/* Move the cursor back to where it was when we pushed this record */
	vc_priv->xcur_frac = pos->xpos_frac;
	vc_priv->ycur = pos->ypos;

	return 0;
}

static int console_truetype_entry_start(struct udevice *dev)
{
	struct console_tt_priv *priv = dev_get_priv(dev);

	/* A new input line has start, so clear our history */
	priv->pos_ptr = 0;

	return 0;
}

/*
 * Provides a list of fonts which can be obtained at run-time in U-Boot. These
 * are compiled in by the Makefile.
 *
 * At present there is no mechanism to select a particular font - the first
 * one found is the one that is used. But the build system and the code here
 * supports multiple fonts, which may be useful for certain firmware screens.
 */
struct font_info {
	char *name;
	u8 *begin;
	u8 *end;
};

#define FONT_DECL(_name) \
	extern u8 __ttf_ ## _name ## _begin[]; \
	extern u8 __ttf_ ## _name ## _end[];

#define FONT_ENTRY(_name)		{ \
	.name = #_name, \
	.begin = __ttf_ ## _name ## _begin, \
	.end = __ttf_ ## _name ## _end, \
	}

FONT_DECL(nimbus_sans_l_regular);
FONT_DECL(ankacoder_c75_r);
FONT_DECL(rufscript010);
FONT_DECL(cantoraone_regular);

static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS
	FONT_ENTRY(nimbus_sans_l_regular),
#endif
#ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER
	FONT_ENTRY(ankacoder_c75_r),
#endif
#ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT
	FONT_ENTRY(rufscript010),
#endif
#ifdef CONFIG_CONSOLE_TRUETYPE_CANTORAONE
	FONT_ENTRY(cantoraone_regular),
#endif
	{} /* sentinel */
};

/**
 * font_valid() - Check if a font-table entry is valid
 *
 * Depending on available files in the build system, fonts may end up being
 * empty.
 *
 * @return true if the entry is valid
 */
static inline bool font_valid(struct font_info *tab)
{
	return abs(tab->begin - tab->end) > 4;
}

/**
 * console_truetype_find_font() - Find a suitable font
 *
 * This searches for the first available font.
 *
 * Return: pointer to the font-table entry, or NULL if none is found
 */
static struct font_info *console_truetype_find_font(void)
{
	struct font_info *tab;

	for (tab = font_table; tab->begin; tab++) {
		if (font_valid(tab)) {
			debug("%s: Font '%s', at %p, size %lx\n", __func__,
			      tab->name, tab->begin,
			      (ulong)(tab->end - tab->begin));
			return tab;
		}
	}

	return NULL;
}

int console_truetype_get_font(struct udevice *dev, int seq,
			      struct vidfont_info *info)
{
	struct font_info *tab;
	int i;

	for (i = 0, tab = font_table; tab->begin; tab++, i++) {
		if (i == seq && font_valid(tab)) {
			info->name = tab->name;
			return 0;
		}
	}

	return -ENOENT;
}

/**
 * truetype_add_metrics() - Add a new font/size combination
 *
 * @dev:	Video console device to update
 * @font_name:	Name of font
 * @font_size:	Size of the font (norminal pixel height)
 * @font_data:	Pointer to the font data
 * @return 0 if OK, -EPERM if stbtt failed, -E2BIG if the the metrics table is
 *	full
 */
static int truetype_add_metrics(struct udevice *dev, const char *font_name,
				uint font_size, const void *font_data)
{
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met;
	stbtt_fontinfo *font;
	int ascent;

	if (priv->num_metrics == CONFIG_CONSOLE_TRUETYPE_MAX_METRICS)
		return log_msg_ret("num", -E2BIG);

	met = &priv->metrics[priv->num_metrics];
	met->font_name = font_name;
	met->font_size = font_size;
	met->font_data = font_data;

	font = &met->font;
	if (!stbtt_InitFont(font, font_data, 0)) {
		debug("%s: Font init failed\n", __func__);
		return -EPERM;
	}

	/* Pre-calculate some things we will need regularly */
	met->scale = stbtt_ScaleForPixelHeight(font, font_size);
	stbtt_GetFontVMetrics(font, &ascent, 0, 0);
	met->baseline = (int)(ascent * met->scale);

	return priv->num_metrics++;
}

/**
 * find_metrics() - Find the metrics for a given font and size
 *
 * @dev:	Video console device to update
 * @name:	Name of font
 * @size:	Size of the font (norminal pixel height)
 * @return metrics, if found, else NULL
 */
static struct console_tt_metrics *find_metrics(struct udevice *dev,
					       const char *name, uint size)
{
	struct console_tt_priv *priv = dev_get_priv(dev);
	int i;

	for (i = 0; i < priv->num_metrics; i++) {
		struct console_tt_metrics *met = &priv->metrics[i];

		if (!strcmp(name, met->font_name) && met->font_size == size)
			return met;
	}

	return NULL;
}

static void select_metrics(struct udevice *dev, struct console_tt_metrics *met)
{
	struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct udevice *vid_dev = dev_get_parent(dev);
	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);

	priv->cur_met = met;
	vc_priv->x_charsize = met->font_size;
	vc_priv->y_charsize = met->font_size;
	vc_priv->xstart_frac = VID_TO_POS(2);
	vc_priv->cols = vid_priv->xsize / met->font_size;
	vc_priv->rows = vid_priv->ysize / met->font_size;
	vc_priv->tab_width_frac = VID_TO_POS(met->font_size) * 8 / 2;
}

static int get_metrics(struct udevice *dev, const char *name, uint size,
		       struct console_tt_metrics **metp)
{
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met;
	struct font_info *tab;

	if (name || size) {
		if (!size)
			size = CONFIG_CONSOLE_TRUETYPE_SIZE;
		if (!name)
			name = font_table->name;

		met = find_metrics(dev, name, size);
		if (!met) {
			for (tab = font_table; tab->begin; tab++) {
				if (font_valid(tab) &&
				    !strcmp(name, tab->name)) {
					int ret;

					ret = truetype_add_metrics(dev,
								   tab->name,
								   size,
								   tab->begin);
					if (ret < 0)
						return log_msg_ret("add", ret);

					met = &priv->metrics[ret];
					break;
				}
			}
		}
		if (!met)
			return log_msg_ret("find", -ENOENT);
	} else {
		/* Use the default font */
		met = priv->metrics;
	}

	*metp = met;

	return 0;
}

static int truetype_select_font(struct udevice *dev, const char *name,
				uint size)
{
	struct console_tt_metrics *met;
	int ret;

	ret = get_metrics(dev, name, size, &met);
	if (ret)
		return log_msg_ret("sel", ret);

	select_metrics(dev, met);

	return 0;
}

int truetype_measure(struct udevice *dev, const char *name, uint size,
		     const char *text, struct vidconsole_bbox *bbox)
{
	struct console_tt_metrics *met;
	stbtt_fontinfo *font;
	int lsb, advance;
	const char *s;
	int width;
	int last;
	int ret;

	ret = get_metrics(dev, name, size, &met);
	if (ret)
		return log_msg_ret("sel", ret);

	bbox->valid = false;
	if (!*text)
		return 0;

	font = &met->font;
	width = 0;
	for (last = 0, s = text; *s; s++) {
		int ch = *s;

		/* Used kerning to fine-tune the position of this character */
		if (last)
			width += stbtt_GetCodepointKernAdvance(font, last, ch);

		/* First get some basic metrics about this character */
		stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);

		width += advance;
		last = ch;
	}

	bbox->valid = true;
	bbox->x0 = 0;
	bbox->y0 = 0;
	bbox->x1 = tt_ceil((double)width * met->scale);
	bbox->y1 = met->font_size;

	return 0;
}

const char *console_truetype_get_font_size(struct udevice *dev, uint *sizep)
{
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct console_tt_metrics *met = priv->cur_met;

	*sizep = met->font_size;

	return met->font_name;
}

static int console_truetype_probe(struct udevice *dev)
{
	struct console_tt_priv *priv = dev_get_priv(dev);
	struct udevice *vid_dev = dev->parent;
	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
	struct font_info *tab;
	uint font_size;
	int ret;

	debug("%s: start\n", __func__);
	if (vid_priv->font_size)
		font_size = vid_priv->font_size;
	else
		font_size = CONFIG_CONSOLE_TRUETYPE_SIZE;
	tab = console_truetype_find_font();
	if (!tab) {
		debug("%s: Could not find any fonts\n", __func__);
		return -EBFONT;
	}

	ret = truetype_add_metrics(dev, tab->name, font_size, tab->begin);
	if (ret < 0)
		return log_msg_ret("add", ret);
	priv->cur_met = &priv->metrics[ret];

	select_metrics(dev, &priv->metrics[ret]);

	debug("%s: ready\n", __func__);

	return 0;
}

struct vidconsole_ops console_truetype_ops = {
	.putc_xy	= console_truetype_putc_xy,
	.move_rows	= console_truetype_move_rows,
	.set_row	= console_truetype_set_row,
	.backspace	= console_truetype_backspace,
	.entry_start	= console_truetype_entry_start,
	.get_font	= console_truetype_get_font,
	.get_font_size	= console_truetype_get_font_size,
	.select_font	= truetype_select_font,
	.measure	= truetype_measure,
};

U_BOOT_DRIVER(vidconsole_truetype) = {
	.name	= "vidconsole_tt",
	.id	= UCLASS_VIDEO_CONSOLE,
	.ops	= &console_truetype_ops,
	.probe	= console_truetype_probe,
	.priv_auto	= sizeof(struct console_tt_priv),
};
