/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - gEDA file format support
 *  Copyright (C) 2022..2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022, Entrust in 2023)
 *
 *  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_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 <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/math_helper.h>
#include <librnd/core/rotate.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/paths.h>
#include <sch-rnd/buffer.h>

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

#include "io_geda_conf.h"
extern conf_io_geda_t io_geda_conf;

#include "read.h"

/*#define dbg_printf printf*/

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

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

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_chdr_t *last;            /* last object succesfully loaded; required for attaching attributes */

	csch_cgrp_t *last_comp;       /* last component read, useful for a [] */
	char *last_comp_name;         /* matters in case of non-embedded component - we need to load from disk by name after the {} block when we figured there was no [] block */
	int last_comp_rot;
	unsigned last_comp_mir:1;
	unsigned last_comp_embedded:1;
	unsigned loading_emcomp:1;    /* set to 1 while loading embedded component */

	unsigned silent:1;
	unsigned no_ext_sym:1;        /* do not attempt to load external symbols from the geda lib */
	unsigned buffer_dirty:1;      /* set if the scratch buffer got used */

	unsigned sym_hash_inited:1;
	htsp_t sym_hash;

	csch_alien_read_ctx_t alien;
} read_ctx_t;

typedef struct {
	char *name;
	char *path;
	csch_cgrp_t *grp; /* NULL if not yet loaded */
} disk_sym_t;

static int read_ver(read_ctx_t *ctx)
{
	int cmd;
	long prog_ver;

	cmd = fgetc(ctx->f);
	if (cmd != 'v') {
		error(ctx, ("Expected 'v' in first line\n"));
		return -1;
	}

	if (fscanf(ctx->f, "%ld %ld\n", &prog_ver, &ctx->ver) != 2) {
		error(ctx, ("Expected two integers in 'v' version line\n"));
		return -1;
	}

	ctx->lineno++;
	return 0;
}

static char *read_str(read_ctx_t *ctx)
{
	gds_t tmp = {0};

	gds_append(&tmp, '\0');
	tmp.used = 0;

	for(;;) {
		int c = fgetc(ctx->f);

		if ((c == '\n') || (c == EOF)) {
			ctx->lineno++;
			return tmp.array;
		}
		if (c != '\r')
			gds_append(&tmp, c);
	}
}

static char *read_data_dot(read_ctx_t *ctx)
{
	gds_t tmp = {0};
	int last_nl = 0;

	for(;;) {
		int c = fgetc(ctx->f);

		if (c == EOF) {
			error(ctx, ("Premature end of file: expected embedded data terminator dot line\n"));
			return NULL;
		}

		if ((c == '.') && last_nl)
			return tmp.array;

		if (c == '\n') {
			ctx->lineno++;
			last_nl = 1;
		}
		else
			last_nl = 0;

		gds_append(&tmp, c);
	}
}

#define IPENMAX 20
static const char *pen_name_sheet[IPENMAX+1] = {
	"sheet-decor",    /* 0     BACKGROUND_COLOR */
	"term-primary",   /* 1     PIN_COLOR */
	"term-secondary", /* 2     NET_ENDPOINT_COLOR */
	"sheet-decor",    /* 3     GRAPHIC_COLOR */
	"wire",           /* 4     NET_COLOR */
	"sheet-decor",    /* 5     ATTRIBUTE_COLOR */
	"sheet-decor",    /* 6     LOGIC_BUBBLE_COLOR */
	"sheet-decor",    /* 7     DOTS_GRID_COLOR */
	"sheet-decor",    /* 8     DETACHED_ATTRIBUTE_COLOR */
	"sheet-decor",    /* 9     TEXT_COLOR */
	"bus",            /* 10    BUS_COLOR */
	"sheet-decor",    /* 11    SELECT_COLOR */
	"sheet-decor",    /* 12    BOUNDINGBOX_COLOR */
	"sheet-decor",    /* 13    ZOOM_BOX_COLOR */
	"sheet-decor",    /* 14    STROKE_COLOR */
	"sheet-decor",    /* 15    LOCK_COLOR */
	"sheet-decor",    /* 16    OUTPUT_BACKGROUND_COLOR */
	"sheet-decor",    /* 17    FREESTYLE1_COLOR */
	"sheet-decor",    /* 18    FREESTYLE2_COLOR */
	"sheet-decor",    /* 19    FREESTYLE3_COLOR */
	"sheet-decor"     /* 20    FREESTYLE4_COLOR */
};

static const char *pen_name_term[IPENMAX+1] = {
	"sheet-decor",    /* 0     BACKGROUND_COLOR */
	"term-primary",   /* 1     PIN_COLOR */
	"term-secondary", /* 2     NET_ENDPOINT_COLOR */
	"term-decor",     /* 3     GRAPHIC_COLOR */
	"wire",           /* 4     NET_COLOR */
	"term-decor",     /* 5     ATTRIBUTE_COLOR */
	"term-decor",     /* 6     LOGIC_BUBBLE_COLOR */
	"term-decor",     /* 7     DOTS_GRID_COLOR */
	"term-decor",     /* 8     DETACHED_ATTRIBUTE_COLOR */
	"term-decor",     /* 9     TEXT_COLOR */
	"bus",            /* 10    BUS_COLOR */
	"term-decor",     /* 11    SELECT_COLOR */
	"term-decor",     /* 12    BOUNDINGBOX_COLOR */
	"term-decor",     /* 13    ZOOM_BOX_COLOR */
	"term-decor",     /* 14    STROKE_COLOR */
	"sheet-decor",    /* 15    LOCK_COLOR */
	"term-decor",     /* 16    OUTPUT_BACKGROUND_COLOR */
	"term-decor",     /* 17    FREESTYLE1_COLOR */
	"term-decor",     /* 18    FREESTYLE2_COLOR */
	"term-decor",     /* 19    FREESTYLE3_COLOR */
	"term-decor"      /* 20    FREESTYLE4_COLOR */
};

