/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - attribute table import/export helpers
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  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 <libcschem/config.h>
#include <libcschem/project.h>
#include <libcschem/compile.h>
#include <librnd/core/plugins.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/paths.h>
#include <librnd/core/conf.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/hid_dad.h>
#include <plugins/backann/backann.h>


#include <stdio.h>
#include <stdarg.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <genvector/gds_char.h>
#include <genvector/vts0.h>

#include "lib_attbl.h"

#include "lib_attbl_conf.h"

static conf_lib_attbl_t conf_lib_attbl;
#include "conf_internal.c"

static const char lib_attbl_cookie[] = "lib_attbl";

const char *attbl_models[] = {"concrete", "concrete-symbols", "concrete-wirenets", "abstract", "abstract-components",  "abstract-nets", NULL};


typedef struct attbl_map_s {
	htsp_t keys;    /* key: const char * to attr keys ever seen */
	vts0_t hdr;     /* (sorted) list of unique key names (table cols) */
	vts0_t tmp;
	gds_t uuid_tmp;
} attbl_map_t;

static void attbl_map_add(attbl_map_t *map, htsp_t *attrs)
{
	htsp_entry_t *e;

	for(e = htsp_first(attrs); e != NULL; e = htsp_next(attrs, e))
		htsp_insert(&map->keys, e->key, e);
}

static int attbl_hdr_cmp(const void *k1, const void *k2)
{
	const char * const *h1 = k1, * const *h2 = k2;
	if ((**h1 == 'n') && (strcmp(*h1, "name") == 0))
		return -1;
	if ((**h2 == 'n') && (strcmp(*h2, "name") == 0))
		return +1;
	return strcmp(*h1, *h2);
}

static void attbl_mkhdr(attbl_map_t *map, const char *uuid1, const char *uuid2)
{
	htsp_entry_t *e;

	map->uuid_tmp.used = 0;
	gds_append_str(&map->uuid_tmp, "<uuid:");
	gds_append_str(&map->uuid_tmp, uuid1);
	if (uuid2 != NULL) {
		gds_append(&map->uuid_tmp, ':');
		gds_append_str(&map->uuid_tmp, uuid2);
	}
	gds_append(&map->uuid_tmp, '>');

	map->hdr.used = 0;
	for(e = htsp_first(&map->keys); e != NULL; e = htsp_next(&map->keys, e))
		vts0_append(&map->hdr, e->key);

	qsort(map->hdr.array, map->hdr.used, sizeof(char *), attbl_hdr_cmp);

	/* <uuid:type> is always the last column */
	vts0_append(&map->hdr, map->uuid_tmp.array);

	/* append terminator NULL */
	vts0_append(&map->hdr, NULL);
	map->hdr.used--;
}

static void attbl_row(attbl_map_t *map, htsp_t *attrs, csch_attbl_row_cb *row_cb, void *uctx, const char *type, const char *uuid)
{
	long n;

	if (row_cb == NULL)
		return;

	map->tmp.used = 0;
	for(n = 0; n < map->hdr.used-1; n++) {
		csch_attrib_t *attr = htsp_get(attrs, map->hdr.array[n]);
		char *aval = (attr == NULL ? NULL : attr->val);
		vts0_append(&map->tmp, aval);
	}

	vts0_append(&map->tmp, (char *)uuid);

	row_cb(uctx, type, uuid, (const char **)map->tmp.array, (const char **)map->hdr.array);
}

RND_INLINE void attbl_clear(attbl_map_t *map)
{
	htsp_clear(&map->keys);
	map->hdr.used = 0;
	map->tmp.used = 0;
	map->uuid_tmp.used = 0;
}

RND_INLINE void attbl_free(attbl_map_t *map)
{
	attbl_clear(map);
	htsp_uninit(&map->keys);
	vts0_uninit(&map->hdr);
	vts0_uninit(&map->tmp);
	gds_uninit(&map->uuid_tmp);
}

