/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - sheet-local symbol library
 *  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
 */

/* Backend local-lib operations */

static csch_cgrp_t *loc_get(csch_sheet_t *sheet, csch_lib_t *src, csch_lib_t **root_dir_out)
{
	long locgrp_oid = src->backend_data.lng[0];
	csch_source_arg_t *srca;
	csch_cgrp_t *symlib;
	csch_lib_t *root_dir;

	srca = csch_attrib_src_p("symlib_local", NULL);
	if (csch_loclib_get_roots(&root_dir, &symlib, loclib_master, sheet, srca, 0, NULL) != 0)
		return NULL;

	if (root_dir_out != NULL)
		*root_dir_out = root_dir;

	return htip_get(&symlib->id2obj, locgrp_oid);
}

static long loc_list_recurse(csch_cgrp_t *grp, csch_cgrp_t *loc, vtp0_t *res)
{
	long sum = 0;
	htip_entry_t *e;

	if ((grp->hdr.type == CSCH_CTYPE_GRP_REF) && (grp->data.ref.grp == loc)) {
		vtp0_append(res, grp);
		return 1;
	}

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_cgrp_t *child = e->value;
		if (csch_obj_is_grp(&child->hdr))
			sum += loc_list_recurse(child, loc, res);
	}
	return sum;
}


static int symlib_local_loc_list(csch_sheet_t *sheet, csch_lib_t *src)
{
	long cnt;
	vtp0_t arr = {0};
	csch_cgrp_t *loc;

	loc = loc_get(sheet, src, NULL);
	if (loc == NULL)
		return -1;

	cnt = loc_list_recurse(&sheet->direct, loc, &arr);
	rnd_message(RND_MSG_INFO, "Found %ld references to local lib entry %s\n", cnt, loc->loclib_name);

	if (cnt > 0) {
		fgw_arg_t args[4], ares;

		args[1].type = FGW_STR; args[1].val.str = "objarr";
		fgw_ptr_reg(&rnd_fgw, &args[2], CSCH_PTR_DOMAIN_COBJ_ARR, FGW_PTR | FGW_STRUCT, &arr);
		rnd_actionv_bin(&sheet->hidlib, "TreeDialog", &ares, 3, args);
		fgw_ptr_unreg(&rnd_fgw, &args[2], CSCH_PTR_DOMAIN_COBJ_ARR);
		vtp0_uninit(&arr);
	}

	return 0;
}

static long loc_del_recurse(csch_cgrp_t *grp, csch_cgrp_t *loc)
{
	long sum = 0;
	htip_entry_t *e;

	if ((grp->hdr.type == CSCH_CTYPE_GRP_REF) && (grp->data.ref.grp == loc)) {
		csch_grp_ref_embed(grp);
		return 1;
	}

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_cgrp_t *child = e->value;
		if (csch_obj_is_grp(&child->hdr))
			sum += loc_del_recurse(child, loc);
	}
	return sum;
}


static int symlib_local_loc_del(csch_sheet_t *sheet, csch_lib_t *src)
{
	long cnt;
	csch_cgrp_t *loc;

	loc = loc_get(sheet, src, NULL);
	if (loc == NULL)
		return -1;

	cnt = loc_del_recurse(&sheet->direct, loc);
	rnd_message(RND_MSG_INFO, "Embedded %ld symbols whole removing local lib entry %s\n", cnt, loc->loclib_name);

	csch_cnc_remove(sheet, &loc->hdr);
	csch_lib_remove(src);

	rnd_event(&sheet->hidlib, CSCH_EVENT_LIBRARY_CHANGED, NULL);

	return 0;
}

static const char *loc_ext_path_split(const char *path)
{
	const char *fn = strrchr(path, '/');
	if (fn != NULL)
		fn++;
	else
		fn = path;

	return fn;
}

static void loc_find_ext(vtp0_t *res, csch_lib_t *libent, const char *name)
{
	if (libent->type == CSCH_SLIB_DIR) {
		long n;
		for(n = 0; n < libent->children.used; n++)
			loc_find_ext(res, (csch_lib_t *)libent->children.array[n], name);
	}
	else {
		const char *lpath, *lfn;
	
		lpath = libent->name;
		lfn = loc_ext_path_split(lpath);

		if (strcmp(lfn, name) == 0) {
/*			rnd_trace("found %s %s %s\n", name, lpath, libent->realpath);*/
			vtp0_append(res, libent);
		}
	}
}

static csch_cgrp_t *first_grp(const csch_cgrp_t *src)
{
	htip_entry_t *e;
	for(e = htip_first(&src->id2obj); e != NULL; e = htip_next(&src->id2obj, e)) {
		csch_cgrp_t *obj = e->value;
		if (csch_obj_is_grp(&obj->hdr))
			return obj;
	}
	return NULL;
}

/* Make sure only floaters are grp-ref-transformed; remove non-floaters
   from the child_xform list and warn */
