/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - eeschema file format support
 *  Copyright (C) 2022..2024 Tibor 'Igor2' Palinkas
 *  Copyright (C) 2024..2025 Aron Barath
 *
 *  (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
 */


#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <genht/ht_utils.h>
#include <libcschem/config.h>
#include <libcschem/cnc_pen.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_grp_child.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/project.h>
#include <libcschem/util_parse.h>
#include <libcschem/util_loclib.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/paths.h>
#include <librnd/core/math_helper.h>
#include <gensexpr/gsxl.h>
#include <sch-rnd/buffer.h>

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

#include "io_eeschema_conf.h"
extern conf_io_eeschema_t io_eeschema_conf;

#include "read.h"

/*#define dbg_printf rnd_trace*/

#ifndef dbg_printf
#define dbg_printf nope_printf
static int nope_printf(const char *fmt, ...) { return 0; }
#endif

#define error(ctx, args) \
	do { \
		rnd_message(RND_MSG_ERROR, "io_eeschema parse error at %s:%ld:\n", ctx->fn, ctx->lineno); \
		rnd_msg_error args; \
	} while(0)

#define TEXT_SYM_ATTR_HANDLED(text) ((text)->tmp[0].l)
#define TEXT_SYM_ATTR_HIDDEN(text)  ((text)->tmp[1].l)

struct cache_sym_s;

typedef struct read_ctx_s {
	FILE *f;
	const char *fn;
	long ver;            /* file version */
	long lineno;
	int level;

	csch_sheet_t *sheet;
	csch_project_t *proj;
	csch_cgrp_t* loclib;
	gsxl_dom_t dom;
	htsp_t syms;
	float page_width;
	float page_height;
	float page_left_margin;
	float page_right_margin;
	float page_top_margin;
	float page_bottom_margin;
	csch_cgrp_t* titleblock;
	csch_coord_t font_height;

	struct cache_sym_s* cur_libsym;
	struct cache_sym_s* cur_libsym0; /* unit of the current libsym */
	csch_cgrp_t* cur_sym;
	csch_coord_t cur_sym_x, cur_sym_y; /* in sch-rnd coord */
	int cur_sym_rot;
	unsigned cur_sym_mirx : 1;
	unsigned cur_sym_miry : 1;
	unsigned cur_sym_exclude_from_sim : 1; /* set to 1 if schsym had this */
	unsigned cur_sym_in_bom : 1; /* set to 1 if schsym had this */
	unsigned cur_sym_on_board : 1; /* set to 1 if schsym had this */

	csch_alien_read_ctx_t alien;
} read_ctx_t;

typedef struct cache_sym_s {
	char* sym_name; /* compiled name from lib_name and ":unit" */
	gsxl_node_t* name; /* name node */
	gsxl_node_t* sym_data; /* first "symbol" data node from "lib_symbols" */
	csch_cgrp_t* grp;
	gsxl_node_t* exclude_from_sim_node;
	gsxl_node_t* in_bom_node;
	gsxl_node_t* on_board_node;
	float pin_names_offset;
	float pin_numbers_offset;
	unsigned exclude_from_sim : 1; /* raw attrib from sym data */
	unsigned in_bom : 1; /* raw attrib from sym data */
	unsigned on_board : 1; /* raw attrib from sym data */
	unsigned show_pin_numbers : 1; /* DOM: (pin_numbers hide) */
	unsigned show_pin_names : 1; /* DOM: (pin_names hide) */
} cache_sym_t;

typedef struct {
	const char* name;
	int (*parse)(read_ctx_t* ctx, csch_cgrp_t* dst, gsxl_node_t* node);
} dispatch_t;

/* TODO: implement low level parsers here; each function should get a
   read_ctx_t* as first arg */

/*** file level parsing and entry points ***/

static void eechema_error(read_ctx_t* ctx, gsxl_node_t* node, const char* fmt, ...)
{
	gds_t str;
	va_list ap;

	gds_init(&str);
	rnd_append_printf(&str, "io_eeschema parse error at %s:%ld:%ld: ",
		ctx->fn, 1+(long)node->line, 1+(long)node->col);

	va_start(ap, fmt);
	rnd_safe_append_vprintf(&str, 0, fmt, ap);
	va_end(ap);

	gds_append(&str, '\n');

	rnd_message(RND_MSG_ERROR, "%s", str.array);

	gds_uninit(&str);
}

static gsx_parse_res_t eechema_parse_file(FILE *FP, gsxl_dom_t *dom)
{
	int c;
	gsx_parse_res_t res;

	gsxl_init(dom, gsxl_node_t);
	dom->parse.line_comment_char = '#';
	do {
		c = fgetc(FP);
	} while((res = gsxl_parse_char(dom, c)) == GSX_RES_NEXT);

	if (res == GSX_RES_EOE) {
		/* compact and simplify the tree */
		gsxl_compact_tree(dom);
	}

	return res;
}

static gsx_parse_res_t eechema_parse_string(const char* str, gsxl_dom_t* dom)
{
	int c;
	gsx_parse_res_t res;

	gsxl_init(dom, gsxl_node_t);
	dom->parse.line_comment_char = '#';

	do
	{
		c = *str++;

		if(c==0)
		{
			c = EOF;
			--str;
		}
	}
	while((res = gsxl_parse_char(dom, c)) == GSX_RES_NEXT);

	if(res==GSX_RES_EOE)
	{
		/* compact and simplify the tree */
		gsxl_compact_tree(dom);
	}

	return res;
}

static void alien_setup(read_ctx_t *ctx)
{
	ctx->alien.sheet = ctx->sheet;
	ctx->alien.coord_factor = io_eeschema_conf.plugins.io_eeschema.coord_mult;
	ctx->alien.fmt_prefix = "io_eeschema";
	ctx->alien.flip_y = 1;
}

static csch_source_arg_t* make_src_attrib(read_ctx_t* const ctx,
	gsxl_node_t* const node)
{
	return csch_attrib_src_c(ctx->fn, 1+(long)node->line, 1+(long)node->col,
		NULL);
}

static int attach_attrib(read_ctx_t* const ctx, csch_cgrp_t* const dst,
	gsxl_node_t* const src_node, const char* const key,
	const char* const value)
{
	csch_source_arg_t* const src = make_src_attrib(ctx, src_node);

	if(!src)
	{
		eechema_error(ctx, src_node, "could not create src_attrib");
		return -1;
	}

	return csch_attrib_set(&dst->attr, CSCH_ATP_USER_DEFAULT, key, value, src,
		NULL);
}

static void eeschema_sym_child_hide(read_ctx_t* const ctx,
	csch_cgrp_t* const sym, csch_chdr_t* const child)
{
	csch_child_xform_t* const xf = calloc(1, sizeof(csch_child_xform_t));
	csch_vtoid_append(&xf->path.vt, child->oid);
	xf->remove = 1;
	vtp0_append(&sym->data.ref.child_xform, xf);
}

static csch_child_xform_t* eeschema_sym_child_moverotmir(read_ctx_t* const ctx,
	csch_cgrp_t* const sym, csch_chdr_t* const child, csch_coord_t dx,
	csch_coord_t dy, double rot, int mirx, int miry)
{
	csch_child_xform_t* const xf = calloc(1, sizeof(csch_child_xform_t));

	csch_vtoid_append(&xf->path.vt, child->oid);

	xf->movex = dx;
	xf->movey = dy;
	xf->rot   = rot;
	xf->mirx  = mirx;
	xf->miry  = miry;

	vtp0_append(&sym->data.ref.child_xform, xf);

	return xf;
}

static csch_coord_t eeschema_get_font_height(read_ctx_t* const ctx)
{
	csch_text_t* text;

	if(ctx->font_height!=0)
	{
		return ctx->font_height;
	}

	text = csch_text_alloc(ctx->sheet, &ctx->sheet->direct,
		csch_oid_new(ctx->sheet, &ctx->sheet->direct));

	text->hdr.stroke_name = csch_comm_str(ctx->sheet, "sym-decor", 1);

	text->spec_rot = 90;
	text->text = (char*)"Mj";

	/* update to get bbox ready */
	csch_text_update(ctx->sheet, text, 1);

	TODO("this is somehow different for vertical/horizontal texts (originally from: glob_label2.kicad_sch)");
	ctx->font_height = text->hdr.bbox.x2 - text->hdr.bbox.x1;

	text->text = NULL;

	csch_text_free(text);

	return ctx->font_height;
}

static csch_text_t* eeschema_find_attr_text(read_ctx_t* const ctx,
	csch_cgrp_t* const grp, const char* const str)
{
	htip_entry_t* ent;

	for(ent=htip_first(&grp->id2obj);ent;ent=htip_next(&grp->id2obj, ent))
	{
		csch_text_t* const text = (csch_text_t*)ent->value;

		if(text->hdr.type==CSCH_CTYPE_TEXT)
		{
			if(text->dyntext && (strcmp(text->text, str)==0))
			{
				return text;
			}
		}
	}

	return NULL;
}

static int eeschema_add_forge(read_ctx_t* const ctx,
	gsxl_node_t* const src_node, csch_cgrp_t* const grp,
	const char* const rail_attrib)
{
	char suba_buf[128];

	const char* forge[] =
	{
		"delete,forge/tmp",
		"scalar,forge/tmp",
		"sub,^,1:,forge/tmp",
		NULL, /* suba */
		"array,connect",
		"append,connect,forge/tmp",
		NULL
	};

	csch_source_arg_t* const src = make_src_attrib(ctx, src_node);

	if(!src)
	{
		eechema_error(ctx, src_node, "could not create src_attrib");
		return -1;
	}

	sprintf(suba_buf, "suba,$,%s,forge/tmp", rail_attrib);
	forge[3] = suba_buf;

	if(csch_attrib_set_arr_c(&grp->attr, CSCH_ATP_USER_DEFAULT, "forge",
		forge, src, NULL)!=0)
	{
		eechema_error(ctx, src_node, "could not add \"forge\" attrib");
		return -1;
	}

	return 0;
}

static void eeschema_reset_floater_handled_flag(read_ctx_t* const ctx,
	csch_cgrp_t* const grp)
{
	htip_entry_t* ent;

	for(ent=htip_first(&grp->id2obj);ent;ent=htip_next(&grp->id2obj, ent))
	{
		csch_text_t* const text = (csch_text_t*)ent->value;

		if(text->hdr.type==CSCH_CTYPE_TEXT)
		{
			if(text->dyntext)
			{
				TEXT_SYM_ATTR_HANDLED(text) = 0;
			}
		}
	}
}

static char* eeschema_make_symname(read_ctx_t* const ctx,
	gsxl_node_t* const src_node,
	const char* const symname0, const char* const unit)
{
	const size_t len_n = strlen(symname0);
	const size_t len_u = strlen(unit);

	char* const name = (char*)malloc(len_n + len_u + 2);

	if(!name)
	{
		eechema_error(ctx, src_node, "could not allocate sym name");
		return NULL;
	}

	strcpy(name, symname0);
	name[len_n] = ':';
	strcpy(name+len_n+1, unit);

	return name;
}

static char* eeschema_make_symname2(read_ctx_t* const ctx,
	gsxl_node_t* const src_node,
	const char* lib_name_src, const char* const unit)
{
	/* lib_name = "GND_3" */
	/* unit = "1" */

	const size_t len_name = strlen(lib_name_src);
	const size_t len_unit = strlen(unit);

	char* const name = (char*)malloc(len_name + len_unit + 2);
	char* str;

	if(!name)
	{
		eechema_error(ctx, src_node, "could not allocate sym name");
		return NULL;
	}

	str = name;

	strcpy(str, lib_name_src);
	str += len_name;
	*str++ = ':';

	strcpy(str, unit);

	return name;
}

static int io_eeschema_postproc(read_ctx_t *ctx)
{
/* enable this if your format has an automatic text rotation so that
   all text objects are readable from bottom and right (0 and 90 deg
   text rots only)
	if (io_eeschema_conf.plugins.io_eeschema.emulate_text_ang_180) {
		csch_cgrp_update(ctx->sheet, &ctx->sheet->direct, 1);
		csch_alien_postproc_text_autorot(&ctx->alien, &ctx->sheet->direct, 1, 0);
	}
*/

	csch_cgrp_update(ctx->alien.sheet, &ctx->alien.sheet->direct, 1);

	/* use this for attribute conversion instead of hardwired C code: read
	   and store attributes as they are in the native format and let this
	   configurable postproc deal with things like symbols's "rename
	   refdes attribute to name attribute" - this is more flexible and
	   user configurable this way */
	if(csch_alien_postproc_sheet(&ctx->alien)!=0)
	{
		return -1;
	}

	csch_alien_update_conns(&ctx->alien);

	return 0;
}

static const dispatch_t* find_disp(const dispatch_t* disp, const char* const name)
{
	dbg_printf("disp lookup: '%s'\n", name);

	for(;disp->name;++disp)
	{
		if(strcmp(name, disp->name)==0)
		{
			return disp;
		}
	}

	return NULL;
}

static void unexpected_child(read_ctx_t* ctx, gsxl_node_t* node,
	gsxl_node_t* child)
{
	eechema_error(ctx, child, "unexpected child under '%s': '%s'", node->str,
		child->str);
}

static int eeschema_parser_number__impl(read_ctx_t* const ctx,
	gsxl_node_t* const node, float* const n)
{
	char* end = NULL;

	*n = strtod(node->str, &end);

	if(!end || (*end)!=0)
	{
		eechema_error(ctx, node, "invalid number: '%s'", node->str);
		return -1;
	}

	return 0;
}

static int eechema_parse_num(read_ctx_t* const ctx, gsxl_node_t* node,
	float* n)
{
	if(node->next!=NULL)
	{
		eechema_error(ctx, node, "invalid 'number' info");
		return -1;
	}

	return eeschema_parser_number__impl(ctx, node, n);
}

static int eechema_parse_xy(read_ctx_t* const ctx, gsxl_node_t* node,
	float* x, float* y)
{
	if(node->next==NULL || node->next->next!=NULL)
	{
		eechema_error(ctx, node, "invalid 'xy' info");
		return -1;
	}

	if(eeschema_parser_number__impl(ctx, node, x)!=0)
	{
		return -1;
	}

	return eeschema_parser_number__impl(ctx, node->next, y);
}

static int eechema_parse_at(read_ctx_t* const ctx, gsxl_node_t* node,
	float* x, float* y, int* rot)
{
	if(node->next==NULL || node->next->next==NULL ||
		node->next->next->next!=NULL)
	{
		eechema_error(ctx, node, "invalid 'at' info");
		return -1;
	}

	if(eeschema_parser_number__impl(ctx, node, x)!=0)
	{
		return -1;
	}

	node = node->next;

	if(eeschema_parser_number__impl(ctx, node, y)!=0)
	{
		return -1;
	}

	node = node->next;

	{
		char* end = NULL;

		long n = strtol(node->str, &end, 10);

		if(!end || (*end)!=0)
		{
			eechema_error(ctx, node, "invalid number: '%s'", node->str);
			return -1;
		}

		*rot = (360+n) % 360;
	}

	return 0;
}

/* returns yes/no value, or negative on error */
static int eechema_parse_yesno(read_ctx_t* const ctx, gsxl_node_t* node)
{
	/* this must be a subtree with exactly 1 node, which value must be */
	/* "yes" or "no" */

	if(node && node->next==NULL)
	{
		if(strcmp(node->str, "yes")==0)
		{
			return 1;
		}
		else
		if(strcmp(node->str, "no")==0)
		{
			return 0;
		}
	}
	else
	if(node==NULL)
	{
		/* if this is NULL, it means parent has no children, thus */
		/* implicitly "yes" */
		return 1;
	}

	eechema_error(ctx, node, "invalid yes-no info");
	return -1;
}

/* returns whether it has a fill, or negative on error */
static int eeschema_has_fill(read_ctx_t* const ctx, gsxl_node_t* node)
{
	for(;node;node=node->next)
	{
		if(strcmp(node->str, "type")==0)
		{
			gsxl_node_t* child = node->children;

			if(child==NULL)
			{
				eechema_error(ctx, node, "missing child");
				return -1;
			}

			if(child->next!=NULL)
			{
				eechema_error(ctx, child->next, "unexpected node: '%s'",
					child->next->str);
				return -1;
			}

			if(strcmp(child->str, "none")==0)
			{
				return 0;
			}
			else
			if(strcmp(child->str, "color")==0 ||
				strcmp(child->str, "background")==0 ||
				strcmp(child->str, "outline")==0)
			{
				return 1;
			}
			else
			{
				eechema_error(ctx, child, "unexpected fill type: '%s'",
					child->str);
				return -1;
			}
		}
		else
		if(strcmp(node->str, "color")==0)
		{
			/* ignore */
		}
		else
		{
			eechema_error(ctx, node, "unexpected node: '%s'", node->str);
			return -1;
		}
	}

	return -1;
}

static const char* eeschema_get_stroke(read_ctx_t* const ctx)
{
	if(ctx->cur_libsym!=NULL)
	{
		return "sym-decor";
	}

	return "sheet-decor";
}

static const char* eeschema_get_fill(read_ctx_t* const ctx)
{
	if(ctx->cur_libsym!=NULL)
	{
		return "sym-decor-fill";
	}

	return "sheet-decor-fill";
}

static csch_cgrp_t* eeschema_get_titleblock(read_ctx_t* const ctx)
{
	if(!ctx->titleblock)
	{
		ctx->titleblock = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct,
			csch_oid_new(ctx->sheet, &ctx->sheet->direct));

		ctx->titleblock->hdr.lock = 1;
	}

	return ctx->titleblock;
}