RND_INLINE void attbl_map_init(attbl_map_t *map)
{
	htsp_init(&map->keys, strhash, strkeyeq);
	vts0_init(&map->hdr);
	vts0_init(&map->tmp);
	gds_init(&map->uuid_tmp);
}

static int csch_attbl_export_abst(rnd_design_t *design, attbl_model_t model, csch_abstract_t *abst, csch_attbl_head_cb *head_cb, csch_attbl_row_cb *row_cb, csch_attbl_foot_cb *foot_cb, void *uctx)
{
	htsp_entry_t *e;
	attbl_map_t map;
	csch_project_t *prj = (csch_project_t *)design->project;
	void **v = vtp0_get(&prj->views, prj->curr, 0);
	csch_view_t *view = *v;

	attbl_map_init(&map);

	/* components */
	if ((model == ATTBLM_ABSTRACT) || (model == ATTBLM_ABSTRACT_COMPONENTS)) {

	for(e = htsp_first(&abst->comps); e != NULL; e = htsp_next(&abst->comps, e)) {
		csch_acomp_t *comp = e->value;
		attbl_map_add(&map, &comp->hdr.attr);
	}

	attbl_mkhdr(&map, "component", view->fgw_ctx.name);

	if (head_cb != NULL)
		head_cb(uctx, "component", (const char **)map.hdr.array);

	for(e = htsp_first(&abst->comps); e != NULL; e = htsp_next(&abst->comps, e)) {
		csch_acomp_t *comp = e->value;
		attbl_row(&map, &comp->hdr.attr, row_cb, uctx, "component", comp->name);
	}

	if (foot_cb != NULL)
		foot_cb(uctx, "component", (const char **)map.hdr.array);
	}

	/* nets */
	if ((model == ATTBLM_ABSTRACT) || (model == ATTBLM_ABSTRACT_NETS)) {

	attbl_clear(&map);
	for(e = htsp_first(&abst->nets); e != NULL; e = htsp_next(&abst->nets, e)) {
		csch_anet_t *net = e->value;
		attbl_map_add(&map, &net->hdr.attr);
	}

	attbl_mkhdr(&map, "net", view->fgw_ctx.name);

	if (head_cb != NULL)
		head_cb(uctx, "net", (const char **)map.hdr.array);

	for(e = htsp_first(&abst->nets); e != NULL; e = htsp_next(&abst->nets, e)) {
		csch_anet_t *net = e->value;
		attbl_row(&map, &net->hdr.attr, row_cb, uctx, "net", net->name);
	}

	if (foot_cb != NULL)
		foot_cb(uctx, "net", (const char **)map.hdr.array);
	}

	attbl_free(&map);
	return 0;
}

static void export_cnc_grps(attbl_map_t *map, vtp0_t *sheets, const char *title, csch_role_t role, csch_attbl_head_cb *head_cb, csch_attbl_row_cb *row_cb, csch_attbl_foot_cb *foot_cb, void *uctx)
{
	htip_entry_t *e;
	long n;

	for(n = 0; n < vtp0_len(sheets); n++) {
		csch_sheet_t *sheet = *vtp0_get(sheets, n, 0);

		for(e = htip_first(&sheet->direct.id2obj); e != NULL; e = htip_next(&sheet->direct.id2obj, e)) {
			csch_cgrp_t *grp = e->value;
			if (csch_obj_is_grp(&grp->hdr) && (grp->role == role))
				attbl_map_add(map, &grp->attr);
		}

		attbl_mkhdr(map, title, NULL);

		if (head_cb != NULL)
			head_cb(uctx, title, (const char **)map->hdr.array);

		for(e = htip_first(&sheet->direct.id2obj); e != NULL; e = htip_next(&sheet->direct.id2obj, e)) {
			csch_cgrp_t *grp = e->value;
			if (csch_obj_is_grp(&grp->hdr) && (grp->role == role)) {
				minuid_str_t uuid;
				minuid_bin2str(uuid, grp->uuid);
				attbl_row(map, &grp->attr, row_cb, uctx, title, uuid);
			}
		}

		if (foot_cb != NULL)
			foot_cb(uctx, title, (const char **)map->hdr.array);
	}
}