static const char *pen_name_sym[IPENMAX+1] = {
	"sheet-decor",    /* 0     BACKGROUND_COLOR */
	"term-primary",   /* 1     PIN_COLOR */
	"term-secondary", /* 2     NET_ENDPOINT_COLOR */
	"sym-decor",      /* 3     GRAPHIC_COLOR */
	"wire",           /* 4     NET_COLOR */
	"sym-decor",      /* 5     ATTRIBUTE_COLOR */
	"sym-decor",      /* 6     LOGIC_BUBBLE_COLOR */
	"sym-decor",      /* 7     DOTS_GRID_COLOR */
	"sym-decor",      /* 8     DETACHED_ATTRIBUTE_COLOR */
	"sym-decor",      /* 9     TEXT_COLOR */
	"bus",            /* 10    BUS_COLOR */
	"sym-decor",      /* 11    SELECT_COLOR */
	"sym-decor",      /* 12    BOUNDINGBOX_COLOR */
	"sym-decor",      /* 13    ZOOM_BOX_COLOR */
	"sym-decor",      /* 14    STROKE_COLOR */
	"sheet-decor",    /* 15    LOCK_COLOR */
	"sym-decor",      /* 16    OUTPUT_BACKGROUND_COLOR */
	"sym-decor",      /* 17    FREESTYLE1_COLOR */
	"sym-decor",      /* 18    FREESTYLE2_COLOR */
	"sym-decor",      /* 19    FREESTYLE3_COLOR */
	"sym-decor"       /* 20    FREESTYLE4_COLOR */
};

static const char *pen_name_wire[IPENMAX+1] = {
	"sheet-decor",    /* 0     BACKGROUND_COLOR */
	"term-primary",   /* 1     PIN_COLOR */
	"term-secondary", /* 2     NET_ENDPOINT_COLOR */
	"sym-decor",      /* 3     GRAPHIC_COLOR */
	"wire",           /* 4     NET_COLOR */
	"wire",           /* 5     ATTRIBUTE_COLOR */
	"sym-decor",      /* 6     LOGIC_BUBBLE_COLOR */
	"sym-decor",      /* 7     DOTS_GRID_COLOR */
	"sym-decor",      /* 8     DETACHED_ATTRIBUTE_COLOR */
	"sym-decor",      /* 9     TEXT_COLOR */
	"bus",            /* 10    BUS_COLOR */
	"sym-decor",      /* 11    SELECT_COLOR */
	"sym-decor",      /* 12    BOUNDINGBOX_COLOR */
	"sym-decor",      /* 13    ZOOM_BOX_COLOR */
	"sym-decor",      /* 14    STROKE_COLOR */
	"sheet-decor",    /* 15    LOCK_COLOR */
	"sym-decor",      /* 16    OUTPUT_BACKGROUND_COLOR */
	"sym-decor",      /* 17    FREESTYLE1_COLOR */
	"sym-decor",      /* 18    FREESTYLE2_COLOR */
	"sym-decor",      /* 19    FREESTYLE3_COLOR */
	"sym-decor"       /* 20    FREESTYLE4_COLOR */
};

const char *read_alloc_pen_by_color(read_ctx_t *ctx, csch_cgrp_t *parent, int color)
{
	const char **p;

	if (parent->role == CSCH_ROLE_SYMBOL)
		p = pen_name_sym;
	else if (parent->role == CSCH_ROLE_TERMINAL)
		p = pen_name_term;
	else if (parent->role == CSCH_ROLE_WIRE_NET)
		p = pen_name_wire;
	else
		p = pen_name_sheet;

	if ((color >= 0) && (color <= IPENMAX))
		return p[color];

	return "sheet-decor";
}

const char *read_alloc_pen(read_ctx_t *ctx, csch_cgrp_t *parent, int color, long width, int capstyle, int dashstyle, long dashlength, long dashspace)
{
	TODO("do not ignore dash and width");
	return read_alloc_pen_by_color(ctx, parent, color);
}

const char *read_alloc_pen_text(read_ctx_t *ctx, csch_cgrp_t *parent, int color, long size)
{
	TODO("do not ignore size");
	return read_alloc_pen_by_color(ctx, parent, color);
}

static int read_nl(read_ctx_t *ctx)
{
	for(;;) {
		int c = fgetc(ctx->f);

		if (c == '\n') {
			ctx->lineno++;
			return 0;
		}

		if (!isspace(c)) {
			error(ctx, ("Expected character '%c' near the end of the line\n", c));
			return -1;
		}
	}
}

#define READ_NL(ctx) if (read_nl(ctx) != 0) return -1;

static csch_source_arg_t *geda_src(read_ctx_t *ctx)
{
	return csch_attrib_src_c(ctx->fn, ctx->lineno, 0, NULL);
}

/* Create a scalar attribute key=val or if key already exist in obj->attr,
   convert obj->attr into an arrayand append val */
