/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - eeschema file format support
 *  Copyright (C) 2024..2025 Aron Barath
 *  Copyright (C) 2022..2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2024)
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
 */

static const char eeschema_wks_default_desc[] =
	"(kicad_wks (version 20210606) (generator pl_editor)\n"
	"(setup (textsize 1.5 1.5)(linewidth 0.15)(textlinewidth 0.15)\n"
	"(left_margin 10)(right_margin 10)(top_margin 10)(bottom_margin 10))\n"
	"(rect (name \"\") (start 110 34) (end 2 2) (comment \"rect around the title block\"))\n"
	"(rect (name \"\") (start 0 0 ltcorner) (end 0 0) (repeat 2) (incrx 2) (incry 2))\n"
	"(line (name \"\") (start 50 2 ltcorner) (end 50 0 ltcorner) (repeat 30) (incrx 50))\n"
	"(tbtext \"1\" (name \"\") (pos 25 1 ltcorner) (font (size 1.3 1.3)) (repeat 100) (incrx 50))\n"
	"(line (name \"\") (start 50 2 lbcorner) (end 50 0 lbcorner) (repeat 30) (incrx 50))\n"
	"(tbtext \"1\" (name \"\") (pos 25 1 lbcorner) (font (size 1.3 1.3)) (repeat 100) (incrx 50))\n"
	"(line (name \"\") (start 0 50 ltcorner) (end 2 50 ltcorner) (repeat 30) (incry 50))\n"
	"(tbtext \"A\" (name \"\") (pos 1 25 ltcorner) (font (size 1.3 1.3)) (justify center) (repeat 100) (incry 50))\n"
	"(line (name \"\") (start 0 50 rtcorner) (end 2 50 rtcorner) (repeat 30) (incry 50))\n"
	"(tbtext \"A\" (name \"\") (pos 1 25 rtcorner) (font (size 1.3 1.3)) (justify center) (repeat 100) (incry 50))\n"
	"(tbtext \"Date: ${ISSUE_DATE}\" (name \"\") (pos 87 6.9))\n"
	"(line (name \"\") (start 110 5.5) (end 2 5.5))\n"
	"(tbtext \"${KICAD_VERSION}\" (name \"\") (pos 109 4.1) (comment \"Kicad version\"))\n"
	"(line (name \"\") (start 110 8.5) (end 2 8.5))\n"
	"(tbtext \"Rev: ${REVISION}\" (name \"\") (pos 24 6.9) (font bold))\n"
	"(tbtext \"Size: ${PAPER}\" (name \"\") (pos 109 6.9) (comment \"Paper format name\"))\n"
	"(tbtext \"Id: ${#}/${##}\" (name \"\") (pos 24 4.1) (comment \"Sheet id\"))\n"
	"(line (name \"\") (start 110 12.5) (end 2 12.5))\n"
	"(tbtext \"Title: ${TITLE}\" (name \"\") (pos 109 10.7) (font (size 2 2) bold italic))\n"
	"(tbtext \"File: ${FILENAME}\" (name \"\") (pos 109 14.3))\n"
	"(line (name \"\") (start 110 18.5) (end 2 18.5))\n"
	"(tbtext \"Sheet: ${SHEETPATH}\" (name \"\") (pos 109 17))\n"
	"(tbtext \"${COMPANY}\" (name \"\") (pos 109 20) (font bold) (comment \"Company name\"))\n"
	"(tbtext \"${COMMENT1}\" (name \"\") (pos 109 23) (comment \"Comment 0\"))\n"
	"(tbtext \"${COMMENT2}\" (name \"\") (pos 109 26) (comment \"Comment 1\"))\n"
	"(tbtext \"${COMMENT3}\" (name \"\") (pos 109 29) (comment \"Comment 2\"))\n"
	"(tbtext \"${COMMENT4}\" (name \"\") (pos 109 32) (comment \"Comment 3\"))\n"
	"(line (name \"\") (start 90 8.5) (end 90 5.5))\n"
	"(line (name \"\") (start 26 8.5) (end 26 2))\n"
	")\n"
	;

struct eeschema_wks_corner
{
	const char* name;
	int r; /* 1: right edge; 0: left edge */
	int b; /* 1: bottom edge; 0: top edge */
};

struct eeschema_wks_xy
{
	float x;
	float y;
	float dx;
	float dy;
};