static int csch_attbl_export_cnc_sheets(rnd_design_t *design, vtp0_t *sheets, attbl_model_t model, csch_attbl_head_cb *head_cb, csch_attbl_row_cb *row_cb, csch_attbl_foot_cb *foot_cb, void *uctx)
{
	attbl_map_t map;

	attbl_map_init(&map);

	if ((model == ATTBLM_CONCRETE) || (model == ATTBLM_CONCRETE_SYMBOLS))
		export_cnc_grps(&map, sheets, "symbol", CSCH_ROLE_SYMBOL, head_cb, row_cb, foot_cb, uctx);

	attbl_clear(&map);

	if ((model == ATTBLM_CONCRETE) || (model == ATTBLM_CONCRETE_WIRENETS))
		export_cnc_grps(&map, sheets, "wirenet", CSCH_ROLE_WIRE_NET, head_cb, row_cb, foot_cb, uctx);

	attbl_free(&map);
	return 0;
}

static int csch_attbl_export_cnc(rnd_design_t *design, int is_prj, attbl_model_t model, csch_attbl_head_cb *head_cb, csch_attbl_row_cb *row_cb, csch_attbl_foot_cb *foot_cb, void *uctx)
{
	if (is_prj) {
		csch_project_t *prj = (csch_project_t *)design->project;
		return csch_attbl_export_cnc_sheets(design, &prj->hdr.designs, model, head_cb, row_cb, foot_cb, uctx);
	}
	else {
		int res;
		vtp0_t dummy = {0};

		vtp0_append(&dummy, design);
		res = csch_attbl_export_cnc_sheets(design, &dummy, model, head_cb, row_cb, foot_cb, uctx);
		vtp0_uninit(&dummy);
		return res;
	}
}


int csch_attbl_export(rnd_design_t *design, int is_prj, attbl_model_t model, csch_abstract_t *abst, csch_attbl_head_cb *head_cb, csch_attbl_row_cb *row_cb, csch_attbl_foot_cb *foot_cb, void *uctx)
{
	if (abst != NULL) {
		if (!is_prj)
			return -1;
		return csch_attbl_export_abst(design, model, abst, head_cb, row_cb, foot_cb, uctx);
	}

	return csch_attbl_export_cnc(design, is_prj, model, head_cb, row_cb, foot_cb, uctx);
}


/*** outfile management */
int attbl_file_init(attbl_file_ctx_t *fctx, rnd_design_t *design, const char *fn_template, int multi)
{
	char *sep;

	if (fn_template == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't export without an output file name\n");
		return -1;
	}

	sep = strrchr(fn_template, '.');

	fctx->design = design;
	fctx->multi_file = multi;
	fctx->fn.used = 0;
	gds_append_str(&fctx->fn, fn_template);

	if (sep != NULL) {
		fctx->fn_restore = sep - fn_template;
		fctx->suffix = sep;
	}
	else {
		fctx->fn_restore = fctx->fn.used;
		fctx->suffix = "";
	}

	return 0;
}

void attbl_file_uninit(attbl_file_ctx_t *fctx)
{
	if (fctx->f != NULL)
		fclose(fctx->f);
	fctx->f = NULL;
	gds_uninit(&fctx->fn);
}

int attbl_file_open(attbl_file_ctx_t *fctx, const char *type)
{
	if (fctx->multi_file) {
		if (fctx->f != NULL)
			fclose(fctx->f);
		fctx->f = NULL;
	}

	if (fctx->f == NULL) {
		if (fctx->multi_file) {
			fctx->fn.used = fctx->fn_restore;
			gds_append(&fctx->fn, '.');
			gds_append_str(&fctx->fn, type);
			gds_append_str(&fctx->fn, fctx->suffix);
		}
		fctx->f = rnd_fopen_askovr(fctx->design, fctx->fn.array, "w", NULL);
		if (fctx->f == NULL) {
			rnd_message(RND_MSG_ERROR, "Cannot open file %s for write\n", fctx->fn.array);
			return 1;
		}
	}

	return 0;
}