static void attr_set_maybe_arr(csch_sheet_t *sheet, csch_cgrp_t *obj, int prio, const char *key, const char *val, csch_source_arg_t *source)
{
	csch_attrib_t *a = csch_attrib_get(&obj->attr, key);


	if ((a == NULL) || (prio < a->prio)) {
		/* first value or {}-overwrites[]-via-prio -> create attrs */
		csch_cobj_attrib_set(sheet, obj, prio, key, val, source);
		a = csch_attrib_get(&obj->attr, key);
		a->prio = prio;
		return;
	}

	/* in case we had a scalar value turn it into an array */
	if (a->val != NULL) {
		vts0_append(&a->arr, a->val);
		a->val = NULL;
	}

	csch_attrib_append(&obj->attr, prio, key, val, source);
}

/* Create a text object and create attribue as needed. read_line returns an
   allocated string that will be free()'d here. Returns 0 on success. */
static int place_text(read_ctx_t *ctx, csch_cgrp_t *parent, long x, long y, long nlines, const char *penname, int vis, int nvis, int rot, int align, int is_attr, int create_attr, char *(*read_line)(void *uctx), void *uctx)
{
	long n;

	TODO("this splits up multiline text into separate lines; in gEDA coords");
#define DEF_TERM_GRID 200.0
	y += (nlines-1) * DEF_TERM_GRID;

	dbg_printf("%s at %ld %ld on level %d\n", (is_attr ? "attr" : "text"), x, y, ctx->level);
	for(n = 0; n < nlines; n++) {
		csch_text_t *text = NULL;
		char *sep, *s = read_line(uctx);
		int do_attr = 0;

		dbg_printf(" '%s'\n", s);

		sep = strchr(s, '=');
		if (!is_attr && (n == 0) && (sep != NULL)) {
			dbg_printf("  implicit attr\n");
			do_attr = 1;
		}
		else if (is_attr)
			do_attr = 1;


		if (do_attr && (parent != NULL) && (sep != NULL)) {
			*sep = '\0';
			sep++;
			if (create_attr)
				attr_set_maybe_arr(ctx->sheet, parent, CSCH_ATP_USER_DEFAULT + ctx->loading_emcomp, s, sep, geda_src(ctx));
			if (vis) {
				text = (csch_text_t *)csch_alien_mktext(&ctx->alien, parent, x, y, penname);
				y -= DEF_TERM_GRID;
				switch(nvis) {
					case 0:
						text->text = rnd_strdup_printf("%s=%%../A.%s%%", s, s);
						text->dyntext = 1;
						text->hdr.floater = 1;
						break;
					case 1:
						text->text = rnd_strdup_printf("%%../A.%s%%", s);
						text->dyntext = 1;
						text->hdr.floater = 1;
						break;
					case 2:
						text->text = s;
						s = NULL;
						break;
					default:
						text->text = rnd_strdup("<invalid nvis field>");
				}
			}
		}
		else {
			text = (csch_text_t *)csch_alien_mktext(&ctx->alien, parent, x, y, penname);
			y -= DEF_TERM_GRID;

			text->text = s;
			s = NULL;
		}
		if (text != NULL) {
			text->spec_rot = rot;
		}
		free(s);

		if (text != NULL) {
			/* halign */
			switch(align) {
				case 0: case 1: case 2: /* left */
					break;
				case 3: case 4: case 5: /* center */
					TODO("Center\n");
					break;
				case 6: case 7: case 8: /* right */
					text->spec_mirx = 1;
					break;
			}

			/* valign */
			switch(align) {
				case 2: case 5: case 8: /* top */
					text->spec_miry = 1;
					break;
				case 1: case 4: case 7: /* center */
					text->spec1.y -= 1500;
					TODO("Center\n");
					break;
				case 0: case 3: case 6: /* bottom */
					break;
			}
		}
		if (text != NULL)
			csch_text_update(ctx->alien.sheet, text, 1);
	}



	return 0;
}

/*** read objects ***/

static int read_text(read_ctx_t *ctx, csch_cgrp_t *parent, int is_attr)
{
	long x, y, nlines;
	int color, size, vis, nvis, rot, align;
	const char *penname;

	if (fscanf(ctx->f, "%ld %ld %d %d %d %d %d %d %ld", &x, &y, &color, &size, &vis, &nvis, &rot, &align, &nlines) != 9) {
		error(ctx, ("invalid integer fields in 'T'\n"));
		return -1;
	}
	READ_NL(ctx);

	penname = read_alloc_pen_text(ctx, parent, color, size);

	return place_text(ctx, parent, x, y, nlines, penname, vis, nvis, rot, align, is_attr, 1, (char *(*)(void *))read_str, ctx);
}

static int read_line(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x1, y1, x2, y2, width, dashlength, dashspace;
	int color, capstyle, dashstyle;
	const char *penname;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d %ld %d %d %ld %ld", &x1, &y1, &x2, &y2, &color, &width, &capstyle, &dashstyle, &dashlength, &dashspace) != 10) {
		error(ctx, ("invalid integer fields in 'L'\n"));
		return -1;
	}
	dbg_printf("line at %ld;%ld %ld;%ld\n", x1, y1, x2, y2);
	penname = read_alloc_pen(ctx, parent, color, width, capstyle, dashstyle, dashlength, dashspace);
	ctx->last = csch_alien_mkline(&ctx->alien, parent, x1, y1, x2, y2, penname);
	READ_NL(ctx);
	return 0;
}