struct eeschema_wks_text
{
	const char* text;
	void (*incr)(struct eeschema_wks_text* const text);
	int len;
	char first;
	char last;
	char buf[16];
};

struct eeschema_wks_helper
{
	float left;
	float right;
	float top;
	float bottom;
	float incrx; /* increment value from (incrx DIST) */
	float incry; /* increment value from (incry DIST) */
	long repeat; /* repetition count from (repeat CNT) */
	int oob; /* out-of-bounds occured when incrementing x;y */
	const char* name; /* value from (name "NAME") */
	const char* comment; /* value from (comment "COMMENT") */
};

static const struct eeschema_wks_corner eeschema_wks_corners[] =
{
	{ "ltcorner", 0, 0 },
	{ "lbcorner", 0, 1 },
	{ "rtcorner", 1, 0 },
	{ "rbcorner", 1, 1 },
	{ 0 }
};

static void eeschema_wks_helper_init(read_ctx_t* const ctx,
	struct eeschema_wks_helper* const wks)
{
	memset(wks, 0, sizeof(*wks));

	wks->left   = ctx->page_left_margin;
	wks->right  = ctx->page_width - ctx->page_right_margin;
	wks->top    = ctx->page_top_margin;
	wks->bottom = ctx->page_height - ctx->page_bottom_margin;
}

/* return 0 if node is handled; 1 if not; negative on error */
static int eeschema_wks_helper_handle_node(read_ctx_t* const ctx,
	struct eeschema_wks_helper* const wks, gsxl_node_t* node)
{
	if(strcmp(node->str, "name")==0)
	{
		wks->name = node->children->str;
		return 0;
	}
	else
	if(strcmp(node->str, "comment")==0)
	{
		wks->comment = node->children->str;
		return 0;
	}
	else
	if(strcmp(node->str, "repeat")==0)
	{
		char* end;
		wks->repeat = strtol(node->children->str, &end, 10);

		if(end==NULL || 0!=(*end))
		{
			eechema_error(ctx, node->children, "not a valid integer");
			return -1;
		}

		return 0;
	}
	else
	if(strcmp(node->str, "incrx")==0)
	{
		char* end;
		wks->incrx = strtod(node->children->str, &end);

		if(end==NULL || 0!=(*end))
		{
			eechema_error(ctx, node->children,
				"not a valid floating-point value");
			return -1;
		}

		return 0;
	}
	else
	if(strcmp(node->str, "incry")==0)
	{
		char* end;
		wks->incry = strtod(node->children->str, &end);

		if(end==NULL || 0!=(*end))
		{
			eechema_error(ctx, node->children,
				"not a valid floating-point value");
			return -1;
		}

		return 0;
	}

	return 1;
}

static int eeschema_wks_helper_use_corner(read_ctx_t* const ctx,
	struct eeschema_wks_helper* const wks, struct eeschema_wks_xy* const xy,
	gsxl_node_t* const corner_node, const char* const corner_str)
{
	const struct eeschema_wks_corner* corner = eeschema_wks_corners;

	for(;corner->name;++corner)
	{
		if(strcmp(corner->name, corner_str)==0)
		{
			break;
		}
	}

	if(!corner)
	{
		eechema_error(ctx, corner_node,
			"corner specification not found: '%s'", corner_str);
		return -1;
	}

	if(corner->r)
	{
		xy->x = wks->right - xy->x;
		xy->dx = -1;
	}
	else
	{
		xy->x += wks->left;
		xy->dx = +1;
	}

	if(corner->b)
	{
		xy->y = wks->bottom - xy->y;
		xy->dy = -1;
	}
	else
	{
		xy->y += wks->top;
		xy->dy = +1;
	}

	return 0;
}

static int eechema_parse_wks_xy(read_ctx_t* const ctx,
	struct eeschema_wks_helper* const wks, gsxl_node_t* node,
	struct eeschema_wks_xy* const xy)
{
	const char* corner = "rbcorner";
	gsxl_node_t* corner_node = node;

	if(node->next==NULL || (node->next->next && node->next->next->next!=NULL))
	{
		eechema_error(ctx, node, "invalid wks 'xy' info");
		return -1;
	}

	xy->x = strtod(node->str, NULL);
	xy->y = strtod(node->next->str, NULL);