/*** do_export helper ***/

int attbl_do_export_begin(rnd_design_t *design, const char *sview, attbl_model_t model, csch_abstract_t *abst)
{
	csch_sheet_t *sheet = (csch_sheet_t *)design;
	int viewid = CSCH_VIEW_DEFAULT;
	int want_abst = ATTBL_MODEL_IS_ABST(model);

	if ((sview != NULL) && (*sview != '\0')) {
		viewid = csch_view_get_id((csch_project_t *)sheet->hidlib.project, sview);
		if (viewid < 0) {
			rnd_message(RND_MSG_ERROR, "No such view in the project: '%s'\n", sview);
			return -1;
		}
	}

	if (want_abst) {
		csch_abstract_init(abst);
		if (csch_compile_project((csch_project_t *)design->project, viewid, abst, 0) != 0) {
			csch_abstract_uninit(abst);
			rnd_message(RND_MSG_ERROR, "Failed to compile the project; can not export abstract model\n");
			return -1;
		}
	}

	return 0;
}

void attbl_do_export_end(csch_abstract_t *abst, attbl_model_t model)
{
	if (ATTBL_MODEL_IS_ABST(model))
		csch_abstract_uninit(abst);
}

/*** import helper ***/

RND_INLINE void free_vts(vts0_t *v)
{
	long n;
	for(n = 0; n < v->used; n++)
		free(v->array[n]);
	v->used = 0;
}

void attbl_import_begin(attbl_import_t *ictx, rnd_design_t *design)
{
	free_vts(&ictx->hdr);
	free_vts(&ictx->col);
	ictx->design = design;
	ictx->type = 0;
	ictx->got_hdr = 0;
	ictx->uuid_col = -1;
}

int attbl_import_cell(attbl_import_t *ictx, const char *text)
{
	int res = 0;

	if (!ictx->got_hdr) {
		const char *view_name_orig = NULL;

		if ((text != NULL) && (*text == '<') && (strncmp(text, "<uuid:", 6) == 0)) {
			if (strcmp(text+6, "symbol>") == 0) {
				ictx->type = 's';
				ictx->is_cnc = 1;
			}
			else if (strcmp(text+6, "wirenet>") == 0) {
				ictx->type = 'w';
				ictx->is_cnc = 1;
			}
			else if (strncmp(text+6, "component:", 10) == 0) {
				ictx->type = 'c';
				ictx->is_abst = 1;
				view_name_orig = text+6+10;
			}
			else if (strncmp(text+6, "net:", 4) == 0) {
				ictx->type = 'n';
				ictx->is_abst = 1;
				view_name_orig = text+6+4;
			}
			else {
				rnd_message(RND_MSG_ERROR, "Attribute table error: last column of the header row of a table is '%s', which is not a valid <uuid:type> string\n", text);
				ictx->type = 0;
			}
			ictx->uuid_col = ictx->hdr.used;

			/* compile abstract model for comparison when importing abstract */
			if ((ictx->is_abst) && (ictx->abst == NULL)) {
				csch_project_t *prj = (csch_project_t *)ictx->design->project;
				long n, viewid = -1;
				long cmplen = strlen(view_name_orig)-1; /* ignore the closing '>' */

				for(n = 0; n < prj->views.used; n++) {
					csch_view_t *view = prj->views.array[n];
					if ((view != NULL) && (strncmp(view->fgw_ctx.name, view_name_orig, cmplen) == 0)) {
						viewid = n;
						break;
					}
				}

				if (viewid < 0)
					rnd_message(RND_MSG_ERROR, "Attribute table: invalid view name <%s\n", view_name_orig);

				ictx->abst = malloc(sizeof(csch_abstract_t));
				csch_abstract_init(ictx->abst);
				if (csch_compile_project(prj, viewid, ictx->abst, 0) != 0) {
					rnd_message(RND_MSG_ERROR, "Failed to compile the project; can not import abstract model\n");
					csch_abstract_uninit(ictx->abst);
					free(ictx->abst);
					res = -1;
					ictx->bactx = NULL;
				}
				else { /* success compiling, set up back annotation */
					ictx->bactx = calloc(sizeof(sch_rnd_backann_t), 1);
					ictx->bactx->sheet = (csch_sheet_t *)ictx->design;
					ictx->bactx->fn = NULL; /* used only in the parser of that plugin */
				}
			}

			if (ictx->is_cnc && !ictx->cnc_frozen) {
				csch_project_t *prj = (csch_project_t *)ictx->design->project;
				long n;

				for(n = 0; n < vtp0_len(&prj->hdr.designs); n++) {
					csch_sheet_t *sheet = *vtp0_get(&prj->hdr.designs, n, 0);
					uundo_freeze_serial(&sheet->undo);
				}
				ictx->cnc_frozen = 1;
			}
		}
		vts0_append(&ictx->hdr, rnd_strdup(text));
	}
	else
		vts0_append(&ictx->col, rnd_strdup(text));

	return res;
}