static int read_arc(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long cx, cy, r, width, dashlength, dashspace;
	int sa, da, color, capstyle, dashstyle;
	const char *penname;

	if (fscanf(ctx->f, "%ld %ld %ld %d %d %d %ld %d %d %ld %ld", &cx, &cy, &r, &sa, &da, &color, &width, &capstyle, &dashstyle, &dashlength, &dashspace) != 11) {
		error(ctx, ("invalid integer fields in 'A'\n"));
		return -1;
	}
	dbg_printf("arc at %ld;%ld r=%ld angles: %d %d\n", cx, cy, r, sa, da);
	READ_NL(ctx);

	penname = read_alloc_pen(ctx, parent, color, width, capstyle, dashstyle, dashlength, dashspace);
	ctx->last = csch_alien_mkarc(&ctx->alien, parent, cx, cy, r, sa, da, penname);

	return 0;
}

static int read_circle(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long cx, cy, r, p1, p2, fillwidth, width, dashlength, dashspace;
	int color, capstyle, dashstyle;
	int filltype, a1, a2;
	const char *penname;

	if (fscanf(ctx->f, "%ld %ld %ld %d %ld %d %d %ld %ld %d %ld %d %ld %d %ld",
		&cx, &cy, &r,
		&color, &width, &capstyle, &dashstyle, &dashlength, &dashspace,
		&filltype, &fillwidth, &a1, &p1, &a2, &p2) != 15) {
		error(ctx, ("invalid integer fields in 'A'\n"));
		return -1;
	}
	dbg_printf("circle at %ld;%ld r=%ld\n", cx, cy, r);
	READ_NL(ctx);

	penname = read_alloc_pen(ctx, parent, color, width, capstyle, dashstyle, dashlength, dashspace);
	ctx->last = csch_alien_mkarc(&ctx->alien, parent, cx, cy, r, 0, 360, penname);

	return 0;
}

static int read_pin(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x1, y1, x2, y2;
	int color, pintype, whichend;
	csch_source_arg_t *src;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d %d %d", &x1, &y1, &x2, &y2, &color, &pintype, &whichend) != 7) {
		error(ctx, ("invalid integer fields in 'P'\n"));
		return -1;
	}
	dbg_printf("pin: %ld %ld  to %ld %ld\n", x1, y1, x2, y2);
	READ_NL(ctx);

	TODO("if whichend is 0, swap coords?");

	src = csch_attrib_src_c(ctx->fn, ctx->lineno, 0, NULL);
	ctx->last = csch_alien_mkpin_line(&ctx->alien, src, parent, x1, y1, x2, y2);

	return 0;
}

static int read_picture(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x, y, w, h;
	int rot, mir, emb;
	char *fn;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d %d %d", &x, &y, &w, &h, &rot, &mir, &emb) != 7) {
		error(ctx, ("invalid integer fields in 'G'\n"));
		return -1;
	}
	READ_NL(ctx);

	fn = read_str(ctx);
	if (fn == NULL)
		return -1;

	if (emb) {
		char *data = read_data_dot(ctx);
		if (data == NULL)
			return -1;
		free(data);
		READ_NL(ctx);
	}

	dbg_printf("picture at %ld %ld (%s)\n", x, y, fn);
	TODO("create bitmap");

	free(fn);
	return 0;
}

static int read_box(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x, y, w, h, width, dashlength, dashspace, fillwidth, p1, p2;
	int color, capstyle, dashstyle, filltype, a1, a2;
	const char *penname;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d %ld %d %d %ld %ld %d %ld %d %ld %d %ld",
		&x, &y, &w, &h, &color, &width, &capstyle, &dashstyle, &dashlength,
		&dashspace, &filltype, &fillwidth, &a1, &p1, &a2, &p2) != 16) {
		error(ctx, ("invalid integer fields in 'B'\n"));
		return -1;
	}
	dbg_printf("box at %ld %ld, size %ld %ld\n", x, y, w, h);

	penname = read_alloc_pen(ctx, parent, color, width, capstyle, dashstyle, dashlength, dashspace);
	csch_alien_mkrect(&ctx->alien, parent, x, y, x+w, y+h, penname, (filltype > 0) ? penname : 0);
	READ_NL(ctx);
	return 0;
}

static int read_net(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x1, y1, x2, y2;
	int color;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d", &x1, &y1, &x2, &y2, &color) != 5) {
		error(ctx, ("invalid integer fields in 'N'\n"));
		return -1;
	}
	READ_NL(ctx);
	dbg_printf("net at %ld;%ld %ld;%ld\n", x1, y1, x2, y2);
	ctx->last = csch_alien_mknet(&ctx->alien, parent, x1, y1, x2, y2);
	return 0;
}

static int read_bus(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long x1, y1, x2, y2;
	int color, ripdir;

	if (fscanf(ctx->f, "%ld %ld %ld %ld %d %d", &x1, &y1, &x2, &y2, &color, &ripdir) != 6) {
		error(ctx, ("invalid integer fields in 'U'\n"));
		return -1;
	}
	dbg_printf("bus at %ld;%ld %ld;%ld\n", x1, y1, x2, y2);
	/* buses are purely graphical in geda and connections are made by other
	   means - emulate that by drawing decoration lines */
	csch_alien_mkline(&ctx->alien, parent, x1, y1, x2, y2, "bus");

	READ_NL(ctx);
	return 0;
}

