/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - sch-rnd (executable)
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

/* Font engine: librnd font */

#include <ctype.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <librnd/font2/font.h>
#include <librnd/hid/hid_inlines.h>
#include <librnd/core/rotate.h>
#include <librnd/core/safe_fs_dir.h>

#define FONTMAP_MAX_DEPTH 128

static htsp_t rf_map;
static rnd_font_t *embf;
static rnd_font_t loadme; /* special value: font is not yet loaded */
static int embf_parsed;

#include "font_internal.c"

static int iolht_error(lht_node_t *nd, char *fmt, ...)
{
	gds_t str;
	va_list ap;

	gds_init(&str);
	gds_append_str(&str, "font lihata parse error");
	if (nd != NULL)
		rnd_append_printf(&str, " at %s:%d.%d: ", nd->file_name, nd->line, nd->col);
	else
		gds_append_str(&str, ": ");

	va_start(ap, fmt);
	rnd_safe_append_vprintf(&str, 0, fmt, ap);
	va_end(ap);

	rnd_message(RND_MSG_ERROR, "%s", str.array);

	gds_uninit(&str);
	return -1;
}

/* Load the rnd_coord_t value of a text node into res. Return 0 on success */
static int parse_coord(rnd_coord_t *res, lht_node_t *nd)
{
	double tmp;
	rnd_bool success;

	if (nd == NULL) return iolht_error(nd, "Missing coord value\n");
	if (nd->type != LHT_TEXT) return iolht_error(nd, "Invalid coord type: '%d'\n", nd->type);

	tmp = rnd_get_value_ex(nd->data.text.value, NULL, NULL, NULL, NULL, &success);
	if (!success) return iolht_error(nd, "Invalid coord value: '%s'\n", nd->data.text.value);

	*res = tmp;
	return 0;
}

/* Load the duble value of a text node into res. Return 0 on success */
static int parse_double(double *res, lht_node_t *nd)
{
	double tmp;
	char *end;

	if (nd == NULL) return iolht_error(nd, "Missing floating point number\n");
	if (nd->type != LHT_TEXT) return iolht_error(nd, "Invalid floating point number type: '%d'\n", nd->type);

	tmp = strtod(nd->data.text.value, &end);

	if (*end != '\0') return iolht_error(nd, "Invalid floating point value: '%s'\n", nd->data.text.value);

	*res = tmp;
	return 0;
}

static lht_node_t *hash_get(lht_node_t *hash, const char *name)
{
	lht_node_t *nd = lht_dom_hash_get(hash, name);
	if (nd != NULL) return nd;
	iolht_error(hash, "Missing hash field: '%s'\n", name);
	return NULL;
}

#define PARSE_COORD(dst, src)       parse_coord(dst, src)
#define PARSE_DOUBLE(dst, src)      parse_double(dst, src)
#define HASH_GET(hash, name)        hash_get(hash, name)
#define RND_LHT_ERROR               iolht_error

#include <librnd/font2/font_lht.h>


/* text object fields:
   - crd_sx, crd_sy: unrotated text's bbox size (framing spaces included)
   - net_sx: unrotated text's net width, <= crd_sx
*/

static rnd_font_t *rf_font_lookup_(rnd_design_t *hidlib, const char *name, const char *style)
{
	long best_score;
	htsp_entry_t *e, *best;
	re_sei_t *rxname, *rxstyle;
	int namelen, stylelen, fnlen;

	if ((name == NULL) || (*name == '\0'))
		name = ".";
	if ((style == NULL) || (*style == '\0'))
		style = "regular";

	if (sch_rnd_font_score_debug)
		fprintf(stderr, " --- font lookup: '%s' '%s'\n", name, style);

	rxname = re_sei_comp(name);
	rxstyle = re_sei_comp(style);

	namelen = strlen(name);
	stylelen = strlen(style);

	retry:;

	if (sch_rnd_font_score_debug)
		fprintf(stderr, " try\n");

	best_score = -1000000l;
	best = NULL;
	for(e = htsp_first(&rf_map); e != NULL; e = htsp_next(&rf_map, e)) {
		const char *s, *name = e->key;
		long score = 0;

		for(s = name; *s != '\0'; s++) {
			if ((*s == '/') || (*s == '\\'))
				name = s+1;
		}

		fnlen = strlen(name);

		if (rxname != NULL)
			score += re_sei_exec(rxname, name);
		if (rxstyle != NULL)
			score += re_sei_exec(rxstyle, name);

		score -= (fnlen - namelen - stylelen)*32;

		if (sch_rnd_font_score_debug)
			fprintf(stderr, "  %ld FONT: '%s'\n", score, name);
		if (score > best_score) {
			best_score = score;
			best = e;
		}
	}

	if ((best != NULL) && (best->value == &loadme)) {
		rnd_font_t *f = calloc(sizeof(rnd_font_t), 1);

		if (sch_rnd_font_score_debug)
			fprintf(stderr, " load: %s\n", best->key);

		if (rnd_font_load(hidlib, f, best->key, 0) != 0) {
			/* failed to load the font, remove from hash, try another */
			free(f);
			htsp_popentry(&rf_map, best->key);
			if (sch_rnd_font_score_debug)
				fprintf(stderr, "  FAILED\n");
			goto retry;
		}
		rnd_font_fix_v1(f);
		best->value = f;
	}

	if (sch_rnd_font_score_debug && (best != NULL) && (best->value != &loadme))
		fprintf(stderr, " use: %s\n", best->key);

	if (rxname != NULL)
		re_sei_free(rxname);
	if (rxstyle != NULL)
		re_sei_free(rxstyle);

	if (sch_rnd_font_score_debug)
		fprintf(stderr, "\n");

	return best == NULL ? NULL : best->value;
}

