/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - BXL file format support
 *  Copyright (C) 2025 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2025)
 *
 *  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
 */


#include <stdio.h>
#include <libcschem/config.h>
#include <libcschem/cnc_pen.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_conn.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/operation.h>
#include <libcschem/util_parse.h>
#include <libcschem/plug_io.h>
#include <sch-rnd/util_sheet.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/safe_fs_dir.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/safe_fs.h>

#include <plugins/lib_alien/read_helper.h>
#include <plugins/lib_alien/read_postproc.h>

#include "bxl_decode.h"
#include "bxl_lex.h"
#include "bxl_gram.h"

#include "io_bxl_conf.h"
extern conf_io_bxl_t io_bxl_conf;

#include "bxl.h"
#include "read.h"

/* Parse a single symbol */
static csch_cgrp_t *create_sym(sch_bxl_ctx_t *ctx)
{
	csch_source_arg_t *src;
	csch_cgrp_t *resgrp = NULL;

	resgrp = csch_cgrp_alloc(ctx->alien.sheet, &ctx->alien.sheet->direct, csch_oid_new(ctx->alien.sheet, &ctx->alien.sheet->direct));
	src = csch_attrib_src_c(ctx->fn, ctx->lineno, 0, NULL);
	csch_cobj_attrib_set(ctx->alien.sheet, resgrp, CSCH_ATP_HARDWIRED, "role", "symbol", src);

	return resgrp;
}

/* IO API function (load symbol from lib) */
csch_cgrp_t *io_bxl_load_grp(FILE *f_text, const char *fn, const char *fmt, const char *subsymname, csch_sheet_t *sheet)
{
	rnd_design_t *hl = &sheet->hidlib;
	sch_bxl_ctx_t bctx = {0};
	int chr, tok, yres;
	hdecode_t hctx;
	sch_bxl_ureglex_t lctx;
	sch_bxl_yyctx_t yyctx;
	sch_bxl_STYPE lval;

	if (htip_get(&sheet->direct.id2obj, 1) != NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': there's already a group1 in destination sheet\n", fn);
		return NULL;
	}

	sch_bxl_init(&bctx, subsymname);

	bctx.fn = fn;
	bctx.f = rnd_fopen(hl, fn, "rb");
	if (bctx.f == NULL)
		return NULL;


	bctx.alien.sheet = sheet;
	bctx.alien.coord_factor = io_bxl_conf.plugins.io_bxl.coord_mult;
	bctx.alien.fmt_prefix = "io_bxl";
	bctx.grp = create_sym(&bctx);

	pcb_bxl_decode_init(&hctx);
	sch_bxl_lex_init(&lctx, sch_bxl_rules);
	sch_bxl_parse_init(&yyctx);

	/* read all bytes of the binary file */
	while((chr = fgetc(bctx.f)) != EOF) {
		int n, ilen;

		/* feed the binary decoding */
		ilen = pcb_bxl_decode_char(&hctx, chr);
		assert(ilen >= 0);
		if (ilen == 0)
			continue;

		/* feed the lexer */
		for(n = 0; n < ilen; n++) {
			tok = sch_bxl_lex_char(&lctx, &lval, hctx.out[n]);
			if (tok == UREGLEX_MORE)
				continue;

			/* feed the grammar */
			lval.line = lctx.loc_line[0];
			lval.first_col = lctx.loc_col[0];
			bctx.lineno = lctx.loc_line[0];
			bctx.colno = lctx.loc_col[0];
			yres = sch_bxl_parse(&yyctx, &bctx, tok, &lval);

			if ((bctx.in_error) && ((tok == T_ID) || (tok == T_QSTR)))
				free(lval.un.s);

			if (yres != 0) {
				fprintf(stderr, "BXL syntax error at %ld:%ld\n", lval.line, lval.first_col);
				if (bctx.grp != NULL)
					csch_cgrp_free(bctx.grp);
				bctx.grp = NULL;
				goto error;
			}
			sch_bxl_lex_reset(&lctx); /* prepare for the next token */
		}
	}

	csch_cgrp_update(bctx.alien.sheet, bctx.grp, 1);
	csch_sheet_bbox_update(bctx.alien.sheet);

	error:;
	sch_bxl_uninit(&bctx);
	fclose(bctx.f);
	return bctx.grp;
}