#define isempty(s) ((s == NULL) || (*s == '\0'))

static int attbl_import_cnc_grp(attbl_import_t *ictx, csch_sheet_t *sheet, csch_cgrp_t *grp)
{
	long n;
	for(n = 0; n < ictx->hdr.used; n++) {
		const char *key = ictx->hdr.array[n], *newval = ictx->col.array[n];
		csch_attrib_t *attr = htsp_get(&grp->attr, key);
		if (attr != NULL) {
			const char *oldval = attr->val;
			if (isempty(oldval) && isempty(newval))
				continue;
			if ((oldval != NULL) && (newval != NULL) && (strcmp(oldval, newval) == 0))
				continue;
			if (!isempty(newval)) {
				csch_source_arg_t *src = csch_attrib_src_p("lib_attbl", "attribute table import (modify)");
				csch_attr_modify_str(sheet, grp, -CSCH_ATP_USER_DEFAULT, key, newval, src, 1);
			}
			else
				csch_attr_modify_del(sheet, grp, key, 1);
		}
		else if ((strncmp(key, "<uuid:", 6) != 0) && (!isempty(newval))) {
			csch_source_arg_t *src = csch_attrib_src_p("lib_attbl", "attribute table import (add)");
			csch_attr_modify_str(sheet, grp, -CSCH_ATP_USER_DEFAULT, key, newval, src, 1);
		}
	}
	return 0;
}


static int attbl_import_cnc(attbl_import_t *ictx, const char *uuid, csch_role_t role)
{
	vtp0_t *sheets, dummy = {0};
	htip_entry_t *e;
	long n;
	int is_prj = 1;
	minuid_bin_t muid;
	const char *rolename = "object";

	if (minuid_str2bin(muid, uuid) != 0) {
		rnd_message(RND_MSG_ERROR, "Attribute table import: invalid uuid string: '%s' (syntax error)\n(Never edit uuids!)\n", uuid);
		return -1;
	}

	if (is_prj) {
		csch_project_t *prj = (csch_project_t *)ictx->design->project;
		sheets = &prj->hdr.designs;
	}
	else {
		sheets = &dummy;
		dummy.array = (void **)&ictx->design;
		dummy.alloced = dummy.used = 1;
	}

	for(n = 0; n < vtp0_len(sheets); n++) {
		csch_sheet_t *sheet = *vtp0_get(sheets, n, 0);

		for(e = htip_first(&sheet->direct.id2obj); e != NULL; e = htip_next(&sheet->direct.id2obj, e)) {
			csch_cgrp_t *grp = e->value;
			if (csch_obj_is_grp(&grp->hdr) && (grp->role == role) && (minuid_cmp(grp->uuid, muid) == 0))
				return attbl_import_cnc_grp(ictx, sheet, grp);
		}
	}

	if (role == CSCH_ROLE_SYMBOL) rolename = "symbol";
	else if (role == CSCH_ROLE_WIRE_NET) rolename = "wirenet";

	rnd_message(RND_MSG_ERROR, "Attribute table import: %s not found for uuid string: '%s'\n(Never edit uuids!)\n", rolename, uuid);
	return -1;
}

