/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,2019,2023 Tibor 'Igor2' Palinkas
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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 "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cnc_grp.h"
#include "cnc_obj.h"
#include "cnc_any_obj.h"
#include "plug_io.h"
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_fs.h>
#include "project.h"
#include "integrity.h"
#include <librnd/core/safe_fs.h>
#include <librnd/core/compat_lrealpath.h>

vtp0_t csch_ios;

void csch_plug_io_register(const csch_plug_io_t *io)
{
	vtp0_append(&csch_ios, (void *)io);
}

void csch_plug_io_unregister(const csch_plug_io_t *io)
{
	long n;
	for(n = 0; n < csch_ios.used; n++)
		if (csch_ios.array[n] == io)
			csch_ios.array[n] = NULL;
}

void csch_plug_io_uninit(void)
{
	vtp0_uninit(&csch_ios);
}

typedef struct {
	int prio;
	csch_plug_io_t *io;
} prio_t;
#define GVT(x) vtpr_ ## x
#define GVT_ELEM_TYPE prio_t
#define GVT_SIZE_TYPE int
#define GVT_DOUBLING_THRS 32
#define GVT_START_SIZE 8
#define GVT_FUNC
#define GVT_SET_NEW_BYTES_TO 0
#include <genvector/genvector_impl.h>
#define GVT_REALLOC(vect, ptr, size)  realloc(ptr, size)
#define GVT_FREE(vect, ptr)           free(ptr)
#include <genvector/genvector_impl.c>

static int prio_cmp(const void *d1, const void *d2)
{
	const prio_t *p1 = d1, *p2 = d2;
	if (p1->prio <= p2->prio) return 1;
	return -1;
}

typedef enum {
	LIST_LOAD,
	LIST_SAVE,
	LIST_EXPORT,
	LIST_IMPORT
} io_list_dir_t;

static void csch_plug_io_list(vtpr_t *res, const char *fn, const char *fmt, csch_plug_io_type_t type, io_list_dir_t dir)
{
	int n, p;
	prio_t *pr;

	for(n = 0; n < vtp0_len(&csch_ios); n++) {
		csch_plug_io_t *io = csch_ios.array[n];
		if (io == NULL) continue;
		switch(dir) {
			case LIST_LOAD:
				if (io->load_prio == NULL) continue;
				p = io->load_prio(fn, fmt, type);
				break;
			case LIST_SAVE:
				if (io->save_prio == NULL) continue;
				p = io->save_prio(fn, fmt, type);
				break;
			case LIST_EXPORT:
				if (io->export_prio == NULL) continue;
				p = io->export_prio(fn, fmt, type);
				break;
			case LIST_IMPORT:
				if (io->import_prio == NULL) continue;
				p = io->import_prio(fn, fmt, type);
				break;
		}

		if (p > 0) {
			pr = vtpr_alloc_append(res, 1);
			pr->prio = p;
			pr->io = io;
		}
	}
	qsort(res->array, vtpr_len(res), sizeof(prio_t), prio_cmp);
}

static int load_postproc_sheet(csch_sheet_t *sheet)
{
	if (sheet->direct.hdr.oid != CSCH_TOP_OID_DIRECT) {
		rnd_msg_error("Load error: top group 'direct' doesn't have the correct OID (has %d, expected %d\n", sheet->direct.hdr.oid, CSCH_TOP_OID_DIRECT);
		return -1;
	}
	if (sheet->indirect.hdr.oid != CSCH_TOP_OID_INDIRECT) {
		rnd_msg_error("Load error: top group 'indirect' doesn't have the correct OID (has %d, expected %d\n", sheet->indirect.hdr.oid, CSCH_TOP_OID_INDIRECT);
		return -1;
	}
	csch_cobj_redraw_freeze(sheet);
	csch_cgrp_render_all(sheet, &sheet->direct);
	csch_cobj_update(sheet, &sheet->direct.hdr, 1);
	csch_cobj_redraw_unfreeze(sheet);
	return 0;
}