/* Copy only standard, symbol-related pen names from the default sheet */
static int sym_as_sheet_chk_copy_pen(void *udata, csch_sheet_t *dst, csch_cpen_t *p)
{
	if (strncmp(p->name.str, "busterm-", 8) == 0) return 1;
	if (strncmp(p->name.str, "term-", 5) == 0) return 1;
	if (strncmp(p->name.str, "sym-", 4) == 0) return 1;
	return 0;
}

int io_bxl_load_sheet(FILE *f_text, const char *fn, const char *fmt, csch_sheet_t *sheet)
{
	csch_cgrp_t *sym = io_bxl_load_grp(f_text, fn, fmt, NULL, sheet);

	if (sym != 0) {
		sheet->is_symbol = 1;
		sch_rnd_sheet_setup(sheet, SCH_RND_SSC_PENS | SCH_RND_SSC_PEN_MARK_DEFAULT, sym_as_sheet_chk_copy_pen, NULL);
		return 0;
	}

	return -1;
}


int io_bxl_test_parse(FILE *f_text, const char *filename, const char *fmt, csch_plug_io_type_t type)
{
	sch_bxl_ctx_t ctx = {0};
	int chr, tok, found_tok = 0, ret = 0;
	hdecode_t hctx;
	sch_bxl_ureglex_t lctx;

	ctx.fn = filename;
	ctx.silent = 1;


	ctx.f = rnd_fopen(NULL, filename, "rb"); /* need to open it again, for binary access */
	if (ctx.f == NULL)
		return -1;

	pcb_bxl_decode_init(&hctx);
	sch_bxl_lex_init(&lctx, sch_bxl_rules);

	/* read all bytes of the binary file */
	while((chr = fgetc(ctx.f)) != EOF) {
		int n, ilen;

		/* feed the binary decoding */
		ilen = pcb_bxl_decode_char(&hctx, chr);
		assert(ilen >= 0);
		if (ilen == 0)
			continue;

		/* feed the lexer */
		for(n = 0; n < ilen; n++) {
			sch_bxl_STYPE lval;
			tok = sch_bxl_lex_char(&lctx, &lval, hctx.out[n]);
			if (tok == UREGLEX_MORE)
				continue;

			if ((tok == UREGLEX_NO_MATCH) || (tok == UREGLEX_TOO_LONG)) {
				fclose(ctx.f);
				return -1; /* error - stop fast, don't read through the whole file */
			}

			/* simplified "grammar": find opening tokens, save the next token
			   as ID and don't do anything until finding the closing token */

			switch(found_tok) {
				
				/* found an opening token, tok is the ID */
				case T_PADSTACK: found_tok = T_ENDPADSTACK; break;
				case T_PATTERN: found_tok = T_ENDPATTERN; break;
				case T_SYMBOL:
					if (io_bxl_conf.plugins.io_bxl.print_subsym_names)
						rnd_message(RND_MSG_INFO, "BXL '%s' symbol '%s'\n", filename, lval.un.s);
					if ((type & CSCH_IOTYP_GROUP) || (type & CSCH_IOTYP_SHEET))
						ret++;
					found_tok = T_ENDSYMBOL;
					break;
				case T_COMPONENT: found_tok = T_ENDCOMPONENT; break;

				default:;
					switch(tok) {
						/* closing token: watch for an open again */
						case T_ENDPADSTACK:
						case T_ENDPATTERN:
						case T_ENDSYMBOL:
						case T_ENDCOMPONENT:
							found_tok = 0;
							break;

						/* outside of known context */
							case T_PADSTACK:
							case T_PATTERN:
							case T_COMPONENT:
							case T_SYMBOL:
								if (found_tok == 0) /* do not allow nested opens */
									found_tok = tok;
								break;
							default:;
								/* ignore anything else */
								break;
					}
					break;
			}

			/* fix memory leak */
			if ((tok == T_ID) || (tok == T_QSTR))
				free(lval.un.s);

			sch_bxl_lex_reset(&lctx); /* prepare for the next token */
		}
	}

	fclose(ctx.f);
	if (ret > 0)
		return 0; /* accept */

	return -1; /* refuse */
}