static int attbl_import_abst_obj(attbl_import_t *ictx, csch_attribs_t *attrs, const char *uuid, int is_comp)
{
	long n;

	for(n = 0; n < ictx->hdr.used; n++) {
		const char *key = ictx->hdr.array[n];
		csch_attrib_t *attr = htsp_get(attrs, key);
		if (attr != NULL) {
			const char *oldval = attr->val, *newval = ictx->col.array[n];
			if (isempty(oldval) && isempty(newval))
				continue;
			if ((oldval != NULL) && (newval != NULL) && (strcmp(oldval, newval) == 0))
				continue;

			if (!isempty(newval)) { /* modify */
				gds_t tmp = {0};
				sch_rnd_ba_t *ba = vtba_alloc_append(&ictx->bactx->list, 1);
				long okey, oval;

				gds_append_str(&tmp, uuid); gds_append(&tmp, '\0');
				okey = tmp.used;
				gds_append_str(&tmp, key); gds_append(&tmp, '\0');
				oval = tmp.used;
				gds_append_str(&tmp, newval);

				ba->type = (is_comp ? SCH_RND_BAT_COMP_ATTR : SCH_RND_BAT_NET_ATTR);
				ba->raw = tmp.array;
				ba->value.net_attr.net = ba->raw;
				ba->value.net_attr.key = ba->raw + okey;
				ba->value.net_attr.val = ba->raw + oval;
			}
			else { /* delete */
				gds_t tmp = {0};
				sch_rnd_ba_t *ba = vtba_alloc_append(&ictx->bactx->list, 1);
				long okey;

				gds_append_str(&tmp, uuid); gds_append(&tmp, '\0');
				okey = tmp.used;
				gds_append_str(&tmp, key); gds_append(&tmp, '\0');

				ba->type = (is_comp ? SCH_RND_BAT_COMP_ATTR : SCH_RND_BAT_NET_ATTR);
				ba->raw = tmp.array;
				ba->value.net_attr.net = ba->raw;
				ba->value.net_attr.key = ba->raw + okey;
				ba->value.net_attr.val = NULL;
			}
		}
	}
	return 0;
}

static int attbl_import_abst_comp(attbl_import_t *ictx, const char *uuid)
{
	csch_acomp_t *acomp;

	if (ictx->abst == NULL) {
		rnd_message(RND_MSG_ERROR, "Attribute table: internal error: missing ictx->abst (component '%s')\n", uuid);
		return -1;
	}

	acomp = htsp_get(&ictx->abst->comps, uuid);
	if (acomp == NULL) {
		rnd_message(RND_MSG_ERROR, "Attribute table: can't find abstract component '%s'\n", uuid);
		return -1;
	}

	return attbl_import_abst_obj(ictx, &acomp->hdr.attr, uuid, 1);
}

static int attbl_import_abst_net(attbl_import_t *ictx, const char *uuid)
{
	csch_anet_t *anet;

	if (ictx->abst == NULL) {
		rnd_message(RND_MSG_ERROR, "Attribute table: internal error: missing ictx->abst (net '%s')\n", uuid);
		return -1;
	}

	anet = htsp_get(&ictx->abst->nets, uuid);
	if (anet == NULL) {
		rnd_message(RND_MSG_ERROR, "Attribute table: can't find abstract net '%s'\n", uuid);
		return -1;
	}
	return attbl_import_abst_obj(ictx, &anet->hdr.attr, uuid, 0);
}