static int read_path_xy(read_ctx_t *ctx, long *x, long *y)
{
	int c;
	if (fscanf(ctx->f, "%ld", x) != 1)
		return -1;
	c = fgetc(ctx->f);
	if ((c != ',') && (!isspace(c)))
		return -1;
	if (fscanf(ctx->f, "%ld", y) != 1)
		return -1;
	return 0;
}

#define READ_PATH_END \
do { \
	for(;;) { \
		int c = fgetc(ctx->f); \
		if (c == '\n') { \
			ctx->lineno++; \
			stay = 0; \
			break; \
		} \
		if (!isspace(c)) { \
			ungetc(c, ctx->f); \
			break; \
		} \
	} \
} while(0)

static int read_path(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	long p1, p2, fillwidth, numlines, n;
	int color, width, capstyle, dashstyle, dashlength, dashspace;
	int filltype, a1, a2;
	csch_chdr_t *poly;
	const char *penname = NULL;

	if (fscanf(ctx->f, "%d %d %d %d %d %d %d %ld %d %ld %d %ld %ld",
		&color, &width, &capstyle, &dashstyle, &dashlength, &dashspace,
		&filltype, &fillwidth, &a1, &p1, &a2, &p2, &numlines) != 13) {
		error(ctx, ("invalid integer fields in 'A'\n"));
		return -1;
	}
	READ_NL(ctx);
	dbg_printf("path of %ld:\n", numlines);

	if (numlines > 0) {
		penname = read_alloc_pen(ctx, parent, color, width, capstyle, dashstyle, dashlength, dashspace);
		poly = csch_alien_mkpoly(&ctx->alien, parent, penname, (filltype > 0) ? penname : NULL);
		ctx->last = poly;
	}

	for(n = 0; n < numlines; n++) {
		long sx, sy, cx, cy, x, y, x1, y1, x2, y2;
		int cmd, stay;

		cmd = fgetc(ctx->f);
		switch(cmd) {
			case 'M':
				for(stay = 1; stay;) {
					if (read_path_xy(ctx, &cx, &cy) != 0) {
						error(ctx, ("invalid x y coordinate for path M\n"));
						return -1;
					}
					sx = cx;
					sy = cy;
					dbg_printf(" move to %ld %ld\n", sx, sy);
					READ_PATH_END;
				}
				break;
			case 'L':
				for(stay = 1; stay;) {
					if (read_path_xy(ctx, &x, &y) != 0) {
						error(ctx, ("invalid x y coordinate for path L\n"));
						return -1;
					}
					dbg_printf(" line to %ld %ld (from %ld %ld)\n", x, y, cx, cy);
					csch_alien_append_poly_line(&ctx->alien, poly, cx, cy, x, y);
					cx = x;
					cy = y;
					READ_PATH_END;
				}
				break;
			case 'C':
				for(stay = 1; stay;) {
					if (read_path_xy(ctx, &x1, &y1) != 0) {
						error(ctx, ("invalid x1 y1 coordinate for path C\n"));
						return -1;
					}
					if (read_path_xy(ctx, &x2, &y2) != 0) {
						error(ctx, ("invalid x2 y2 coordinate for path C\n"));
						return -1;
					}
					if (read_path_xy(ctx, &x, &y) != 0) {
						error(ctx, ("invalid x y coordinate for path C\n"));
						return -1;
					}
					dbg_printf(" Curve to %ld %ld %ld %ld %ld %ld (from %ld %ld)\n", x1, y1, x2, y2, x, y, cx, cy);
					csch_alien_append_poly_bezier(&ctx->alien, poly, cx, cy, x1, y1, x2, y2, x, y);
					cx = x;
					cy = y;
					READ_PATH_END;
				}
				break;
			case 'Z':
			case 'z':
				dbg_printf(" Line to %ld %ld (from %ld %ld)\n", sx, sy, cx, cy);
				csch_alien_append_poly_line(&ctx->alien, poly, cx, cy, sx, sy);
				READ_NL(ctx);
				break;
		}
	}

	return 0;
}

static void map_sym(read_ctx_t *ctx, char *name, gds_t *path)
{
	disk_sym_t *sym = malloc(sizeof(disk_sym_t));

	sym->name = name;
	sym->path = rnd_strdup(path->array);
	sym->grp = NULL;
	htsp_set(&ctx->sym_hash, sym->name, sym);
/*	rnd_trace(" geda symbol: '%s' '%s'\n", name, path->array);*/
}

static void map_dir_(read_ctx_t *ctx, gds_t *path)
{
	struct dirent *de;
	DIR *dir;
	int saved_used = path->used, restore;

	dir = rnd_opendir(&ctx->sheet->hidlib, path->array);
	if (dir == NULL)
		return;

	gds_append(path, RND_DIR_SEPARATOR_C);
	restore = path->used;
	while((de = rnd_readdir(dir)) != NULL) {
		struct stat st;

		if (de->d_name[0] == '.')
			continue;

		path->used = restore;
		gds_append_str(path, de->d_name);

		if (stat(path->array, &st) != 0)
			continue;

		if (!S_ISDIR(st.st_mode)) {
			char *name, *ext;

			ext = strrchr(de->d_name, '.');
			if ((ext == NULL) || (rnd_strcasecmp(ext+1, "sym") != 0))
				continue;

			name = rnd_strdup(de->d_name);
			if (htsp_has(&ctx->sym_hash, name)) {
				free(name);
				continue;
			}

			map_sym(ctx, name, path);
		}
		else
			map_dir_(ctx, path);
	}
	rnd_closedir(dir);

	path->used = saved_used;
}