	if(node->next->next)
	{
		corner_node = node->next->next;
		corner = corner_node->str;
	}

	return eeschema_wks_helper_use_corner(ctx, wks, xy, corner_node, corner);
}

static void eeschema_wks_helper_xy_incr(struct eeschema_wks_helper* const wks,
	struct eeschema_wks_xy* const xy)
{
	xy->x += wks->incrx * xy->dx;
	xy->y += wks->incry * xy->dy;

	if(xy->x<wks->left)
	{
		xy->x = wks->left;
		wks->oob = 1;
	}
	else
	if(xy->x>wks->right)
	{
		xy->x = wks->right;
		wks->oob = 1;
	}

	if(xy->y<wks->top)
	{
		xy->y = wks->top;
		wks->oob = 1;
	}
	else
	if(xy->y>wks->bottom)
	{
		xy->y = wks->bottom;
		wks->oob = 1;
	}
}

static void eeschema_wks_text__incr(struct eeschema_wks_text* const text)
{
	int idx = text->len - 1;

	for(;;)
	{
		if(text->buf[idx]<text->last)
		{
			++text->buf[idx];
			return;
		}

		if(idx==0)
		{
			/* extend */
			memmove(text->buf+1, text->buf, 1+text->len);
			text->buf[0] = text->first;
			++text->len;
			return;
		}

		--idx;
	}
}

static void eeschema_wks_text_init(struct eeschema_wks_text* const text,
	const char* const str)
{
	if(str[1]==0 && (str[0]=='A' || str[0]=='1'))
	{
		text->text   = text->buf;
		text->incr   = eeschema_wks_text__incr;
		text->len    = 1;
		text->buf[0] = str[0];
		text->buf[1] = 0;
		text->first  = str[0];
		text->last   = (str[0]=='A') ? 'Z' : '9';
	}
	else
	{
		text->text = str;
		text->incr = NULL;
	}
}

static void eeschema_wks_text_incr(struct eeschema_wks_text* const text)
{
	if(text->incr)
	{
		text->incr(text);
	}
}

struct eeschema_wks_trans
{
	char        ch1;
	char        ch2; /* for %Cx only (sigh...) */
	int         toklen;
	const char* token;
	const char* replacement;
};

#define TRANS(ch1, ch2, token, repl) \
	{ ch1, ch2, sizeof(token)-1, token, repl }

static const struct eeschema_wks_trans eeschema_wks_translations[] =
{
	TRANS('K', 0, "KICAD_VERSION", "%../../A.generator% %../../A.generator_version%"),
	TRANS('Z', 0, "PAPER", "%../../A.paper%"),
	TRANS('Y', 0, "COMPANY", "%../../A.company%"),
	TRANS('D', 0, "ISSUE_DATE", "%../../A.date%"),
	TRANS('R', 0, "REVISION", "%../../A.rev%"),
	TRANS('S', 0, "#", "%../../A.sheet_number%"), /* TODO: no such attrib! */
	TRANS('N', 0, "##", "%../../A.number_of_sheets%"), /* TODO: no such attrib! */
	TRANS('P', 0, "SHEETPATH", "%../../A.sheet_path%"), /* TODO: no such attrib! */
	TRANS('F', 0, "FILENAME", "%" "filename%"),
	TRANS('T', 0, "TITLE", "%../../A.title%"),
	TRANS('C', '0', "COMMENT1", "%../../A.comment1%"),
	TRANS('C', '1', "COMMENT2", "%../../A.comment2%"),
	TRANS('C', '2', "COMMENT3", "%../../A.comment3%"),
	TRANS('C', '3', "COMMENT4", "%../../A.comment4%"),
	TRANS('C', '4', "COMMENT5", "%../../A.comment5%"),
	TRANS('C', '5', "COMMENT6", "%../../A.comment6%"),
	TRANS('C', '6', "COMMENT7", "%../../A.comment7%"),
	TRANS('C', '7', "COMMENT8", "%../../A.comment8%"),
	TRANS('C', '8', "COMMENT9", "%../../A.comment9%"),
	/* note: kicad does not handle %C9 */
	{ 0 }
};

#undef TRANS