static int attbl_import_apply(attbl_import_t *ictx)
{
	const char *uuid;
	int is_empty = 1;
	long n;

	/* ignore empty rows */
	for(n = 0; n < ictx->col.used; n++) {
		if ((ictx->col.array[n] != NULL) && (*(ictx->col.array[n]) != '\0')) {
			is_empty = 0;
			break;
		}
	}
	if (is_empty)
		return 0;

	if ((ictx->uuid_col < 0) || (ictx->uuid_col >= ictx->col.used)) {
		rnd_message(RND_MSG_ERROR, "Attribute table: can't find uuid column\n(Never delete or move the uuid column, it must be the last column of the table!)\n");
		return -1;
	}

	uuid = ictx->col.array[ictx->uuid_col];
	if (uuid == NULL) {
		rnd_message(RND_MSG_ERROR, "Attribute table: can't apply row with empty uuid\n(Never delete the uuid!)\n");
		return -1;
	}

	switch(ictx->type) {
		case 's': /* symbol */
			return attbl_import_cnc(ictx, uuid, CSCH_ROLE_SYMBOL);
		case 'w': /* wirenet */
			return attbl_import_cnc(ictx, uuid, CSCH_ROLE_WIRE_NET);
		case 'c': /* component */
			return attbl_import_abst_comp(ictx, uuid);
		case 'n': /* net */
			return attbl_import_abst_net(ictx, uuid);
	}

	rnd_message(RND_MSG_ERROR, "Attribute table: internal error: invalid object type string\n");
	return -1;
}

void attbl_import_endline(attbl_import_t *ictx)
{
	if (ictx->got_hdr) {
		attbl_import_apply(ictx);
		free_vts(&ictx->col);
	}
	else {
		ictx->got_hdr = 1;
	}
}

void attbl_import_uninit(attbl_import_t *ictx)
{
	if (ictx->is_abst) {
		sch_rnd_backann_postproc(ictx->bactx);
		sch_rnd_backann_present(ictx->bactx);
		csch_abstract_uninit(ictx->abst);
		/* do not free(ictx->bactx) - ownershit taken over by present() */
		free(ictx->abst);
		ictx->abst = NULL;
		ictx->is_abst = 0;
		ictx->bactx = NULL;
	}

	if (ictx->cnc_frozen) {
		csch_project_t *prj = (csch_project_t *)ictx->design->project;
		long n;

		for(n = 0; n < vtp0_len(&prj->hdr.designs); n++) {
			csch_sheet_t *sheet = *vtp0_get(&prj->hdr.designs, n, 0);
			uundo_unfreeze_serial(&sheet->undo);
			uundo_inc_serial(&sheet->undo);
		}

		ictx->cnc_frozen = 0;
	}
	ictx->is_cnc = 0;

	free_vts(&ictx->hdr);
	free_vts(&ictx->col);
	vts0_uninit(&ictx->hdr);
	vts0_uninit(&ictx->col);
}

/*** external editor ***/


