/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - easyeda file format support
 *  Copyright (C) 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., 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
 */

/* high level for the 'std' format - included from read.c; glue layer
   between draw and io */

/* Load a zip-unpacked sheet file from fn into sheet with symbols coming
   from symtab. Unused args for now:
   - user_name: user assigned name, from the project file
   - loclib_sym: sheet-local symbol library (but we are embedding instead) */
static int easypro_load_sheet(const char *user_name, const char *fn, csch_sheet_t *sheet, csch_cgrp_t *loclib_sym, htsp_t *symtab, htss_t *devmap)
{
	read_ctx_t ctx = {0};
	int rv = 0, starti, n;
	FILE *f;

	f = rnd_fopen(&sheet->hidlib, fn, "r");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': failed to open for read\n", fn);
		return -1;
	}

	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = sheet;
	ctx.pro_symtab = symtab;
	ctx.pro_devmap = devmap;

	alien_setup(&ctx);
	csch_alien_sheet_setup(&ctx.alien, 1);
	easypro_init_ctx(&ctx);

	ctx.root = easypro_low_parse(f);
	fclose(f);

	if (ctx.root == NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': low level 'pro' parser failed\n", fn);
		return -1;
	}

	starti = easypro_verify_header(&ctx, 0);
	if (starti < 0)
		rv = -1;

#if 0
	we embed from hash table instead
	if (rv == 0) {
		/* TODO:  copy relevant symbols from symtab to loclib_sym */
	}
#endif

	/* parse the data section of the tree */
	if (rv == 0) {
		csch_cgrp_t *parent = &sheet->direct;
		for(n = starti; !ctx.pro_stop && (rv == 0) && (n < ctx.root->value.array.used); n++)
			rv = easypro_parse_any_obj(&ctx, ctx.root->value.array.child[n], n, &parent);
	}

	if (rv == 0) {
		csch_cgrp_update(sheet, &sheet->direct, 1);
		csch_sheet_bbox_update(sheet);
		csch_alien_update_conns(&ctx.alien);
	}

	easypro_uninit_ctx(&ctx);
	if (ctx.root != NULL)
		gdom_free(ctx.root);
	return rv;
}

/* Load a symbol from a fully prepared ctx into resgrp; if resgrp is
   NULL, allocate a new group in sheet */
static csch_cgrp_t *easypro_load_sym(read_ctx_t *ctx, csch_cgrp_t *resgrp)
{
	csch_source_arg_t *src;
	int rv = 0, starti, n, alloced = 0;

	ctx->root = easypro_low_parse(ctx->f);
	if (ctx->root == NULL) {
		rnd_message(RND_MSG_ERROR, "Error loading '%s': low level 'pro' parser failed\n", ctx->fn);
		return NULL;
	}

	starti = easypro_verify_header(ctx, 1);
	if (starti < 0)
		return NULL;

	if (ctx->pro_symtab == NULL) {
		/* create the symbol group (if not creating one per PART for a symtab) */
		if (resgrp == NULL) {
			resgrp = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct, csch_oid_new(ctx->sheet, &ctx->sheet->direct));
			alloced = 1;
		}

		src = csch_attrib_src_c(ctx->fn, 0, 0, NULL); /* whole-file context, no need to set location */
		csch_cobj_attrib_set(ctx->sheet, resgrp, CSCH_ATP_HARDWIRED, "role", "symbol", src);
	}

	easypro_init_ctx(ctx);

	/* parse the data section of the tree */
	for(n = starti; !ctx->pro_stop && (rv == 0) && (n < ctx->root->value.array.used); n++)
		rv = easypro_parse_any_obj(ctx, ctx->root->value.array.child[n], n, &resgrp);

	if (rv == 0) {
		csch_cgrp_update(ctx->sheet, resgrp, 1);
		csch_sheet_bbox_update(ctx->sheet);
	}
	else {
		if (alloced)
			csch_cgrp_free(resgrp);
		resgrp = NULL;
	}

	easypro_uninit_ctx(ctx);

	return resgrp;
}

/* Load a symbol from an open file into a group; if existing is NULL, allocate
   new */
csch_cgrp_t *io_easypro_load_grp_into(FILE *f, const char *fn, csch_sheet_t *sheet, csch_cgrp_t *existing, int slotno)
{
	read_ctx_t ctx = {0};
	csch_cgrp_t *grp;

	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;
	}

	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = sheet;
	ctx.pro_want_slot = slotno;

	alien_setup(&ctx);

	grp = easypro_load_sym(&ctx, existing);
	if (io_easyeda_postproc(&ctx, 0) != 0)
		rnd_message(RND_MSG_ERROR, "io_easyeda: failed to postprocess newly loaded symbol\n");

	if (ctx.root != NULL) {
		gdom_free(ctx.root);
		ctx.root = NULL;
	}

	return grp;
}