static void loc_validate_xforms(csch_cgrp_t *grpref)
{
	long n;

	for(n = 0; n < grpref->data.ref.child_xform.used; n++) {
		csch_child_xform_t *cx = grpref->data.ref.child_xform.array[n];
		if (cx != NULL) {
			csch_chdr_t *child = csch_oidpath_resolve_in(grpref->data.ref.grp, &cx->path);
			if (!child->floater) {
				const char *aname = csch_attrib_get_str(&grpref->attr, "name");
				rnd_message(RND_MSG_WARNING, "removing group ref transformation in %s:\nin the new symbol child object is not a floater\n", aname);
				vtp0_remove(&grpref->data.ref.child_xform, n, 1);
				n--;
			}
		}
	}
}

static void loc_replace_cached_refs(csch_cgrp_t *grp, csch_cgrp_t *old_grp, csch_cgrp_t *new_grp)
{
	htip_entry_t *e;

	if (grp->hdr.type == CSCH_CTYPE_GRP_REF) {
		if (grp->data.ref.grp == old_grp) {
			grp->data.ref.grp = new_grp;
			loc_validate_xforms(grp);
		}
	}

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e))
		if (csch_obj_is_grp(e->value))
			loc_replace_cached_refs(e->value, old_grp, new_grp);
}

static void loc_replace(csch_sheet_t *sheet, csch_cgrp_t *old_grp, csch_lib_t *old_lib, csch_lib_t *new_lib)
{
	csch_sheet_t tmp = {0};
	csch_cgrp_t *new_grp = NULL;
	int r;

	csch_sheet_init(&tmp, NULL);
	r = csch_lib_load(sheet, &tmp, new_lib, NULL);
	if (r == 0)
		new_grp = first_grp(&tmp.direct);
	if (new_grp != NULL) {
		new_grp->hdr.oid = old_grp->hdr.oid; /* preserve OID so grp_refs don't break */

		csch_lib_remove(old_lib); /* remove from lib hash first, this depends on existing grp... */
		csch_cnc_remove(sheet, &old_grp->hdr); /* ... now it's safe to get rid of the old grp */

		new_grp = local_insert(sheet, new_grp, 0);

		rnd_event(&sheet->hidlib, CSCH_EVENT_LIBRARY_CHANGED, NULL);
		loc_replace_cached_refs(&sheet->direct, old_grp, new_grp);
		csch_cgrp_render_all(sheet, &sheet->direct);
		if (rnd_gui != NULL)
			rnd_gui->invalidate_all(rnd_gui);
	}
	else
		rnd_message(RND_MSG_ERROR, "Failed to load group object from external library\n");

	csch_sheet_uninit(&tmp);
}

static int symlib_local_loc_refresh_from_ext(csch_sheet_t *sheet, csch_lib_t *src)
{
	long n;
	csch_cgrp_t *loc;
	csch_lib_t *root_dir;
	vtp0_t cand = {0};
	char *name, *sep;
	csch_lib_root_t *libroot;

	loc = loc_get(sheet, src, &root_dir);
	if (loc == NULL)
		return -1;

	libroot = sheet->libs.array[loclib_master->uid];

	name = rnd_strdup(src->name);
	sep = strrchr(name, '.');
	if (sep != NULL)
		*sep = '\0';

	rnd_trace("search %s\n", name);
	for(n = 0; n < libroot->roots.used; n++) {
		csch_lib_t *root = libroot->roots.array[n];
		if ((root != root_dir) && (root != NULL))
			loc_find_ext(&cand, root, name);
	}

	switch(cand.used) {
		case 0:
			rnd_message(RND_MSG_ERROR, "No such symbol found in external libs: '%s'\n", name);
			break;
		default: /* more than one candidates */
			TODO("offer picking one (we are currently using the first only)");
			rnd_message(RND_MSG_WARNING, "More than one candidates for '%s' in libs.\nUsing the first one.\n", name);
		case 1:
			loc_replace(sheet, loc, src, cand.array[0]);
	}

	vtp0_uninit(&cand);
	free(name);
	rnd_event(&sheet->hidlib, CSCH_EVENT_LIBRARY_CHANGED, NULL);
	return 0;
}


static int symlib_local_loc_edit(csch_sheet_t *sheet, csch_lib_t *src)
{
	csch_cgrp_t *loc;
	fgw_arg_t args[2], ares;
	gds_t tmp = {0};
	csch_oidpath_t oidp = {0};

	loc = loc_get(sheet, src, NULL);
	if (loc == NULL)
		return -1;

	/* build object:oidpath for first arg of AttributeDialog() */
	csch_oidpath_from_obj(&oidp, &loc->hdr);
	gds_append_str(&tmp, "object:");
	csch_oidpath_to_str_append(&tmp, &oidp);
	csch_oidpath_free(&oidp);

	/* call AttributeDialog */
	args[1].type = FGW_STR | FGW_DYN; args[1].val.str = tmp.array;
	rnd_actionv_bin(&sheet->hidlib, "AttributeDialog", &ares, 2, args);

	return 0;
}