static int eeschema_dispatch(read_ctx_t* const ctx, csch_cgrp_t* const dst,
	gsxl_node_t* node, const dispatch_t* const disptab)
{
	for(;node;node=node->next)
	{
		const dispatch_t* const disp = find_disp(disptab, node->str);

		if(disp)
		{
			const int res = disp->parse(ctx, dst, node->children);

			if(res!=0)
			{
				return res;
			}
		}
		else
		{
			eechema_error(ctx, node, "Unknown node: '%s'", node->str);
			return -1;
		}
	}

	return 0;
}

static const char a_dnp[] = "dnp";
static const char a_pcb_omit[] = "pcb/omit";
static const char a_sim_omit[] = "spice/omit";
static const char a_in_bom[] = "in_bom";

static int eechema_parse_libsym(read_ctx_t* const ctx, csch_cgrp_t* const dst,
	gsxl_node_t* const data_node);
static int eechema_parse_libsymdata(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node);
static int eechema_parse_schsymdata(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node);
static int eechema_parse_titleblock(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node);
static int eechema_parse_wkssetup(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node);
static int eechema_parse_wks(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node);

#include "read_netclass.c"
#include "read_render.c"
#include "read_paper.c"
#include "read_pin.c"
#include "read_wks.c"
#include "read_disp.c"