#include <rnd_inclib/lib_easyeda/zip_helper.c>

/* return 0 if f/fn looks like a symbol file */
static int easypro_test_parse_sym(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	char line_[256], *line;

	/* sheets are loaded as bundles from zip */
	if (type == CSCH_IOTYP_SHEET)
		return -1;

	/* first line is: ["DOCTYPE","SYMBOL","1.1"] */
	line = fgets(line_, sizeof(line_), f);
	if ((line == NULL) || (*line != '['))
		return -1;

	if (strncmp(line+1, "\"DOCTYPE\",", 10) != 0)
		return -1;
	line += 11;

	if (type == CSCH_IOTYP_GROUP) {
		if (strncmp(line, "\"SYMBOL\",", 9) != 0)
			return -1;
		return 0;
	}
	
	return -1;
}

/* returns 0 if f/fn looks like a zip that may contain the type-requested
   kind of data; fill in is_sym if the file is really a symbol and not a sheet. */
static int easypro_test_parse_zip(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type, int *is_sym)
{
	int res = -1;
	char *cmd;
	FILE *fc;

	*is_sym = 0;

	if (!easypro_is_file_zip(f))
		return -1;

	/* get a file list and look for known index files */
	cmd = easypro_zip_cmd(NULL, io_easyeda_conf.plugins.io_easyeda.zip_list_cmd, fn);
	fc = rnd_popen(NULL, cmd, "r");
	if (fc != NULL) {
		char *line, buf[1024];
		while((line = fgets(buf, sizeof(buf), fc)) != NULL) {
			if (strstr(line, "project.json") != NULL) { /* sheet */
				res = 0;
				break;
			}
			else if (strstr(line, "device.json") != NULL) { /* symbol */
				*is_sym = 1;
				res = 0;
				break;
			}
		}

		fclose(fc);
	}
	free(cmd);
	return res;
}

typedef struct easyepro_bundle_s {
	const char *fn;
	char *dir;
	unsigned is_sym:1;
	gds_t tempdir;

	/* sym load */
	DIR *dr;
	char *next_fn;

	/* bundled seet load */
	unsigned sheet_loader_inited:1;
	unsigned symsheet_inited:1;
	unsigned symtab_inited:1;
	vts0_t sheets;     /* list of name,filename pairs of all sheets */
	vts0_t syms;       /* list of name,filename pairs of all syms */
	int sheet_idx;     /* within ->sheets */
	htsp_t symtab;
	csch_sheet_t symsheet;
	htss_t devmap;     /* key: Device attribute value; val: symbol name */
} easypro_bundle_t;

/* read next dirent and set bnd->next_fn; skip over . and .. and hidden files */
static struct dirent *easypro_next_de(easypro_bundle_t *bnd)
{
	/* skip over . and .. */
	for(;;) {
		struct dirent *de = rnd_readdir(bnd->dr);
		if (de == NULL)
			return NULL;
		if (*de->d_name != '.') {
			bnd->next_fn = de->d_name;
			return de;
		}
	}
}

static int easypro_load_sym_as_sheet(const char *fn, csch_sheet_t *sheet)
{
	FILE *f;
	csch_cgrp_t *grp;
	
	f = rnd_fopen(&sheet->hidlib, fn, "r");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "failed to open '%s' for read\n", fn);
		return -1;
	}

	grp = io_easypro_load_grp_into(f, fn, sheet, &sheet->direct, -1);
	fclose(f);

	sch_rnd_sheet_setup(sheet, SCH_RND_SSC_PENS | SCH_RND_SSC_PEN_MARK_DEFAULT, sym_as_sheet_chk_copy_pen, NULL);
	sheet->is_symbol = 1;

	return (grp == NULL) ? -1 : 0;
}

/* read symbols from zip already unpacked into dir;
   return 0 (more to read) or 1 (no more sheets to read) or -1 on error */
static int easypro_load_zip_sym(easypro_bundle_t *bnd, csch_sheet_t *dst)
{
	struct dirent *de;
	char *fn;

	if (bnd->dr == NULL) {
		char *fn = rnd_concat(bnd->dir, "/SYMBOL", NULL);

		bnd->dr = rnd_opendir(NULL, fn);
		free(fn);
		if (bnd->dr == NULL)
			return -1;

		de = easypro_next_de(bnd);
		if (de == NULL)
			return -1;
	}

	/* load next symbol */
	fn = rnd_concat(bnd->dir, "/SYMBOL/", bnd->next_fn, NULL);
	easypro_load_sym_as_sheet(fn, dst);
	free(fn);

	/* figure name of the next symbol and terminate the load if there's no more */
	de = easypro_next_de(bnd);
	return (de == NULL) ? 1 : 0;
}