/* if proj is not NULL and sheet is NULL, allocate a new sheet */
csch_sheet_t *csch_load_sheet_io(csch_project_t *proj, csch_sheet_t *sheet, const char *load_fn, const char *real_fn, const char *fmt, int is_buffer, FILE *optf)
{
	int n;
	vtpr_t prios;
	FILE *f;

	if (optf == NULL) {
		f = rnd_fopen(NULL, real_fn, "r");
		if (f == NULL) {
			TODO("error reporting");
			return NULL;
		}
	}
	else
		f = optf;

	vtpr_init(&prios);
	csch_plug_io_list(&prios, real_fn, fmt, (is_buffer ? CSCH_IOTYP_BUFFER : CSCH_IOTYP_SHEET), LIST_LOAD);

#if 0
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		rnd_trace("*** %d %s\n", pr->prio, pr->io->name);
	}
#endif

	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		int alloced = 0, load_res;

		rewind(f);
		if (pr->io->test_parse != NULL) {
			if (pr->io->test_parse(f, real_fn, fmt, (is_buffer ? CSCH_IOTYP_BUFFER : CSCH_IOTYP_SHEET)) != 0)
				continue;
			rewind(f);
		}

		if ((proj != NULL) && (sheet == NULL) && (!is_buffer)) {
			sheet = csch_sheet_alloc(proj);
			alloced = 1;
		}
		if (sheet == NULL)
			break;

		csch_cobj_redraw_freeze(sheet);
		if (is_buffer) {
			load_res = pr->io->load_buffer(f, real_fn, fmt, sheet);
		}
		else if (pr->io->load_sheet == NULL)
			load_res = -1;
		else {
			rnd_event(&sheet->hidlib, RND_EVENT_LOAD_PRE, "s", real_fn);
			load_res = pr->io->load_sheet(f, real_fn, fmt, sheet);
			if (load_res != 0) {
				sheet->non_graphical = 0;
				sheet->non_graphical_impl = NULL;
				sheet->non_graphical_data = NULL;
			}
			rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, "si", real_fn, load_res);
		}
		csch_cobj_redraw_unfreeze(sheet);

		if (load_res == 0) {
			free(sheet->hidlib.fullpath);
			sheet->hidlib.fullpath = rnd_strdup(real_fn);
			free(sheet->hidlib.loadname);
			sheet->hidlib.loadname = rnd_strdup(load_fn);
			free(sheet->loadfmt);
			sheet->loadfmt = (fmt == NULL ? NULL : rnd_strdup(fmt));
			sheet->load_pending = 0;
			load_postproc_sheet(sheet);
			break;
		}
		if (alloced) {
			csch_sheet_free(sheet);
			sheet = NULL;
		}
		else
			csch_sheet_uninit(sheet);
	}
	vtpr_uninit(&prios);

	if (optf == NULL)
		fclose(f);

	return sheet;
}

int csch_test_parse_file(rnd_design_t *hl, FILE *f, const char *real_fn, csch_plug_io_type_t type)
{
	long n;
	int res = 0;

	for(n = 0; n < vtp0_len(&csch_ios); n++) {
		csch_plug_io_t *io = csch_ios.array[n];

		rewind(f);
		if ((io->test_parse != NULL) && (io->test_parse(f, real_fn, NULL, type) == 0)) {
			res = 1;
			break;
		}
	}

	return res;
}

int csch_test_parse_fn(rnd_design_t *hl, const char *fn, csch_plug_io_type_t type)
{
	int res;
	FILE *f;

	f = rnd_fopen(hl, fn, "r");
	if (f == NULL)
		return -1;

	res = csch_test_parse_file(hl, f, fn, type);

	fclose(f);
	return res;
}

void *csch_test_parse_bundled(rnd_design_t *hl, const char *real_fn, csch_plug_io_type_t type, FILE **f_out, void **io_out)
{
	long n;
	FILE *f = NULL;
	void *cookie = NULL;

	*io_out = NULL;

	for(n = 0; n < vtp0_len(&csch_ios); n++) {
		csch_plug_io_t *io = csch_ios.array[n];

		if (io->test_parse_bundled == NULL)
			continue;

		if (f == NULL) {
			f = rnd_fopen(hl, real_fn, "r");
			if (f == NULL)
				return NULL;
		}
		else
			rewind(f);

		cookie = io->test_parse_bundled(f, real_fn, NULL, type);
		if (cookie != NULL) {
			*io_out = io;
			break;
		}
	}

	*f_out = f;
	return cookie;
}