static void map_dir(read_ctx_t *ctx, const char *cfgdir, const char *cwd, gds_t *tmp)
{
	tmp->used = 0;
	if (!rnd_is_path_abs(cfgdir)) {
		gds_append_str(tmp, cwd);
		gds_append(tmp, RND_DIR_SEPARATOR_C);
	}
	gds_append_str(tmp, cfgdir);
	map_dir_(ctx, tmp);
}

static void init_sym_hash(read_ctx_t *ctx)
{
	gds_t tmp = {0};
	char *cwd, *sep;
	rnd_conf_listitem_t *ci;

	if (ctx->sym_hash_inited)
		return;

	cwd = rnd_strdup(ctx->fn);

	TODO("make an rnd_dirname(): on windows this is / or \\");
	sep = strrchr(cwd, RND_DIR_SEPARATOR_C);
	if (sep != NULL)
		*sep = '\0';

	htsp_init(&ctx->sym_hash, strhash, strkeyeq);

	for(ci = rnd_conflist_first((rnd_conflist_t *)&io_geda_conf.plugins.io_geda.library_search_paths); ci != NULL; ci = rnd_conflist_next(ci)) {
		char *path = rnd_build_fn(&ctx->sheet->hidlib, ci->val.string[0]);
		/*rnd_trace("io_geda: path resovle '%s' -> '%s'\n", ci->val.string[0], path);*/
		map_dir(ctx, path, cwd, &tmp);
		free(path);
	}

	free(cwd);
	gds_uninit(&tmp);
	ctx->sym_hash_inited = 1;
}

static void uninit_sym_hash(read_ctx_t *ctx)
{
	if (!ctx->sym_hash_inited)
		return;

	genht_uninit_deep(htsp, &ctx->sym_hash, {
		disk_sym_t *ds = htent->value;
		free(ds->name);
		free(ds->path);
		/* no need to: csch_cgrp_free(ds->grp); - all groups were in the scratch buffer and that buffer is cleared */
		free(ds);
	});
}

/* Remove one text object from sym that references the same attribute that can
   be extracted from the dyntext template string orig */
static void remove_orig_attr_by_text(read_ctx_t *ctx, csch_cgrp_t *sym, const char *orig)
{
	char *template, *sep;
	htip_entry_t *e;

	sep = strchr(orig, '%');
	if (sep == NULL)
		return;

	template = rnd_strdup(sep);
	sep = strchr(template+1, '%');
	if (sep != NULL)
		sep[1] = '\0';

	for(e = htip_first(&sym->id2obj); e != NULL; e = htip_next(&sym->id2obj, e)) {
		csch_text_t *t = e->value;

		if (t->hdr.type != CSCH_CTYPE_TEXT) continue;
		if (strstr(t->text, template) != NULL) {
			csch_cnc_remove(ctx->sheet, &t->hdr);
			break; /* expect only one - each attribute created only once */
		}
	}

	free(template);
}

static csch_cgrp_t *load_sym(rnd_design_t *hidlib, const char *fn, csch_sheet_t *sheet);

/* Move over all attributes and properites from src sym to dst sym and remove
   src sym */
static void last_comp_move_fields_props(read_ctx_t *ctx, csch_cgrp_t *dst, csch_cgrp_t *src)
{
	double x, y;

	dst->hdr.lock = src->hdr.lock;

	x = (double)src->x * ctx->alien.coord_factor;
	y = (double)src->y * ctx->alien.coord_factor;

	dst->spec_rot = ctx->last_comp_rot;
	dst->mirx = ctx->last_comp_mir;
	dst->x = rnd_round(x);
	dst->y = rnd_round(y);
}

/* move over all attributes and floaters: any src attrib fully rewrites dst attrib */
static void last_comp_move_fields_attrs(read_ctx_t *ctx, csch_cgrp_t *dst, csch_cgrp_t *src)
{
	{
		htsp_entry_t *e;
		for(e = htsp_first(&src->attr); e != NULL; e = htsp_next(&src->attr, e)) {
			csch_source_arg_t *src;
			csch_attrib_t *a = e->value;

			src = csch_attrib_src_c(ctx->fn, ctx->lineno, 0, NULL);
			csch_attrib_set(&dst->attr, CSCH_ATP_USER_DEFAULT-1, a->key, a->val, src, NULL);
		}
	}

	/* copy over all text objects, replace existing attribute text where needed */
	{
		htip_entry_t *e;
		for(e = htip_first(&src->id2obj); e != NULL; e = htip_next(&src->id2obj, e)) {
			csch_text_t *t = e->value, *newt;

			if (t->hdr.type != CSCH_CTYPE_TEXT) continue;

			remove_orig_attr_by_text(ctx, dst, t->text);

			newt = csch_text_dup(ctx->sheet, dst, t, 0, 0);

			newt->spec1.x = t->spec1.x;
			newt->spec1.y = t->spec1.y;
			csch_cgrp_vinverse_xform(dst, &newt->spec1.x, &newt->spec1.y, NULL);

			newt->spec_rot -= dst->spec_rot;
			newt->spec_mirx = t->spec_mirx ^ dst->mirx;
			newt->spec_miry = t->spec_miry ^ dst->miry;

			csch_text_update(ctx->sheet, newt, 1);

			/* rnd_trace(" TXT: spec=%ld;%ld inst=%ld;%ld\n", newt->spec1.x, newt->spec1.y, newt->inst1.x, newt->inst1.y); */

		}
	}

	csch_cnc_remove(ctx->sheet, &src->hdr);
}