/* read the zip-unpacked project file and map sheets and symbols to load 
   into bnd->syms and bnd->sheets as pairs of strings */
static int easypro_load_zip_sheet_init(easypro_bundle_t *bnd, csch_sheet_t *sheet)
{
	char *fn, *sname = NULL;
	char *user_name = NULL; /* user readable sheet name or symbol title */
	char *sch_name = NULL; /* user readable sch name */
	njson_sem_ctx_t jctx = {0};
	FILE *f;
	enum {M_MISC, M_SCH, M_SYM, M_DEV} tmain = M_MISC;
	int level = 0, c, res = 0, sch_id= -1;

	fn = rnd_concat(bnd->dir, "/project.json", NULL);
	f = rnd_fopen(&sheet->hidlib, fn, "r");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "easypro sheet init: failed to open '%s' for read\n", fn);
		free(fn);
		return -1;
	}


	/* parse project.json and remember sheets and syms */
	while((c = fgetc(f)) != 0) {
		njson_sem_ev_t ev = njson_sem_push(&jctx, c);

		if (ev == NJSON_SEM_EV_eof) break;
		if (ev == NJSON_SEM_EV_error) {
			rnd_message(RND_MSG_ERROR, "easypro sheet init: project.json parse error: %s at %ld:%ld\n", jctx.njs.error, jctx.njs.lineno, jctx.njs.col);
			error:;
			res = -1;
			break;
		}

		switch(ev) {
			case NJSON_SEM_EV_more: continue;
			case NJSON_SEM_EV_eof: case NJSON_SEM_EV_error: break; /* can't happen */
			case NJSON_SEM_EV_OBJECT_BEGIN:
				if (level == 1) { /* remember which main tree we are in */
					if (strcmp(jctx.name, "schematics") == 0) tmain = M_SCH;
					else if (strcmp(jctx.name, "symbols") == 0) tmain = M_SYM;
					else if (strcmp(jctx.name, "devices") == 0) tmain = M_DEV;
				}
				if ((level == 2) && ((tmain == M_SYM) || (tmain == M_SCH) || (tmain == M_DEV))) {
					/* remember the name of the object subtree */
					if (sname != NULL) free(sname);
					sname = rnd_strdup(jctx.name);
/*					rnd_trace(" sname: %s\n", jctx.name);*/
				}

			case NJSON_SEM_EV_ARRAY_BEGIN:
				level++;
/*				rnd_trace(" [%d %d] %s\n", level, tmain, jctx.name);*/
				break;
			case NJSON_SEM_EV_OBJECT_END:
			case NJSON_SEM_EV_ARRAY_END:
				if ((level == 5) && (tmain == M_SCH)) { /* finished reading a schematic block */
					char *name, *path;
/*					rnd_trace("!sch flush! sname=%s uname=%s id=%d\n", sname, user_name, sch_id);*/
					name = rnd_concat(sch_name, "::", user_name, NULL);
					path = rnd_strdup_printf("%s/SHEET/%s/%d.esch", bnd->dir, sname, sch_id);

					vts0_append(&bnd->sheets, name);
					vts0_append(&bnd->sheets, path);

					if (user_name != NULL) free(user_name);
					user_name = NULL;
					sch_id = -1;
				}
				if ((level == 3) && (tmain == M_SYM)) { /* finished reading a symbol block */
					char *name, *path;
/*					rnd_trace("!sym flush! sname=%s title=%s\n", sname, user_name);*/
					name = rnd_strdup(sname); /* user name should be the hash-like name, in some cases the sheet will reference using that */
					path = rnd_concat(bnd->dir, "/SYMBOL/", sname, ".esym", NULL);

					vts0_append(&bnd->syms, name);
					vts0_append(&bnd->syms, path);

					user_name = NULL; /* don't free user_name, ownership is passed to the vector */
				}
				level--;
				if (level == 1) tmain = M_MISC;
				break;

			case NJSON_SEM_EV_ATOMIC:
				switch(tmain) {
					case M_MISC: break;
					case M_SCH:
						if ((level == 3) && (strcmp(jctx.name, "name") == 0)) {
							/* remember user-unreadable long uid with use readable name - however, it's not necessarily unique */
							if (jctx.type != NJSON_SEM_TYPE_STRING) {
								rnd_message(RND_MSG_ERROR, "easypro sheet init: project.json parse error: sch name must be a string at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
								goto error;
							}
							free(sch_name);
							sch_name = rnd_strdup(jctx.value.string);
							break;
						}

						if (level != 5) break;
						/* reading items of a schematic block */
/*						rnd_trace("  [%d] sch atom: %s\n", level, jctx.name);*/

						/* remember a few of the sch data fields for flush to use above */
						if (strcmp(jctx.name, "id") == 0) {
							if (jctx.type != NJSON_SEM_TYPE_NUMBER) {
								rnd_message(RND_MSG_ERROR, "easypro sheet init: project.json parse error: sch id must be a number at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
								goto error;
							}
							sch_id = jctx.value.number;
						}
						if (strcmp(jctx.name, "name") == 0) {
							if (jctx.type != NJSON_SEM_TYPE_STRING) {
								rnd_message(RND_MSG_ERROR, "easypro sheet init: project.json parse error: sheet name must be a string at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
								goto error;
							}
							if (user_name != NULL) free(user_name);
							user_name = rnd_strdup(jctx.value.string);
						}
						break;
					case M_SYM:
						break;
					case M_DEV:
						if ((level == 4) && (jctx.name != NULL) && (strcmp(jctx.name, "Symbol") == 0)) {
							rnd_trace("project.json DEVMAP: '%s' -> '%s'\n", sname, jctx.value.string);
							TODO("devmap: this ignores the other attributes which should probably be inherited by the symbol instance");
							htss_insert(&bnd->devmap, rnd_strdup(sname), rnd_strdup(jctx.value.string));
						}
				}
				break;
		}
	}

	/* cleanup and return */
	free(sname);
	free(user_name);
	free(sch_name);
	njson_sem_uninit(&jctx);
	fclose(f);
	free(fn);
	return res;
}

/* load the next sheet from bnd into sheet */
static int easypro_load_zip_sheet(easypro_bundle_t *bnd, csch_sheet_t *sheet)
{
	csch_cgrp_t *loclib_sym;

	if (!bnd->sheet_loader_inited) { /* setup bnd before the first sheet */
		long n;
		read_ctx_t symctx = {0};

		/* parse the project file */
		if (easypro_load_zip_sheet_init(bnd, sheet) != 0)
			return -1;

		if (bnd->sheets.used < 2) {
			rnd_message(RND_MSG_ERROR, "easypro sheet init: project.json does not name any sheet\n");
			return -1;
		}
		bnd->sheet_loader_inited = 1;

		/* load all symbols into temporary symsheet and build a symhash */
		csch_sheet_init(&bnd->symsheet, NULL);
		bnd->symsheet_inited = 1;

		symctx.sheet = &bnd->symsheet;
		symctx.pro_symtab = &bnd->symtab;
		htsp_init(&bnd->symtab, strhash, strkeyeq);
		bnd->symtab_inited = 1;

		alien_setup(&symctx);

		for(n = 0; n < bnd->syms.used; n+=2) {
			const char *key = bnd->syms.array[n];
			const char *path = bnd->syms.array[n+1];
			csch_cgrp_t *sym;

			symctx.f = rnd_fopen(&sheet->hidlib, path, "r");
			if (symctx.f == NULL) {
				rnd_message(RND_MSG_ERROR, "Failed to open symbol at '%s'\n", path);
				return -1;
			}

			symctx.fn = path;
			symctx.symtab_key = key;
			sym = easypro_load_sym(&symctx, NULL);
			fclose(symctx.f);

			if (sym == NULL) {
				rnd_message(RND_MSG_ERROR, "Failed to load symbol from '%s'\n", path);
				return -1;
			}
		}
	}

#if 0
we are embedding symbols for now
	/* create sheet local library */
	src = csch_attrib_src_c(bnd->fn, 0, 0, NULL);
	loclib_sym = csch_loclib_get_root_by_name(sheet, "symbol", src, 1, &alloced);
	if (loclib_sym == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to allocate symbol local lib (root)\n");
		return -1;
	}
#else
	loclib_sym = NULL;
#endif

	if (easypro_load_sheet(bnd->sheets.array[bnd->sheet_idx], bnd->sheets.array[bnd->sheet_idx+1], sheet, loclib_sym, &bnd->symtab, &bnd->devmap) != 0)
		return -1;

	bnd->sheet_idx += 2;
	if (bnd->sheet_idx >= bnd->sheets.used)
		return 1; /* success, no more sheet */
	return 0; /* success, reiterate for the next sheet */
}