static int load_postproc_project(csch_project_t *proj)
{
	return 0;
}

csch_sheet_t *csch_load_bundled_sheets(void *cookie, void *io_, FILE *f, const char *fn, void (*post_load_cb)(csch_sheet_t *))
{
	csch_plug_io_t *io = io_;
	csch_project_t *prj;
	csch_sheet_t *res = NULL;

	prj = csch_project_alloc();
	prj->hdr.loadname = rnd_strdup(fn);
	load_postproc_project(prj);

	for(;;) {
		int r;
		csch_sheet_t *sheet = csch_sheet_alloc(prj);
		rnd_conf_state_t *ncs;

		rnd_conf_multi_pre_new_design(&ncs);
		rnd_conf_reset(RND_CFR_DESIGN, fn);
		rnd_conf_merge_all(NULL); /* get the conf tree filled in - some io plugins depend on config for the load/parse */

		csch_cobj_redraw_freeze(sheet);
		r = io->load_sheet_bundled(cookie, f, fn, sheet);
		csch_cobj_redraw_unfreeze(sheet);

		if (r >= 0) {
			rnd_project_append_design(&prj->hdr, &sheet->hidlib);
			if (res == NULL)
				res = sheet; /* return first sheet loaded */
		}

		rnd_conf_multi_post_new_design(&ncs, &sheet->hidlib);
		rnd_conf_state_new_design(&sheet->hidlib);
		rnd_conf_merge_all(NULL); /* to get the project file applied */

		post_load_cb(sheet);
		rnd_conf_state_save(sheet->hidlib.saved_rnd_conf);

		if (r != 0) /* stop iteration on error or eof */
			break;
	}

	io->end_bundled(cookie, fn);

	return res;
}


csch_sheet_t *csch_load_sheet(csch_project_t *proj, const char *load_fn, const char *fmt, int *already_in_proj, FILE *optf)
{
	int n;
	csch_sheet_t *sheet = NULL;
	char *real_fn;

	if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */

	if (already_in_proj != NULL)
		*already_in_proj = 0;

	real_fn = rnd_lrealpath(load_fn);
	if (real_fn == NULL) {
		TODO("error reporting: already loaded");
		return NULL;
	}
	for(n = 0; n < proj->hdr.designs.used; n++) {
		csch_sheet_t *sh = proj->hdr.designs.array[n];
		if (sh->hidlib.fullpath == NULL) continue;
		if (strcmp(sh->hidlib.fullpath, real_fn) == 0) {
			if (already_in_proj != NULL)
				*already_in_proj = 1;
			if (!sh->load_pending) {
				free(real_fn);
				return sh;
			}
			sheet = sh;
			break;
		}
	}

	sheet = csch_load_sheet_io(proj, sheet, load_fn, real_fn, fmt, 0, optf);
	if ((sheet != NULL) && (sheet->hidlib.project != NULL) && (already_in_proj != NULL))
		*already_in_proj = 1;

	free(real_fn);
	if (sheet != NULL)
		CSCH_INTEGRITY_SHEET_AUTO(sheet);
	return sheet;
}

void csch_revert_sheet(csch_sheet_t *sheet, csch_sheet_t *(*sheet_new)(csch_sheet_t *))
{
	csch_sheet_t *orig_sheet = sheet;
	char *orig_fmt = sheet->loadfmt;
	char *orig_real_fn = sheet->hidlib.fullpath;
	char *orig_load_fn = sheet->hidlib.loadname;
	csch_project_t *proj = (csch_project_t *)sheet->hidlib.project;
	int defsheet = 0, newed = 0;

	if (orig_real_fn == NULL) {
		/* this happens on a "new sheet", when default sheet is loaded; revert should load that */
		orig_real_fn = rnd_strdup_null(orig_load_fn == NULL ? sheet->newname : orig_load_fn);
		orig_load_fn = rnd_strdup_null(orig_real_fn);
		defsheet = 1;
	}

	rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_PREUNLOAD, NULL);
	sheet->hidlib.loadname = NULL;
	sheet->loadfmt = NULL;
	sheet->hidlib.fullpath = NULL;
	csch_sheet_uninit(sheet);
	csch_sheet_init(sheet, proj);

	sheet = csch_load_sheet_io(NULL, orig_sheet, orig_load_fn, orig_real_fn, orig_fmt, 0, NULL);
	if (sheet == NULL) {
		sheet = sheet_new(orig_sheet);
		newed = 1;
	}
	free(orig_fmt);
	free(orig_real_fn);
	free(orig_load_fn);

	if (defsheet) {
		free(sheet->hidlib.fullpath);
		sheet->hidlib.fullpath = NULL;
		sheet->newname = sheet->hidlib.loadname;
		sheet->hidlib.loadname = NULL;
	}

	if (!newed)
		rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTLOAD, NULL);
	rnd_event(&sheet->hidlib, RND_EVENT_LOAD_POST, NULL);
}