static void extedit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	rnd_design_t *dsg = rnd_gui->get_dad_design(hid_ctx);
	rnd_hid_export_opt_func_dad_t *dad = caller_data;
	int wcmd = attr->max_val, res, n;
	const char *templ = dad->dlg[wcmd].val.str, *outfile_save;
	char *cmd, *tmpfn, *tmpfnn;
	rnd_build_argfn_t args = {0};
	const rnd_export_opt_t *opt = attr->user_data;
	const char *ftype = opt->default_val.str;
	rnd_hid_attribute_t *ebtn = NULL, *aoutfile = NULL;

	/* find the "outfile" field, before wcmd */
	for(n = wcmd; n >= 0; n--) {
		rnd_hid_attribute_t *a = dad->dlg+n;
		if ((a->type == RND_HATT_STRING) && (a->name != NULL) && (strcmp(a->name, "outfile") == 0)) {
			aoutfile = a;
			break;
		}
	}

	/* find the "Export!" button, after wcmd */
	for(n = wcmd; n < dad->dlg_len; n++) {
		rnd_hid_attribute_t *a = dad->dlg+n;
		if ((a->type == RND_HATT_BUTTON) && (a->val.str != NULL) && (strcmp(a->val.str, "Export!") == 0)) {
			ebtn = a;
			break;
		}
	}

	if (ebtn == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: can't find the 'Export!' button\n");
		return;
	}

	if (aoutfile == NULL) {
		rnd_message(RND_MSG_ERROR, "Internal error: can't find the 'outfule' entry\n");
		return;
	}

	/* render temp file name - extension is ftype */
	tmpfnn = rnd_concat("attbl.", ftype, NULL);
	tmpfn = rnd_tempfile_name_new(tmpfnn);
	free(tmpfnn);

	/* render command line from template */
	args.design = dsg;
	args.params['f' - 'a'] = tmpfn;
	cmd = rnd_build_argfn(templ, &args);

	rnd_trace("extedit! %d '%s' '%s' dsg=%p ftype='%s' tmpfn='%s'\n", wcmd, templ, cmd, dsg, ftype, tmpfn);

	/* export: replace outfile temporarily and "click" the export button */
	outfile_save = aoutfile->val.str;
	aoutfile->val.str = tmpfn;
	ebtn->change_cb(hid_ctx, caller_data, ebtn);
	aoutfile->val.str = outfile_save;

	/* run external command and import result */
	res = rnd_system(dsg, cmd);
	if (res == 0)
		rnd_actionva(dsg, "importattbl", tmpfn, ftype, NULL);
	else
		rnd_message(RND_MSG_ERROR, "External editor returned error, not importing\n");

	/* clean up */
	free(cmd);
	rnd_tempfile_unlink(tmpfn);
}


void attbl_extedit_dad(rnd_hid_export_opt_func_action_t act, void *call_ctx, const rnd_export_opt_t *opt, rnd_hid_attr_val_t *val)
{
	rnd_hid_export_opt_func_dad_t *dad = call_ctx;
	int wcmd;
	const char *editor_cmd_def = conf_lib_attbl.plugins.lib_attbl.cmd_template;

	switch(act) {
		case RND_HIDEOF_USAGE:
			/* not available - GUI-only feature */
			break;
		case RND_HIDEOF_DAD:

			RND_DAD_BEGIN_VBOX(dad->dlg);
				RND_DAD_LABEL(dad->dlg, "");
				RND_DAD_BEGIN_VBOX(dad->dlg);
					RND_DAD_COMPFLAG(dad->dlg, RND_HATF_EXPFILL | RND_HATF_FRAME);
					RND_DAD_LABEL(dad->dlg, "External editor instead of export:");
					RND_DAD_BEGIN_HBOX(dad->dlg);
						RND_DAD_STRING(dad->dlg);
							RND_DAD_DEFAULT_PTR(dad->dlg, rnd_strdup(editor_cmd_def));
							wcmd = RND_DAD_CURRENT(dad->dlg);
						RND_DAD_BUTTON(dad->dlg, "Run");
							RND_DAD_CHANGE_CB(dad->dlg, extedit_cb);
							dad->dlg[dad->dlg_len - 1].user_data = (void *)opt;
							dad->dlg[dad->dlg_len - 1].max_val = wcmd;
					RND_DAD_END(dad->dlg);
				RND_DAD_END(dad->dlg);
				RND_DAD_LABEL(dad->dlg, "");
			RND_DAD_END(dad->dlg);
			break;
	}
}


/*** plugin ***/

int pplg_check_ver_lib_attbl(int ver_needed) { return 0; }

void pplg_uninit_lib_attbl(void)
{
	rnd_conf_plug_unreg("plugins/lib_attbl/", lib_attbl_conf_internal, lib_attbl_cookie);
}

int pplg_init_lib_attbl(void)
{
	RND_API_CHK_VER;

	rnd_conf_plug_reg(conf_lib_attbl, lib_attbl_conf_internal, lib_attbl_cookie);
#define conf_reg(field,isarray,type_name,cpath,cname,desc,flags) \
	rnd_conf_reg_field(conf_lib_attbl, field,isarray,type_name,cpath,cname,desc,flags);
#include "lib_attbl_conf_fields.h"

	return 0;
}