static int eechema_parse_sch(read_ctx_t* const ctx)
{
	gsxl_node_t* node;

	node = ctx->dom.root;

	if(strcmp(node->str, "kicad_sch")!=0)
	{
		eechema_error(ctx, node, "DOM is not a kicad_sch root");
		return 1;
	}

	return eeschema_dispatch(ctx, &ctx->alien.sheet->direct, node->children,
		eechema_disptab_sch);
}

static int eechema_parse_libsym(read_ctx_t* const ctx, csch_cgrp_t* const dst,
	gsxl_node_t* const data_node)
{
	if(eeschema_dispatch(ctx, dst, data_node, eechema_disptab_libsym0)!=0)
	{
		return -1;
	}

	return eeschema_dispatch(ctx, dst, data_node, eechema_disptab_libsym1);
}

static int eechema_parse_libsymdata(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node)
{
	if(data_node->next==NULL)
	{
		eechema_error(ctx, data_node->parent, "broken lib_symbol data");
		return -1;
	}

	/* drop first child node, it is a name, which we don't need */
	return eeschema_dispatch(ctx, dst, data_node->next,
		eechema_disptab_libsymdata);
}

static int eechema_parse_schsymdata(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node)
{
	return eeschema_dispatch(ctx, dst, data_node, eechema_disptab_schsymdata);
}