csch_project_t *csch_load_project(const char *load_fn, const char *fmt, int with_sheets)
{
	vtpr_t prios;
	int n;
	FILE *f;
	csch_project_t *proj = NULL;
	char *real_fn;

	if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */

	real_fn = rnd_lrealpath(load_fn);
	if (real_fn == NULL) {
		TODO("error reporting");
		return NULL;
	}
	f = rnd_fopen(NULL, real_fn, "r");
	if (f == NULL) {
		TODO("error reporting");
		free(real_fn);
		return NULL;
	}

	vtpr_init(&prios);
	csch_plug_io_list(&prios, real_fn, fmt, CSCH_IOTYP_PROJECT, LIST_LOAD);
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];

		rewind(f);
		if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, real_fn, fmt, CSCH_IOTYP_PROJECT) != 0))
			continue;

		proj = csch_project_alloc();
		if (pr->io->load_project(f, real_fn, fmt, proj, with_sheets) == 0) {
			free(proj->hdr.fullpath);
			proj->hdr.fullpath = real_fn;
			real_fn = NULL;
			free(proj->hdr.loadname);
			proj->hdr.loadname = rnd_strdup(load_fn);
			load_postproc_project(proj);
			break;
		}
		csch_project_free(proj);
		proj = NULL;
	}
	vtpr_uninit(&prios);
	free(real_fn);
	fclose(f);
	return proj;
}

static int csch_save_sheet_as_sym(prio_t *pr, const char *fn, const char *fmt, const csch_sheet_t *sheet)
{
	return pr->io->save_grp(fn, fmt, &sheet->direct, 0);
}