static const char emb_font_key[] = "__embedded/internal__";
static rnd_font_t *rf_font_lookup(rnd_design_t *hidlib, const char *name, const char *style)
{
	rnd_font_t *res = rf_font_lookup_(hidlib, name, style);

	if (res == NULL) /* fall back to configured default */
		res = rf_font_lookup_(hidlib, conf_core.editor.default_font.family, conf_core.editor.default_font.style);

	if (res == NULL) { /* fall back to embedded */
		if (!embf_parsed) {
			embf = malloc(sizeof(rnd_font_t));
			rnd_font_load_internal(embf, embf_font, sizeof(embf_font) / sizeof(embf_font[0]), embf_minx, embf_miny, embf_maxx, embf_maxy);
			embf_parsed = 1;
			htsp_set(&rf_map, (char *)emb_font_key, embf); /* there's an extra if() for not freeing this */
		}
		res = embf;
	}
	return res;
}

static rnd_font_t *rf_resolve_pen(rnd_design_t *hidlib, csch_cpen_t *pen)
{
	static int epic_fail = 0;
	rnd_font_t *f;
	htsp_entry_t *e;
	const char *family, *style;

	if (epic_fail)
		return NULL;

	if (pen->font_handle_valid)
		return pen->font_handle;

	retry:;
	f = NULL;

	/* get pen's preferred font */
	if ((pen->font_family != NULL) || (pen->font_style != NULL)) {
		f = rf_font_lookup(hidlib, pen->font_family, pen->font_style);
		if (f != NULL) {
			family = pen->font_family;
			style = pen->font_style;
		}
	}

	/* fall back to configured default */
	if (f == NULL) {
		family = conf_core.editor.default_font.family;
		style = conf_core.editor.default_font.style;
		if ((family != NULL) || (style != NULL))
			f = rf_font_lookup(hidlib, family, style);
		if (f == NULL)
			family = style = NULL;
	}

	/* final fallback: any font we know of */
	if (f == NULL) {
		printf("FONT: final fallback");
		e = htsp_first(&rf_map);
		if (e != NULL)
			f = e->value;
		family = "<final fallback>";
		style = "";
	}

	if (f == NULL) {
		static int warned = 0;
		if (!warned) {
			rnd_message(RND_MSG_ERROR, "Failed to find absolutely _any_ font; text rendering is impossible.\nPlease revise rc/font_dirs in your conf!\n");
			epic_fail = 1;
		}
		return NULL;
	}

	if (f == &loadme) {
		if (e != NULL)
			htsp_popentry(&rf_map, e->key); /* don't try this again */
		goto retry;
	}

	pen->font_handle = f;
	pen->font_handle_valid = 1;
	return f;
}

RND_INLINE int count_spaces(const unsigned char *str, int *nospc)
{
	int cnt, ns;
	for(cnt = ns = 0; *str != '\0'; str++)
		if (isspace(*str))
			cnt++;
		else
			ns++;

	*nospc = ns;
	return cnt;
}