static char* eeschema_wks_text_render(read_ctx_t* const ctx,
	gsxl_node_t* const node, const char* str, int* const out_is_dyntext)
{
	char* res;
	gds_t buf;
	int   dyntext = 0;

	gds_init(&buf);

	while(*str)
	{
		int c = *str++;

		if(c=='$' && (*str)=='{')
		{
			const char* const token = str+1;
			const char* const close = strchr(token, '}');

			if(close)
			{
				const int toklen = close - token;

				const struct eeschema_wks_trans* trans =
					eeschema_wks_translations;

				str = close + 1;

				for(;trans->replacement;++trans)
				{
					if(toklen==trans->toklen &&
						memcmp(token, trans->token, toklen)==0)
					{
						break;
					}
				}

				if(trans->replacement)
				{
					dyntext = 1;
					rnd_append_printf(&buf, "%s", trans->replacement);
					continue;
				}
				else
				{
					eechema_error(ctx, node, "unknown '$' sequence, string "
						"is truncated; unprocessed string: %s", token-2);
					goto leave;
				}
			}
			else
			{
				eechema_error(ctx, node, "missing closing '}' after '${', "
					"string is truncated");
				goto leave;
			}
		}
		else
		if(c=='%' && (*str))
		{
			c = *str++;

			if(c=='%')
			{
				gds_append(&buf, '%');
				gds_append(&buf, '%');
			}
			else
			{
				int next_c = *str;

				const struct eeschema_wks_trans* trans =
					eeschema_wks_translations;

				for(;trans->replacement;++trans)
				{
					if(c==trans->ch1 && !trans->ch2)
					{
						break;
					}
					else
					if(c==trans->ch1 && trans->ch2 && trans->ch2==next_c)
					{
						++str;
						break;
					}
				}

				if(trans->replacement)
				{
					dyntext = 1;
					rnd_append_printf(&buf, "%s", trans->replacement);
					continue;
				}
				else
				{
					eechema_error(ctx, node, "unknown sequence: %%%c, string "
						"is truncated", *(str-1));
					goto leave;
				}
			}
		}

		gds_append(&buf, c);
	}

leave:;
	res = rnd_strdup(buf.array);

	gds_uninit(&buf);

	*out_is_dyntext = dyntext;

	return res;
}

static int eeschema_process_wks_impl(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const node, float fw, float fh)
{
	if(strcmp(node->str, "kicad_wks")!=0)
	{
		eechema_error(ctx, node, "DOM is not a kicad_wks root");
		return -1;
	}

	ctx->page_width  = fw;
	ctx->page_height = fh;

	{
		gsxl_node_t* setup;

		for(setup=node->children;setup;setup=setup->next)
		{
			if(strcmp(setup->str, "setup")==0)
			{
				break;
			}
		}

		if(!setup)
		{
			eechema_error(ctx, node, "setup node not found in kicad_wks root");
			return -1;
		}

		if(eechema_parse_wkssetup(ctx, dst, setup->children)!=0)
		{
			return -1;
		}
	}

	if(ctx->alien.coord_factor!=0 && ctx->alien.coord_factor!=1)
	{
		double tmp = (fh * ctx->alien.coord_factor) / 4000.0;
		csch_coord_t ih = 1 + rnd_round(tmp);
		ctx->alien.oy = (((double)ih) * 4000.0) / ctx->alien.coord_factor;
	}
	else
	{
		ctx->alien.oy = fh;
	}

	dbg_printf(
		"page size: %f x %f\nmargins: l=%f, r=%f, t=%f, b=%f\n",
		ctx->page_width, ctx->page_height,
		ctx->page_left_margin, ctx->page_right_margin,
		ctx->page_top_margin, ctx->page_bottom_margin);

	return eechema_parse_wks(ctx, dst, node->children);
}

static int eeschema_process_wks(read_ctx_t* const ctx, float fw, float fh)
{
	gsxl_dom_t wks_dom;
	const char* old_fn;
	int res;

	if(eechema_parse_string(eeschema_wks_default_desc, &wks_dom)!=GSX_RES_EOE)
	{
		rnd_message(RND_MSG_ERROR, "Parse error in internal wks\n");
		return -1;
	}

	old_fn = ctx->fn;
	ctx->fn = "<builtin_wks>";

	res = eeschema_process_wks_impl(ctx, &ctx->alien.sheet->direct,
		wks_dom.root, fw, fh);

	ctx->fn = old_fn;

	gsxl_uninit(&wks_dom);

	return res;
}