typedef enum { SS_SAVE_SHEET, SS_SAVE_SHEET_BACKUP, SS_EXPORT_SHEET, SS_SAVE_BUFFER, SS_SAVE_SHEET_AS_SYM } save_sheet_t;
static int csch_export_or_save_sheet(csch_sheet_t *sheet, const char *fn_, const char *fmt, save_sheet_t stype, int preserve_changed)
{
	vtpr_t prios;
	int n, ret = -1, len, preserve_name = 0;
	char *fn, *ext = NULL;

	if ((fmt == NULL) || (*fmt == '\0'))
		return -1;

	if (stype == SS_SAVE_SHEET_BACKUP)
		preserve_name = 1;

	if (sheet->is_symbol && ((stype == SS_SAVE_SHEET) || (stype == SS_SAVE_SHEET_BACKUP)))
		stype = SS_SAVE_SHEET_AS_SYM;

	len = strlen(fn_);
	fn = malloc(len+1);
	memcpy(fn, fn_, len+1);
	if (fn[len-1] == '*')
		ext = fn + len - 1;

	vtpr_init(&prios);
	csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_SHEET, ((stype == SS_EXPORT_SHEET) ? LIST_EXPORT : LIST_SAVE));
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		if (ext != NULL) {
			switch(stype) {
				case SS_EXPORT_SHEET:
					if (pr->io->ext_export_sheet != NULL)
						strcpy(ext, pr->io->ext_export_sheet);
					else
						*ext = '\0';
					break;
				case SS_SAVE_SHEET:
				case SS_SAVE_SHEET_BACKUP:
					if (pr->io->ext_save_sheet != NULL)
						strcpy(ext, pr->io->ext_save_sheet);
					else
						*ext = '\0';
					break;
				case SS_SAVE_SHEET_AS_SYM:
					if (pr->io->ext_save_grp != NULL)
						strcpy(ext, pr->io->ext_save_grp);
					else
						*ext = '\0';
					break;
				case SS_SAVE_BUFFER:
					if (pr->io->ext_save_buffer != NULL)
						strcpy(ext, pr->io->ext_save_buffer);
					else
						*ext = '\0';
					break;
			}
		}


		switch(stype) {
			case SS_EXPORT_SHEET:
				ret = pr->io->export_sheet(fn, fmt, sheet);
				break;
			case SS_SAVE_SHEET:
			case SS_SAVE_SHEET_BACKUP:
			case SS_SAVE_SHEET_AS_SYM:
				sheet->saving = 1;
				rnd_event(&sheet->hidlib, RND_EVENT_SAVE_PRE, "s", fmt);

				if (stype == SS_SAVE_SHEET_AS_SYM)
					ret = csch_save_sheet_as_sym(pr, fn, fmt, sheet);
				else
					ret = pr->io->save_sheet(fn, fmt, sheet);

				if (!preserve_name && ((stype == SS_SAVE_SHEET) || (stype == SS_SAVE_SHEET_AS_SYM))) {
					char *newname = rnd_lrealpath(fn);
					if (newname == NULL) {
						rnd_message(RND_MSG_ERROR, "Failed to realpath(%s)\nMight be write permission problems\n", fn);
						ret = -1;
						goto error;
					}
					if ((sheet->hidlib.fullpath == NULL) || (strcmp(sheet->hidlib.fullpath, newname) != 0)) {
						free(sheet->hidlib.fullpath);
						sheet->hidlib.fullpath = newname;
						free(sheet->hidlib.loadname);
						sheet->hidlib.loadname = rnd_strdup(rnd_basename(newname));
					}
					else
						free(newname);
				}
				if (preserve_changed == 0)
					rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_POSTSAVE, NULL); /* updates the info bar */
				sheet->saving = 0;
				rnd_event(&sheet->hidlib, RND_EVENT_SAVE_POST, "si", fmt, ret);
				if ((ret == 0) && !preserve_changed)
					csch_sheet_set_changed(sheet, 0);
				break;

			case SS_SAVE_BUFFER:
				ret = pr->io->save_buffer(fn, fmt, sheet);
				break;
		}

		if (ret == 0)
			break;
	}

	error:;
	free(fn);
	vtpr_uninit(&prios);
	return ret;
}

int csch_export_sheet(csch_sheet_t *sheet, const char *fn, const char *fmt)
{
	return csch_export_or_save_sheet(sheet, fn, fmt, SS_EXPORT_SHEET, 0);
}

int csch_save_sheet(csch_sheet_t *sheet, const char *fn, const char *fmt)
{
	return csch_export_or_save_sheet(sheet, fn, fmt, SS_SAVE_SHEET, 0);
}

int csch_save_sheet_backup(csch_sheet_t *sheet, const char *fn, const char *fmt)
{
	return csch_export_or_save_sheet(sheet, fn, fmt, SS_SAVE_SHEET_BACKUP, 1);
}


int csch_export_project_abst(csch_abstract_t *abs, const char *fn_, const char *fmt, rnd_hid_attr_val_t *options)
{
	vtpr_t prios;
	int n, ret = -1, len;
	char *fn, *ext = NULL;

	if ((fmt == NULL) || (*fmt == '\0'))
		return -1;

	len = strlen(fn_);
	fn = malloc(len+256); /* leave "enough room" for the extension */
	memcpy(fn, fn_, len+1); /* copy \0 as well */
	if (fn[len-1] == '*')
		ext = fn + len - 1;

	vtpr_init(&prios);
	csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_NETLIST, LIST_EXPORT);
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		if (ext != NULL) {
			if (pr->io->ext_export_project != NULL)
				strcpy(ext, pr->io->ext_export_project);
			else
				*ext = '\0';
		}
		ret = pr->io->export_project_abst(fn, fmt, abs, options);
		if (ret == 0)
			break;
	}

	free(fn);
	vtpr_uninit(&prios);
	return ret;
}