RND_INLINE csch_coord_t rf_text_halign(const unsigned char *str, csch_halign_t halign, csch_coord_t avail, csch_coord_t used, double scale, csch_coord_t *extra_glyph, csch_coord_t *extra_spc, csch_coord_t *net_sx)
{
	int spaces, nospc;
	double left;

	switch(halign) {
		case CSCH_HALIGN_invalid:
		case CSCH_HALIGN_START:           *net_sx = used; return 0;
		case CSCH_HALIGN_CENTER: center:; *net_sx = used; return rnd_round((double)(avail - used) / 2.0);
		case CSCH_HALIGN_END:             *net_sx = used; return avail - used;
		case CSCH_HALIGN_WORD_JUST:
			spaces = count_spaces(str, &nospc);
			if (spaces == 0)
				goto center;
			*net_sx = avail;
			*extra_spc = floor((double)(avail - used) / (double)spaces / scale);
			/* rnd_trace("space = %ld %ld - %ld spaces=%d extra_spc=%ld\n", avail - used, avail, used, spaces, *extra_spc); */
			return 0;
		case CSCH_HALIGN_JUST:
			spaces = count_spaces(str, &nospc);
			left = (double)(avail - used);
			if (spaces > 0) {
				*extra_spc = floor((left / 2.0) / (double)spaces / scale);
				left = left / 2.0;
			}
			*extra_glyph = floor(left / (double)nospc / scale);
			*net_sx = avail;
			return 0;
	}
	return 0;
}

static void rf_text_update(csch_sheet_t *sheet, csch_text_t *t, csch_cpen_t *pen)
{
	rnd_font_t *f;
	double w, h;
	const unsigned char *text_str;
	rnd_coord_t cx[4], cy[4];
	int save;

	if (t->scale != 0)
		return; /* already calculated */

	if (pen == NULL)
		return;

	f = rf_resolve_pen(&sheet->hidlib, pen);
	if (f == NULL)
			return;

	text_str = (const unsigned char *)csch_text_get_rtext(t);

	t->extra_glyph = t->extra_spc = 0;

	/* get initial size */
	rnd_font_string_bbox(cx, cy, f, text_str, 0, 0, 1, 1, 0, 0, 0, 0);
	cx[0] = 0; /* undo the effect of left side whitespace: we started to render from 0;0 */
	w = P2C(cx[1] - cx[0]);
	h = P2C(f->height);

	/* figure which kind of text fitting to use */
	if (t->has_bbox) { /* fit in bbox */
		double scx, scy;

		t->crd_sx = t->spec2.x - t->spec1.x;
		t->crd_sy = t->spec2.y - t->spec1.y;
		scx = (double)t->crd_sx / (double)w;
		scy = (double)t->crd_sy / (double)h;

		if (scx < scy) {
			t->scale = scx;
			t->off_x = -P2C(cx[0])*t->scale;;
			t->off_y = (t->crd_sy - h * scx) / 2;
		}
		else {
			t->scale = scy;
			t->off_x = rf_text_halign(text_str, t->halign, t->crd_sx, w * scy, t->scale, &t->extra_glyph, &t->extra_spc, &t->net_sx);
			if (t->off_x < 0) t->off_x = 0;
			t->off_x -= P2C(cx[0])*t->scale;
			t->off_y = 0;
		}
	}
	else { /* fixed height */
		t->scale = (double)pen->font_height / (double)P2C(f->height);
		t->net_sx = t->crd_sx = w * t->scale;
		t->crd_sy = pen->font_height;
		t->off_x = -P2C(cx[0])*t->scale;
		t->off_y = 0;

		t->spec2.x = t->spec1.x + t->crd_sx;
		t->spec2.y = t->spec1.y + t->crd_sy;
	}

	/* recalculate bbox */
	save = t->bbox_calcing;
	t->bbox_calcing = 1;
	csch_text_update(sheet, t, 1);
	t->bbox_calcing = save;
}

typedef struct {
/*	csch_sheet_t *sheet;*/
	rnd_hid_gc_t gc;
/*	csch_text_t *t;
	csch_cpen_t *pen;*/
} rf_draw_t;

static void rf_draw_atom_cb(void *cb_ctx, const rnd_glyph_atom_t *a)
{
	rf_draw_t *ctx = cb_ctx;
	long h;

	switch(a->type) {
		case RND_GLYPH_LINE:
			rnd_hid_set_line_width(ctx->gc, a->line.thickness);
			rnd_render->draw_line(ctx->gc, a->line.x1, a->line.y1, a->line.x2, a->line.y2);
			break;
		case RND_GLYPH_ARC:
			rnd_hid_set_line_width(ctx->gc, a->arc.thickness);
			rnd_render->draw_arc(ctx->gc, a->arc.cx, a->arc.cy, a->arc.r, a->arc.r, a->arc.start, a->arc.delta);
			break;
		case RND_GLYPH_POLY:
			h = a->poly.pts.used / 2;
			rnd_render->fill_polygon(ctx->gc, h, &a->poly.pts.array[0], &a->poly.pts.array[h]);
			break;
	}
}

void sch_rnd_render_text_string(rnd_hid_gc_t gc, void *font, rnd_coord_t x, rnd_coord_t y, rnd_coord_t size, const unsigned char *text_str)
{
	rf_draw_t ctx;

	ctx.gc = gc;

	rnd_font_draw_string_justify(font, text_str, x, y, size, size, 0,
		RND_FONT_MIRROR_Y, -3, 0, RND_FONT_TINY_CHEAP,
		0, 0, rf_draw_atom_cb, &ctx);
}