static void sym_error(read_ctx_t *ctx, const char *msg)
{
	double x = (double)ctx->last_comp->x, y = (double)ctx->last_comp->y;
	csch_text_t *text;

	text = (csch_text_t *)csch_alien_mktext(&ctx->alien, ctx->last_comp, x, y, "sym-error");
	text->text = rnd_strdup(msg);
	text = (csch_text_t *)csch_alien_mktext(&ctx->alien, ctx->last_comp, x, y-DEF_TERM_GRID, "sym-error");
	text->text = rnd_strdup(ctx->last_comp_name);
}

static void flush_last_comp(read_ctx_t *ctx)
{
	if (!ctx->no_ext_sym && (ctx->last_comp != NULL) && !ctx->last_comp_embedded && (ctx->last_comp_name != NULL)) {
		disk_sym_t *ds;

		init_sym_hash(ctx);
		ds = htsp_get(&ctx->sym_hash, ctx->last_comp_name);
		if (ds != NULL) {

			if (ds->grp == NULL) {
				ds->grp = load_sym(&ctx->sheet->hidlib, ds->path, sch_rnd_buffer[SCH_RND_BUFFER_SCRATCH]);
				ctx->buffer_dirty = 1;
			}

			if (ds->grp != NULL) {
				csch_cgrp_t *newo = (csch_cgrp_t *)csch_cobj_dup(ctx->sheet, &ctx->sheet->direct, &ds->grp->hdr, 0, 0);

				last_comp_move_fields_props(ctx, newo, ctx->last_comp);


				if (newo->mirx)
					newo->spec_rot = -newo->spec_rot;

TODO("remove this dead code after some testing:");
#if 0
				/* offset coming from the symbol; this used to be bbox bottom left */
				rnd_coord_t ox, oy;
				ox = oy = 0;

				if ((ox != 0) || (oy != 0)) {
					if (newo->spec_rot != 0) {
						double rad = -newo->spec_rot / RND_RAD_TO_DEG;
						rnd_rotate(&ox, &oy, 0, 0, cos(rad), sin(rad));
					}
					if (newo->mirx)
						ox = -ox;
					newo->x -= ox;
					newo->y -= oy;
				}
#endif

				csch_cgrp_update(ctx->alien.sheet, newo, 1); /* make sure transformations are available for floater text placement */
				last_comp_move_fields_attrs(ctx, newo, ctx->last_comp);
			}
			else {
				error(ctx, ("Failed to load library symbol called '%s'\n", ctx->last_comp_name));
				sym_error(ctx, "!broken sym:");
			}
		}
		else {
			error(ctx, ("Can't find library symbol called '%s'\n", ctx->last_comp_name));
			sym_error(ctx, "!sym not found:");
		}

	}

	free(ctx->last_comp_name);
	ctx->last_comp_name = NULL;
	ctx->last_comp = NULL;
	ctx->last_comp_embedded = 0;
}

static int read_comp(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	csch_source_arg_t *src;
	long x, y;
	int selectable, angle, mirror;
	char *name;

	if (fscanf(ctx->f, "%ld %ld %d %d %d ", &x, &y, &selectable, &angle, &mirror) != 5) {
		error(ctx, ("invalid integer fields in 'C'\n"));
		return -1;
	}

	name = read_str(ctx);
	if (name == NULL) {
		error(ctx, ("missing name in 'C'\n"));
		return -1;
	}

	flush_last_comp(ctx);
	ctx->last_comp = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	src = csch_attrib_src_c(ctx->fn, ctx->lineno, 0, NULL);
	csch_cobj_attrib_set(ctx->sheet, ctx->last_comp, CSCH_ATP_HARDWIRED, "role", "symbol", src);
	ctx->last_comp->x = x;
	ctx->last_comp->y = y;
	ctx->last_comp->hdr.lock = !selectable;
	ctx->last_comp_name = rnd_strdup(name);
	ctx->last_comp_rot = angle;
	ctx->last_comp_mir = mirror;
	ctx->last = &ctx->last_comp->hdr;

	dbg_printf("Comp at %ld %ld: '%s'\n", x, y, name);
	return 0;
}

static int read_attr(read_ctx_t *ctx)
{
	csch_cgrp_t *parent;

	READ_NL(ctx); /* of the opening line */

	if (ctx->last != NULL)
		parent = csch_alien_attr_grp(&ctx->alien, &ctx->last);
	else
		parent = &ctx->sheet->direct;

	for(;;) {
		int ws, cmd;

		cmd = fgetc(ctx->f);
		if (cmd == '}') {
			/* if last object already had attributes in a {} block, any new
			   attribute defined on the same level is really attribute of the parent
			   group of the last object; until a new object is created */
			if (ctx->last != NULL)
				ctx->last = &ctx->last->parent->hdr;
			break;
		}
		if (cmd != 'T') {
			error(ctx, ("Invalid '%c' line in attribute list, only 'T' is supported\n", cmd));
			return -1;
		}

		ws = fgetc(ctx->f);
		if (!isspace(ws)) {
			error(ctx, ("Expected whitespace after command char\n"));
			return -1;
		}

		if (read_text(ctx, parent, 1) != 0)
			return -1;
	}

	READ_NL(ctx); /* of the closing line */
	return 0;
}

static int read_any(read_ctx_t *ctx, csch_cgrp_t *parent, int in_emcomp);