static int eechema_parse_titleblock(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node)
{
	return eeschema_dispatch(ctx, dst, data_node, eechema_disptab_titleblock);
}

static int eechema_parse_wkssetup(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node)
{
	return eeschema_dispatch(ctx, dst, data_node, eechema_disptab_wkssetup);
}

static int eechema_parse_wks(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const data_node)
{
	return eeschema_dispatch(ctx, dst, data_node, eechema_disptab_wks);
}

static void io_eeschema_free_cache(read_ctx_t* const ctx)
{
	htsp_entry_t *e;

	for(e=htsp_first(&ctx->syms);e!=NULL;e=htsp_next(&ctx->syms, e))
	{
		cache_sym_t* cs = (cache_sym_t*)e->value;
		/* do not free cs->grp, it is added to the sheet local lib! */
		free(cs->sym_name);
		free(cs);
	}

	htsp_uninit(&ctx->syms);
}

/* load a single sheet from f/fn into preallocated empty sheet dst;
   fmt is an optional hint on the format (safe to ignore). Return 0
   on success */
int io_eeschema_load_sheet(FILE *f, const char *fn, const char *fmt, csch_sheet_t *dst)
{
	int res = -1;
	read_ctx_t ctx = {0};

	ctx.f = f;
	ctx.fn = fn;
	ctx.sheet = dst;
	ctx.lineno = 1;

	if (eechema_parse_file(f, &ctx.dom) != GSX_RES_EOE) {
		error((&ctx), ("failed to parse s-expression\n"));
		return -1;
	}

	htsp_init(&ctx.syms, strhash, strkeyeq);

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

	res = eechema_parse_sch(&ctx);

	if (res == 0)
		res = io_eeschema_postproc(&ctx);

	if ((res == 0) && io_eeschema_conf.plugins.io_eeschema.auto_normalize)
		csch_alien_postproc_normalize(&ctx.alien);

	io_eeschema_free_cache(&ctx);
	gsxl_uninit(&ctx.dom);

	return res;
}