void sch_rnd_render_text_string_scrotmir(rnd_hid_gc_t gc, void *font, rnd_coord_t x, rnd_coord_t y, double size, double rot, int mirrory, const unsigned char *text_str)
{
	rf_draw_t ctx;

	ctx.gc = gc;

	rnd_font_draw_string_justify(font, text_str, x, y, size, size, rot,
		mirrory ? RND_FONT_MIRROR_Y : 0, -3, 0, RND_FONT_TINY_CHEAP,
		0, 0, rf_draw_atom_cb, &ctx);
}

static void rf_text_render(csch_sheet_t *sheet, rnd_hid_gc_t gc, csch_text_t *t, csch_cpen_t *pen)
{
	const unsigned char *text_str;
	rf_draw_t ctx;
	rnd_font_t *font;
	rnd_coord_t x, y, xo, yo;
	double rad, deg;
	int mirx, miry;

	if (t->scale == 0)
		rf_text_update(sheet, t, pen);

	if (t->scale == 0)
		return;

/*	ctx.sheet = sheet;*/
	ctx.gc = gc;
/*	ctx.t = t;
	ctx.pen = pen;*/


	rnd_hid_set_line_cap(gc, rnd_cap_round);

	text_str = (const unsigned char *)csch_text_get_rtext(t);

	font = pen->font_handle;
	mirx = t->inst_mirx;
	miry = t->inst_miry;

	xo = C2P(t->inst1.x);
	yo = C2P(t->inst1.y);

	deg = t->inst_raw_rot;

	if ((deg > 360.0) || (deg < -360.0))
		deg = fmod(deg, 360.0);

	/* rnd_trace("deg=%f %d %d '%s' \n", deg, mirx, miry, text_str); */

	x = xo + C2P(mirx ? -t->off_x : +t->off_x) - (mirx ? C2P(t->net_sx) : 0);
	y = yo + C2P(miry ? -t->off_y : +t->off_y) + (!miry ? font->height * t->scale : 0);

	rad = -deg / RND_RAD_TO_DEG;
	rnd_rotate(&x, &y, xo, yo, cos(rad), sin(rad));

	rnd_font_draw_string_justify(font, text_str, x, y, t->scale, t->scale, deg,
		RND_FONT_MIRROR_Y | RND_FONT_HTAB | RND_FONT_INVIS_TAB,
		0, 0, RND_FONT_TINY_CHEAP,
		C2P(t->extra_glyph), C2P(t->extra_spc), rf_draw_atom_cb, &ctx);
}

static void rf_inval_font(csch_sheet_t *sheet, csch_text_t *text)
{
	text->scale = 0;
}

static void map_fontdir(rnd_design_t *hidlib, const char *dr, int depth)
{
	DIR *dir;
	struct dirent *de;

	if (depth > FONTMAP_MAX_DEPTH) {
		rnd_message(RND_MSG_ERROR, "map_fontdor(): went too deep at %s\n", dr);
		return;
	}

	dir = rnd_opendir(hidlib, dr);
	if (dir == NULL)
		return;

	while((de = rnd_readdir(dir)) != NULL) {
		char *fp;

		if (de->d_name[0] == '.')
			continue;

		fp = rnd_concat(dr, "/", de->d_name, NULL);
		if (rnd_is_dir(hidlib, fp)) {
			map_fontdir(hidlib, fp, depth+1);
			free(fp);
		}
		else
			htsp_set(&rf_map, fp, &loadme);
	}
	rnd_closedir(dir);
}

static void sch_rnd_font_rnd_init2(void)
{
	rnd_conf_listitem_t *ci;

	htsp_init(&rf_map, strhash, strkeyeq);

	for(ci = rnd_conflist_first((rnd_conflist_t *)&conf_core.rc.font_dirs); ci != NULL; ci = rnd_conflist_next(ci)) {
		char *dr = rnd_build_fn(NULL, ci->val.string[0]);
		map_fontdir(NULL, dr, 0);
		free(dr);
	}
}

static void sch_rnd_font_rnd_uninit(void)
{
	htsp_entry_t *e;
	for(e = htsp_first(&rf_map); e != NULL; e = htsp_next(&rf_map, e)) {
		if (e->key != emb_font_key) {
			rnd_font_t *f = e->value;
			if (f != &loadme) {
				rnd_font_free(f);
				free(f);
			}
			free(e->key);
		}
	}
	htsp_uninit(&rf_map);
	free(embf);
	embf = NULL;
	embf_parsed = 0;
}