static int read_emcomp(read_ctx_t *ctx, csch_cgrp_t *parent)
{
	csch_cgrp_t *grp = ctx->last_comp;

	if (grp == NULL) {
		error(ctx, ("Unexpected '[': not in a component\n"));
		return -1;
	}

	if (ctx->last_comp_embedded) {
		error(ctx, ("Unexpected '[': multiple blocks in the same component\n"));
		return -1;
	}

	READ_NL(ctx); /* of the opening line */

	ctx->last_comp_embedded = 1;
	ctx->loading_emcomp = 1;
	grp->x = grp->y = 0;

	for(;;) {
		int cmd;

		cmd = fgetc(ctx->f);
		if (cmd == ']')
			break;

		ungetc(cmd, ctx->f);
		if (read_any(ctx, grp, 1) != 0) {
			error(ctx, ("Error in emebdded component data\n"));
			return -1;
		}
	}
	ctx->loading_emcomp = 0;

	READ_NL(ctx); /* of the closing line */
	flush_last_comp(ctx);
	return 0;
}

static int read_any(read_ctx_t *ctx, csch_cgrp_t *parent, int in_emcomp)
{
	int cmd, ws, res;

	ctx->level++;

	cmd = fgetc(ctx->f);
	switch(cmd) { /* No argument commands (so that the whitespace is not read */
		case EOF: res = 1; goto done; /* empty line, typically at the end */
		case '\0': goto done; /* empty line, typically at the end */
		case '{': res = read_attr(ctx); goto done;
		case '[': res = read_emcomp(ctx, parent); goto done;
	}

	ws = fgetc(ctx->f);
	if (!isspace(ws)) {
		error(ctx, ("Expected whitespace after command char\n"));
		return -1;
	}

	switch(cmd) {
		case 'C': res = read_comp(ctx, parent); break;
		case 'T': res = read_text(ctx, parent, 0); break;
		case 'L': res = read_line(ctx, parent); break;
		case 'A': res = read_arc(ctx, parent); break;
		case 'V': res = read_circle(ctx, parent); break;
		case 'P': res = read_pin(ctx, parent); break;
		case 'G': res = read_picture(ctx, parent); break;
		case 'B': res = read_box(ctx, parent); break;
		case 'N': res = read_net(ctx, parent); break;
		case 'U': res = read_bus(ctx, parent); break;
		case 'H': res = read_path(ctx, parent); break;
		/* No support for 'F', that's used only in special font files */
		default:
			error(ctx, ("Unknown command char: '%c'\n", cmd));
			return -1;
	}

	done:;
	ctx->level--;
	return res;
}

#include "read_postproc.c"

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

static void alien_setup(read_ctx_t *ctx)
{
	ctx->alien.sheet = ctx->sheet;
	ctx->alien.coord_factor = io_geda_conf.plugins.io_geda.coord_mult;
	ctx->alien.fmt_prefix = "io_geda";
}

static int io_geda_postproc(read_ctx_t *ctx)
{
	if (io_geda_conf.plugins.io_geda.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);
	}
	postproc_symbols(ctx);
	return csch_alien_postproc_sheet(&ctx->alien);
}

int io_geda_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 (read_ver(&ctx) != 0)
		return -1;

	if (ctx.ver != 2) {
		error((&ctx), ("wrong version of gEDA schematics: only file version 2 is supported, yours is %d\n", ctx.ver));
		return -1;
	}

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

	for(;;) {
		int r = read_any(&ctx, &ctx.sheet->direct, 0);

		if (r < 0) /* error */
			return r;

		if (r == 1) { /* reached eof without error */
			res = 0;
			break;
		}
	}

	flush_last_comp(&ctx);

	if (ctx.buffer_dirty)
		sch_rnd_buffer_clear(sch_rnd_buffer[SCH_RND_BUFFER_SCRATCH]);

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

	if ((res == 0) && io_geda_conf.plugins.io_geda.auto_normalize)
		csch_alien_postproc_normalize(&ctx.alien);

	uninit_sym_hash(&ctx);

	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 (read_ver(ctx) != 0)
		return NULL;

	if ((ctx->ver != 1) && (ctx->ver != 2)) {
		error(ctx, ("wrong version of gEDA 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;

		cmd = fgetc(ctx->f);
		if (cmd == EOF)
			break;
		ungetc(cmd, ctx->f);
		if (read_any(ctx, resgrp, 1) != 0) {
			error(ctx, ("Error in gEDA 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;
}

/* Load symbol from fn into sheet and return the group; sheet is typically a buffer */
static csch_cgrp_t *load_sym(rnd_design_t *hidlib, const char *fn, csch_sheet_t *sheet)
{
	read_ctx_t ctx = {0};
	csch_cgrp_t *res;

	ctx.f = rnd_fopen(hidlib, fn, "r");
	if (ctx.f == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to open symbol file %s for read\n", fn);
		return NULL;
	}

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

	alien_setup(&ctx);

	res = load_sym_(&ctx);

	fclose(ctx.f);

	return res;
}

/* IO API function (load symbol from lib) */
csch_cgrp_t *io_geda_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_geda_postproc(&ctx) != 0)
		rnd_message(RND_MSG_ERROR, "io_geda: failed to postprocess newly loaded symbol\n");

	return grp;
}

int io_geda_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	read_ctx_t ctx = {0};

	ctx.f = f;
	ctx.fn = fn;
	ctx.silent = 1;
	ctx.no_ext_sym = 1;

	if (read_ver(&ctx) != 0)
		return -1;

	return 0;
}