static csch_cgrp_t *load_sym_(read_ctx_t *ctx)
{
	csch_source_arg_t *src;
	csch_cgrp_t *resgrp = NULL;
	int rv = 0;

#if 0
TODO
	if (read_ver(ctx) != 0)
		return NULL;
#endif

	if ((ctx->ver != 1) && (ctx->ver != 2)) {
		error(ctx, ("wrong version of eeschema symbol: only file version 1 and 2 are supported, yours is %d\n", ctx->ver));
		return NULL;
	}

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

	/* read the file */
	for(;;) {
		int cmd, r;

		cmd = fgetc(ctx->f);
		if (cmd == EOF)
			break;
		ungetc(cmd, ctx->f);

/*		r = read_next_object_from_your_file(ctx, resgrp, 1);*/
		r = -1;
		if (r != 0) {
			error(ctx, ("Error in eeschema symbol data\n"));
			rv = -1;
			break;
		}
	}

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

	return resgrp;
}

/* IO API function (load symbol from lib) */
csch_cgrp_t *io_eeschema_load_grp(FILE *f, const char *fn, const char *fmt, const char *subsymname, csch_sheet_t *sheet)
{
	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.lineno = 1;

	alien_setup(&ctx);

	grp = load_sym_(&ctx);
	if (io_eeschema_postproc(&ctx) != 0)
		rnd_message(RND_MSG_ERROR, "io_eeschema: failed to postprocess newly loaded symbol\n");

	return grp;
}

/* Read as little as possible from f/fn and return 0 if there's any chance
   of this file being the right format or return -1 if it's surely not a
   valid file in the supported format. fmt is a hint on the expected file
   format family. Look at type to figure if the format needs to be a sheet
   or a symbol */
int io_eeschema_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	/* type: CSCH_IOTYP_SHEET ? */

	char line[1024];
	int lineno;
	int has_opar;

	lineno = 1;
	has_opar = 0;

	while((lineno++)<100 && fgets(line, sizeof(line), f)!=NULL)
	{
		char* s = line;

		/* drop leading spaces */
		while(isspace(*s)) ++s;

		/* test empty/comment lines */
		if((*s)==0 || (*s)=='#') continue;

		if(!has_opar)
		{
			/* looking for open parenthesis */

			s = strchr(s, '(');

			if(!s) continue;

			has_opar = 1;
		}

		if(strstr(s, "kicad_sch"))
			return 0;
	}

	return -1;
}