int csch_save_grp(csch_cgrp_t *grp, const char *fn_, const char *fmt, int inhibit_uuid)
{
	vtpr_t prios;
	int n, ret = -1, len;
	char *fn, *ext = NULL;

	if ((fmt == NULL) || (*fmt == '\0'))
		return -1;

	len = strlen(fn_);
	fn = malloc(len+1);
	memcpy(fn, fn_, len+1);
	if (fn[len-1] == '*')
		ext = fn + len - 1;

	vtpr_init(&prios);
	csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_SHEET, LIST_SAVE);
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		if (ext != NULL) {
			if (pr->io->ext_save_grp != NULL)
				strcpy(ext, pr->io->ext_save_grp);
			else
				*ext = '\0';
		}
		ret = pr->io->save_grp(fn, fmt, grp, inhibit_uuid);
		if (ret == 0)
			break;
	}

	free(fn);
	vtpr_uninit(&prios);
	return ret;
}

csch_cgrp_t *csch_load_grp(csch_sheet_t *dst, const char *load_fn, const char *fmt, const char *subsymname)
{
	vtpr_t prios;
	int n;
	FILE *f;
	char *real_fn;
	csch_cgrp_t *res = NULL;

	if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */

	real_fn = rnd_lrealpath(load_fn);
	if (real_fn == NULL) {
		TODO("error reporting: not found");
		return NULL;
	}

	f = rnd_fopen(NULL, real_fn, "r");
	if (f == NULL) {
		TODO("error reporting");
		free(real_fn);
		return NULL;
	}

	vtpr_init(&prios);
	csch_plug_io_list(&prios, real_fn, fmt, CSCH_IOTYP_SHEET, LIST_LOAD);

#if 0
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];
		rnd_trace("*** %d %s\n", pr->prio, pr->io->name);
	}
#endif

	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];

		if (pr->io->load_grp == NULL)
			continue;

		rewind(f);
		if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, real_fn, fmt, CSCH_IOTYP_GROUP) != 0))
			continue;

		rewind(f);
		res = pr->io->load_grp(f, real_fn, fmt, subsymname, dst);
	}
	vtpr_uninit(&prios);
	free(real_fn);
	fclose(f);

	if (res != NULL)
		res->file_name = rnd_strdup(load_fn);

	return res;
}

int csch_load_buffer(csch_sheet_t *buffer, const char *load_fn, const char *fmt)
{
	csch_sheet_t *rsheet;
	char *real_fn;

	if ((fmt != NULL) && (*fmt == '\0')) fmt = NULL; /* reduce the number of cases */

	real_fn = rnd_lrealpath(load_fn);
	if (real_fn == NULL) {
		rnd_message(RND_MSG_ERROR, "csch_load_buffer(): can't find real path for '%s'\n", load_fn);
		return -1;
	}

	rsheet = csch_load_sheet_io(NULL, buffer, load_fn, real_fn, fmt, 1, NULL);
	free(real_fn);
	return (rsheet == NULL) ? -1 : 0;
}

int csch_save_buffer(csch_sheet_t *buffer, const char *fn, const char *fmt)
{
	return csch_export_or_save_sheet(buffer, fn, fmt, SS_SAVE_BUFFER, 0);
}

int csch_import_attbl(rnd_design_t *dsg, const char *fn, const char *fmt)
{
	vtpr_t prios;
	FILE *f;
	long n;
	int res = -1;

	f = rnd_fopen(dsg, fn, "r");
	if (f == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't open '%s' for read\n", fn);
		return -1;
	}

	vtpr_init(&prios);
	csch_plug_io_list(&prios, fn, fmt, CSCH_IOTYP_ATTBL, LIST_IMPORT);
	for(n = 0; n < vtpr_len(&prios); n++) {
		prio_t *pr = &prios.array[n];

		if (pr->io->import_attbl == NULL)
			continue;

		rewind(f);
		if ((pr->io->test_parse != NULL) && (pr->io->test_parse(f, fn, fmt, CSCH_IOTYP_ATTBL) != 0))
			continue;

		rewind(f);
		if (pr->io->import_attbl(f, fn, fmt, (csch_sheet_t *)dsg) == 0) {
			res = 0;
			goto done;
		}
	}

	rnd_message(RND_MSG_ERROR, "Failed to load '%s':\nNone of the attbl plugins could recognize the file format\n", fn);

	done:;
	fclose(f);
	return res;
}

