/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  (this file is based on PCB, interactive printed circuit board design)
 *  Copyright (C) 1994,1995,1996,1997,1998,2005,2006 Thomas Nau
 *  Copyright (C) 2015, 2018, 2021 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/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 *
 */

/* file save, load, merge ... routines */

#include "config.h"
#include "conf_core.h"
#include <librnd/core/rnd_conf.h>

#include <locale.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "buffer.h"
#include "change.h"
#include "crosshair.h"
#include "data.h"
#include <librnd/core/error.h>
#include "file.h"
#include "plug_io.h"
#include <librnd/hid/hid.h>
#include "layer.h"
#include "layer_grp.h"
#include "move.h"
#include "parse_common.h"
#include <librnd/core/rnd_printf.h>
#include "polygon.h"
#include <librnd/poly/polygon1_gen.h>
#include "remove.h"
#include "flag_str.h"
#include <librnd/core/compat_fs.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/paths.h>
#include "rats_patch.h"
#include <librnd/core/actions.h>
#include "flag_str.h"
#include "attribs.h"
#include "route_style.h"
#include "obj_poly.h"
#include "thermal.h"
#include "event.h"
#include "funchash_core.h"
#include "netlist.h"
#include "plug_footprint.h"
#include "obj_pstk_inlines.h"

#include "src_plugins/lib_compat_help/layer_compat.h"
#include "src_plugins/lib_compat_help/pstk_compat.h"
#include "src_plugins/lib_compat_help/subc_help.h"
#include "src_plugins/lib_compat_help/elem_rot.h"
#include "src_plugins/lib_compat_help/route_style.h"

/* Note: this works because of using str_flag compat_types */
#define PCB_OBJ_VIA PCB_OBJ_PSTK
#define PCB_OBJ_PIN PCB_OBJ_PSTK
#define PCB_OBJ_PAD PCB_OBJ_PSTK
#define PCB_OBJ_ELEMENT PCB_OBJ_SUBC
#define PCB_OBJ_ELEMENT_NAME PCB_OBJ_TEXT

pcb_unit_style_t pcb_io_pcb_usty_seen;

static void WritePCBInfoHeader(FILE *);
static void WritePCBDataHeader(FILE *);
static void WritePCBFontData(FILE *);
static void WriteViaData(FILE *, pcb_data_t *);
static void WritePCBRatData(FILE *);
static void WriteLayerData(FILE *, rnd_cardinal_t, pcb_layer_t *);

#define IGNORE_FLAGS (PCB_FLAG_FLOATER | PCB_FLAG_DRC_INTCONN | PCB_FLAG_CLEARPOLYPOLY | PCB_FLAG_DYNTEXT)

static char *pcb_strflg_f2s_compat(pcb_flag_t flags, int object_type, unsigned char *intconn)
{
	flags.f &= ~IGNORE_FLAGS;
	return pcb_strflg_f2s(flags, object_type, intconn, 1);
}


#define F2S(OBJ, TYPE) (pcb_strflg_f2s_compat(((OBJ)->Flags), TYPE, &((OBJ)->intconn)))

/* The idea here is to avoid gratuitously breaking backwards
   compatibility due to a new but rarely used feature.  The first such
   case, for example, was the polygon Hole - if your design included
   polygon holes, you needed a newer PCB to read it, but if your
   design didn't include holes, PCB would produce a file that older
   PCBs could read, if only it had the correct version number in it.

   If, however, you have to add or change a feature that really does
   require a new PCB version all the time, it's time to remove all the
   tests below and just always output the new version.

   Note: Best practices here is to add support for a feature *first*
   (and bump PCB_FILE_VERSION in file.h), and note the version that
   added that support below, and *later* update the file format to
   need that version (which may then be older than PCB_FILE_VERSION).
   Hopefully, that allows for one release between adding support and
   needing it, which should minimize breakage.  Of course, that's not
   *always* possible, practical, or desirable.

*/

/* Hole[] in Polygon.  */
#define PCB_FILE_VERSION_HOLES 20100606
/* First version ever saved.  */
#define PCB_FILE_VERSION_BASELINE 20070407

int PCBFileVersionNeeded(void)
{
	PCB_POLY_ALL_LOOP(PCB->Data);
	{
		if (polygon->HoleIndexN > 0)
			return PCB_FILE_VERSION_HOLES;
	}
	PCB_ENDALL_LOOP;

	return PCB_FILE_VERSION_BASELINE;
}

/* In future all use of this should be supplanted by
 * pcb-printf and %mr/%m# spec */
static const char *c_dtostr(double d)
{
	static char buf[100];
	int i, f;
	char *bufp = buf;

	if (d < 0) {
		*bufp++ = '-';
		d = -d;
	}
	d += 0.0000005;								/* rounding */
	i = floor(d);
	d -= i;
	sprintf(bufp, "%d", i);
	bufp += strlen(bufp);
	*bufp++ = '.';

	f = floor(d * 1000000.0);
	sprintf(bufp, "%06d", f);
	return buf;
}

/* Returns pointer to private buffer */
static char *LayerGroupsToString(pcb_layer_stack_t *lg)
{
#if PCB_MAX_LAYER < 9998
	/* Allows for layer numbers 0..9999 */
	static char buf[(PCB_MAX_LAYER + 2) * 5 + 1];
#endif
	char *cp = buf;
	char sep = 0;
	int group, entry;
TODO("layer: revise this loop to save only what the original code saved")
	for (group = 0; group < pcb_max_group(PCB); group++)
		if (PCB->LayerGroups.grp[group].len) {
			unsigned int gflg = pcb_layergrp_flags(PCB, group);

			if (gflg & PCB_LYT_SILK) /* silk is hacked in asusming there's a top and bottom copper */
				continue;

			if (sep)
				*cp++ = ':';
			sep = 1;
			for (entry = 0; entry < PCB->LayerGroups.grp[group].len; entry++) {
				rnd_layer_id_t layer = PCB->LayerGroups.grp[group].lid[entry];

				sprintf(cp, "%ld", layer + 1);
				while (*++cp);

				if (entry != PCB->LayerGroups.grp[group].len - 1)
					*cp++ = ',';
			}

			if ((gflg & PCB_LYT_COPPER) && (gflg & PCB_LYT_TOP)) {
				*cp++ = ',';
				*cp++ = 'c';
			}
			else if ((gflg & PCB_LYT_COPPER) && (gflg & PCB_LYT_BOTTOM)) {
				*cp++ = ',';
				*cp++ = 's';
			}
		}
	*cp++ = 0;
	return buf;
}


static void WriteAttributeList(FILE * FP, pcb_attribute_list_t *list, const char *prefix, const char **inhibit)
{
	int i;
	const char **ih;

	for (i = 0; i < list->Number; i++) {
		if (inhibit != NULL) {
			for(ih = inhibit; *ih != NULL; ih++) {
				if (strcmp(list->List[i].name, *ih) == 0)
					goto skip;
			}
		}
		fprintf(FP, "%sAttribute(", prefix);
		pcb_print_quoted_string(FP, list->List[i].name);
		fputc(' ', FP);
		pcb_print_quoted_string(FP, list->List[i].value);
		fprintf(FP, ")\n");
		skip:;
	}
}

static void WritePCBInfoHeader(FILE * FP)
{
	/* write some useful comments */
	fprintf(FP, "# release: pcb-rnd " PCB_VERSION "\n");

	/* avoid writing things like user name or date, as these cause merge
	 * conflicts in collaborative environments using version control systems
	 */
}

static void conf_update_pcb_flag(pcb_flag_t *dest, const char *hash_path, int binflag)
{
	rnd_conf_native_t *n = rnd_conf_get_field(hash_path);
	struct {
		pcb_flag_t Flags;
	} *tmp = (void *)dest;

	if ((n == NULL) || (n->type != RND_CFN_BOOLEAN) || (n->used < 0) || (!n->val.boolean[0]))
		PCB_FLAG_CLEAR(binflag, tmp);
	else
		PCB_FLAG_SET(binflag, tmp);
}

/* data header: the name of the PCB, cursor location, zoom and grid
 * layergroups and some flags */
static void WritePCBDataHeader(FILE * FP)
{
	int group;
	pcb_flag_t pcb_flags;

	memset(&pcb_flags, 0, sizeof(pcb_flags));

	io_pcb_attrib_c2a(PCB);

	/* set binary flags from conf hash; these flags used to be checked
	   with PCB_FLAG_TEST() but got moved to the conf system */
	conf_update_pcb_flag(&pcb_flags, "plugins/mincut/enable", PCB_ENABLEPCB_FLAG_MINCUT);
	conf_update_pcb_flag(&pcb_flags, "editor/show_number", PCB_SHOWNUMBERFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/show_drc", PCB_SHOWPCB_FLAG_DRC);
	conf_update_pcb_flag(&pcb_flags, "editor/rubber_band_mode", PCB_RUBBERBANDFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/auto_drc", PCB_AUTOPCB_FLAG_DRC);
	conf_update_pcb_flag(&pcb_flags, "editor/all_direction_lines", PCB_ALLDIRECTIONFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/swap_start_direction", PCB_SWAPSTARTDIRFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/unique_names", PCB_UNIQUENAMEFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/clear_line", PCB_CLEARNEWFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/full_poly", PCB_NEWPCB_FLAG_FULLPOLY);
	conf_update_pcb_flag(&pcb_flags, "editor/snap_pin", PCB_SNAPPCB_FLAG_PIN);
	conf_update_pcb_flag(&pcb_flags, "editor/orthogonal_moves", PCB_ORTHOMOVEFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/live_routing", PCB_LIVEROUTEFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/lock_names", PCB_LOCKNAMESFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/only_names", PCB_ONLYNAMESFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/hide_names", PCB_HIDENAMESFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/thin_draw", PCB_THINDRAWFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/thin_draw_poly", PCB_THINDRAWPOLYFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/local_ref", PCB_LOCALREFFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/check_planes",PCB_CHECKPLANESFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/description", PCB_DESCRIPTIONFLAG);
	conf_update_pcb_flag(&pcb_flags, "editor/name_on_pcb", PCB_NAMEONPCBFLAG);

	fprintf(FP, "\n# To read pcb files, the pcb version (or the git source date) must be >= the file version\n");
	fprintf(FP, "FileVersion[%i]\n", PCBFileVersionNeeded());

	fputs("\nPCB[", FP);
	pcb_print_quoted_string(FP, (char *) RND_EMPTY(PCB->hidlib.name));
	rnd_fprintf(FP, " %[0] %[0]]\n\n", PCB->hidlib.dwg.X2, PCB->hidlib.dwg.Y2);
	rnd_fprintf(FP, "Grid[%[0] %[0] %[0] %d]\n", PCB->hidlib.grid, PCB->hidlib.grid_ox, PCB->hidlib.grid_oy, rnd_conf.editor.draw_grid);
	rnd_fprintf(FP, "Cursor[%[0] %[0] 1000]\n", pcb_crosshair.X, pcb_crosshair.Y);
	/* PolyArea should be output in square cmils, no suffix */
	fprintf(FP, "PolyArea[%s]\n", c_dtostr(RND_COORD_TO_MIL(RND_COORD_TO_MIL(conf_core.design.poly_isle_area) * 100) * 100));
	rnd_fprintf(FP, "Thermal[%s]\n", c_dtostr(PCB->ThermScale));
	rnd_fprintf(FP, "DRC[%[0] %[0] %[0] %[0] %[0] %[0]]\n", conf_core.design.bloat, conf_core.design.shrink,
							conf_core.design.min_wid, conf_core.design.min_slk, conf_core.design.min_drill, conf_core.design.min_ring);
	fprintf(FP, "Flags(%s)\n", pcb_strflg_board_f2s(pcb_flags));
	fprintf(FP, "Groups(\"%s\")\n", LayerGroupsToString(&PCB->LayerGroups));
	fputs("Styles[\"", FP);

	if (vtroutestyle_len(&PCB->RouteStyle) > 0) {
		rnd_coord_t drill_dia, pad_dia, mask;
		for (group = 0; group < vtroutestyle_len(&PCB->RouteStyle) - 1; group++) {
			pcb_compat_route_style_via_save(PCB->Data, &PCB->RouteStyle.array[group], &drill_dia, &pad_dia, &mask);
			rnd_fprintf(FP, "%s,%[0],%[0],%[0],%[0]:", PCB->RouteStyle.array[group].name,
				PCB->RouteStyle.array[group].Thick, pad_dia,
				drill_dia, PCB->RouteStyle.array[group].Clearance);
		}
		pcb_compat_route_style_via_save(PCB->Data, &PCB->RouteStyle.array[group], &drill_dia, &pad_dia, &mask);
		rnd_fprintf(FP, "%s,%[0],%[0],%[0],%[0]\"]\n\n", PCB->RouteStyle.array[group].name,
			PCB->RouteStyle.array[group].Thick, pad_dia,
			drill_dia, PCB->RouteStyle.array[group].Clearance);
	}
	else {
		pcb_io_incompat_save(PCB->Data, NULL, "route-style", "There are no routing styles - many versions of gEDA/PCB will segfault on loading the file", "Create exactly 4 routing styles.");
		fprintf(FP, "\"]\n\n");
	}
}

static void WritePCBFontData_rnd(FILE * FP)
{
	rnd_cardinal_t i, j;
	rnd_font_t *font = pcb_font(PCB, 0, 1);
	int warned = 0;

	if (PCB->fontkit.fonts.used > 0)
		pcb_io_incompat_save(PCB->Data, NULL, "font", "Can't save multiple fonts", "Only the default font is saved, all text objecst will use the default font.");

	for(i = 0; i <= RND_FONT_MAX_GLYPHS; i++) {
		rnd_glyph_atom_t *a;

		if (!font->glyph[i].valid)
			continue;

		if (isprint(i))
			rnd_fprintf(FP, "Symbol['%c' %[0]]\n(\n", i, font->glyph[i].xdelta);
		else
			rnd_fprintf(FP, "Symbol[%i %[0]]\n(\n", i, font->glyph[i].xdelta);

		for(a = font->glyph[i].atoms.array, j = font->glyph[i].atoms.used; j; j--, a++) {
			switch(a->type) {
				case RND_GLYPH_LINE:
					rnd_fprintf(FP, "\tSymbolLine[%[0] %[0] %[0] %[0] %[0]]\n",
						a->line.x1, a->line.y1, a->line.x2, a->line.y2, a->line.thickness);
					break;
				default:
					if (!warned) {
						pcb_io_incompat_save(PCB->Data, NULL, "font", "Font glyph contain non-line atoms in default font", "Use a line-only default font");
						warned = 1;
					}
			}
		}
		fputs(")\n", FP);
	}
}

#include "brave.h"

static void WritePCBFontData(FILE *FP)
{
	WritePCBFontData_rnd(FP);
}


static void WriteViaData(FILE * FP, pcb_data_t *Data)
{
	gdl_iterator_t it;
	pcb_pstk_t *ps;

	/* write information about vias */
	padstacklist_foreach(&Data->padstack, &it, ps) {
		rnd_coord_t x, y, drill_dia, pad_dia, clearance, mask;
		pcb_pstk_compshape_t cshape;
		rnd_bool plated;
		char *name = pcb_attribute_get(&ps->Attributes, "name");

		if (!pcb_pstk_export_compat_via(ps, &x, &y, &drill_dia, &pad_dia, &clearance, &mask, &cshape, &plated)) {
			pcb_io_incompat_save(Data, (pcb_any_obj_t *)ps, "via", "Failed to convert to old-style via", "Old via format is very much restricted; try to use a simpler, uniform shape padstack");
			continue;
		}

		rnd_fprintf(FP, "Via[%[0] %[0] %[0] %[0] %[0] %[0] ", x, y,
			pad_dia, clearance*2, mask, drill_dia);
		pcb_print_quoted_string(FP, (char *) RND_EMPTY(name));
		fprintf(FP, " %s]\n", pcb_strflg_f2s(pcb_pstk_compat_pinvia_flag(ps, cshape, PCB_PSTKCOMP_OLD_OCTAGON), PCB_OBJ_VIA, NULL, 1));
	}
}

static void WritePCBRatData(FILE * FP)
{
	gdl_iterator_t it;
	pcb_rat_t *line;

	/* write information about rats */
	ratlist_foreach(&PCB->Data->Rat, &it, line) {
		rnd_fprintf(FP, "Rat[%[0] %[0] %d %[0] %[0] %d ",
								line->Point1.X, line->Point1.Y, line->group1, line->Point2.X, line->Point2.Y, line->group2);
		fprintf(FP, " %s]\n", F2S(line, PCB_OBJ_RAT));
	}
}

static void WritePCBNetlistData(FILE *FP)
{
	htsp_entry_t *e;

	/* write out the netlist if it exists */
	if (PCB->netlist[PCB_NETLIST_INPUT].used < 1)
		return;

	fprintf(FP, "NetList()\n(\n");

	for(e = htsp_first(&PCB->netlist[PCB_NETLIST_INPUT]); e != NULL; e = htsp_next(&PCB->netlist[PCB_NETLIST_INPUT], e)) {
		pcb_net_term_t *t;
		pcb_net_t *net = e->value;

		fprintf(FP, "\tNet(");
		pcb_print_quoted_string(FP, net->name);
		fprintf(FP, " ");
		pcb_print_quoted_string(FP, (char *) RND_UNKNOWN(pcb_attribute_get(&net->Attributes, "style")));
		fprintf(FP, ")\n\t(\n");
		for(t = pcb_termlist_first(&net->conns); t != NULL; t = pcb_termlist_next(t)) {
			fprintf(FP, "\t\tConnect(\"");
			pcb_print_quoted_string_(FP, t->refdes);
			fputc('-', FP);
			pcb_print_quoted_string_(FP, t->term);
			fprintf(FP, "\")\n");
		}
		fprintf(FP, "\t)\n");
	}
	fprintf(FP, ")\n");
}

static void WritePCBNetlistPatchData(FILE * FP)
{
	if (PCB->NetlistPatches != NULL) {
		pcb_io_incompat_save(PCB->Data, NULL, "net-patch", "Saving netlist patch makes the file incompatible with geda/pcb.", "Either remove (or resolve) your netlist patches before save or remove the NetListPatch() subtree manually from the saved file before using with geda/pcb.");
		fprintf(FP, "NetListPatch()\n(\n");
		pcb_ratspatch_fexport(PCB, FP, PCB_RPFM_PCB);
		fprintf(FP, ")\n");
	}
}

static void io_pcb_print_subc(pcb_plug_io_t *ctx, FILE *FP, pcb_subc_t *sc)
{
	static const char *attr_inhibit[] = {"refdes", "value", "footprint", "io_pcb::hidename_x", "io_pcb::hidename_y", "io_pcb::hidename_direction", "io_pcb::hidename_scale", NULL}; /* these are saved in the header if the element */
	gdl_iterator_t it;
	int l;

		rnd_coord_t ox, oy, rx = 0, ry = 0;
		int rdir = 0, rscale = 100, on_bot = 0;
		pcb_text_t *trefdes;
		pcb_pstk_t *ps;
		pcb_any_obj_t fobj;

		pcb_subc_get_origin(sc, &ox, &oy);
		trefdes = pcb_subc_get_refdes_text(sc);
		if (pcb_subc_get_side(sc, &on_bot) != 0)
			pcb_io_incompat_save(sc->parent.data, (pcb_any_obj_t *)sc, "element-side", "Can not determine element side", "Missing or botched subc aux layer; can not tell if subc is on top or bottom layer, using top.");

		if (trefdes != NULL) {
			int scale;
			if (pcb_text_old_scale(trefdes, &scale) != 0)
				pcb_io_incompat_save(sc->data, (pcb_any_obj_t *)trefdes, "text-scale", "file format does not support different x and y direction text scale - using average scale", "Use the scale field, set scale_x and scale_y to 0");
			if (trefdes->mirror_x)
				pcb_io_incompat_save(NULL, (pcb_any_obj_t *)trefdes, "text-mirror-x", "file format does not support different mirroring text in the x direction", "do not mirror, or mirror in the y direction (with the ONSOLDER flag)");

			rx = trefdes->X - ox;
			ry = trefdes->Y - oy;
			if (!pcb_text_old_direction(&rdir, trefdes->rot))
				pcb_io_incompat_save(NULL, (pcb_any_obj_t *)trefdes, "text rotation", "text rotation angle rounded", "the gEDA/PCB file format does not support text rotation other than multiple of 90 degree");
			rscale = scale;
		}
		else {
			const char *tmp;
			tmp = pcb_attribute_get(&sc->Attributes, "io_pcb::hidename_x");
			if (tmp != NULL)
				rx = rnd_get_value(tmp, NULL, NULL, NULL) - ox;
			tmp = pcb_attribute_get(&sc->Attributes, "io_pcb::hidename_y");
			if (tmp != NULL)
				ry = rnd_get_value(tmp, NULL, NULL, NULL) - oy;
			tmp = pcb_attribute_get(&sc->Attributes, "io_pcb::hidename_direction");
			if (tmp != NULL)
				rdir = atoi(tmp);
			tmp = pcb_attribute_get(&sc->Attributes, "io_pcb::hidename_scale");
			if (tmp != NULL)
				rscale = atoi(tmp);
		}

		memset(&fobj, 0, sizeof(fobj));
		fobj.Flags = sc->Flags;
		if (trefdes == NULL)
			fobj.Flags.f |= PCB_FLAG_HIDENAME;
		if (on_bot)
			fobj.Flags.f |= PCB_FLAG_ONSOLDER;
		fprintf(FP, "\nElement[%s ", F2S(&fobj, PCB_OBJ_ELEMENT));
		pcb_print_quoted_string(FP, (char *) RND_EMPTY(pcb_attribute_get(&sc->Attributes, "footprint")));
		fputc(' ', FP);
		pcb_print_quoted_string(FP, (char *) RND_EMPTY(pcb_attribute_get(&sc->Attributes, "refdes")));
		fputc(' ', FP);
		pcb_print_quoted_string(FP, (char *) RND_EMPTY(pcb_attribute_get(&sc->Attributes, "value")));
		rnd_fprintf(FP, " %[0] %[0] %[0] %[0] %d %d %s]\n(\n", ox, oy, rx, ry, rdir, rscale, trefdes != NULL ? F2S(trefdes, PCB_OBJ_ELEMENT_NAME) : "\"\"");
		WriteAttributeList(FP, &sc->Attributes, "\t", attr_inhibit);

		padstacklist_foreach(&sc->data->padstack, &it, ps) {
			rnd_coord_t x, y, drill_dia, pad_dia, clearance, mask, x1, y1, x2, y2, thickness;
			pcb_pstk_compshape_t cshape;
			rnd_bool plated, square, nopaste;
			unsigned char ic = ps->intconn;
			if (pcb_pstk_export_compat_via(ps, &x, &y, &drill_dia, &pad_dia, &clearance, &mask, &cshape, &plated)) {
				rnd_fprintf(FP, "\tPin[%[0] %[0] %[0] %[0] %[0] %[0] ", x - ox, y - oy, pad_dia, clearance*2, mask, drill_dia);
				pcb_print_quoted_string(FP, (char *)RND_EMPTY(pcb_attribute_get(&ps->Attributes, "name")));
				fprintf(FP, " ");
				pcb_print_quoted_string(FP, (char *) RND_EMPTY(pcb_attribute_get(&ps->Attributes, "term")));
				fprintf(FP, " %s]\n", pcb_strflg_f2s(pcb_pstk_compat_pinvia_flag(ps, cshape, PCB_PSTKCOMP_OLD_OCTAGON | PCB_PSTKCOMP_PCB_CLEARLINE_WORKAROUND), PCB_OBJ_PIN, &ic, 1));
			}
			else if (pcb_pstk_export_compat_pad(ps, &x1, &y1, &x2, &y2, &thickness, &clearance, &mask, &square, &nopaste)) {
				unsigned long fl = (square ? PCB_FLAG_SQUARE : 0) | (nopaste ? PCB_FLAG_NOPASTE : 0) | (on_bot ? PCB_FLAG_ONSOLDER : 0);
				int n;

				if (!PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, ps)) {
					/* no clearline in pcb-rnd means pad has solid conneciton to poly; this is emulated with clearance=0 in pcb */
					clearance = 0;
				}
				
				/* can't save thermals on pads */
				for(n = 0; n < sizeof(ps->Flags.t) / sizeof(ps->Flags.t[0]); n++) {
					unsigned char *ot = pcb_pstk_get_thermal(ps, n, 0);
					if ((ot != NULL) && ((*ot) & PCB_THERMAL_ON)) {
						pcb_io_incompat_save(PCB->Data, (pcb_any_obj_t *)ps, "smd-thermal", "SMD pad thermal", "Geda/PCB has no thermals on SMD; saved with solid connection instead, manual soldering will be very hard on this pad.");
						clearance = 0;
						break;
					}
				}

				rnd_fprintf(FP, "\tPad[%[0] %[0] %[0] %[0] %[0] %[0] %[0] ",
					x1 - ox, y1 - oy, x2 - ox, y2 - oy, thickness, clearance, mask);
					pcb_print_quoted_string(FP, (char *)RND_EMPTY(pcb_attribute_get(&ps->Attributes, "name")));
					fprintf(FP, " ");
					pcb_print_quoted_string(FP, (char *) RND_EMPTY(pcb_attribute_get(&ps->Attributes, "term")));
					fprintf(FP, " %s]\n", pcb_strflg_f2s(pcb_flag_make(fl), PCB_OBJ_PAD, &ic, 1));
			}
			else
				pcb_io_incompat_save(sc->data, (pcb_any_obj_t *)ps, "pin-pad", "Padstack can not be exported as pin or pad", "use simpler padstack; for pins, all copper layers must have the same shape and there must be no paste; for pads, use a line or a rectangle; paste and mask must match the copper shape");
		}

		for(l = 0; l < sc->data->LayerN; l++) {
			pcb_layer_type_t my_side;
			pcb_layer_t *ly = &sc->data->Layer[l];
			pcb_line_t *line;
			pcb_arc_t *arc;

			my_side = on_bot ? PCB_LYT_BOTTOM : PCB_LYT_TOP;

			if ((ly->meta.bound.type & PCB_LYT_SILK) && (ly->meta.bound.type & my_side)) {
				linelist_foreach(&ly->Line, &it, line) {
					rnd_fprintf(FP, "\tElementLine [%[0] %[0] %[0] %[0] %[0]]\n",
						line->Point1.X - ox, line->Point1.Y - oy,
						line->Point2.X - ox, line->Point2.Y - oy,
						line->Thickness);
				}
				arclist_foreach(&ly->Arc, &it, arc) {
					rnd_fprintf(FP, "\tElementArc [%[0] %[0] %[0] %[0] %ma %ma %[0]]\n",
						arc->X - ox, arc->Y - oy, arc->Width, arc->Height,
						arc->StartAngle, arc->Delta, arc->Thickness);
				}
				{
					pcb_gfx_t *gfx;
					for(gfx = gfxlist_first(&ly->Gfx); gfx != NULL; gfx = gfxlist_next(gfx))
						pcb_io_incompat_save(sc->data, (pcb_any_obj_t *)gfx, "element-obj", "gfx can not be exported in this format, please use lihata", "only lines and arcs are exported");
				}
				if (polylist_length(&ly->Polygon) > 0) {
					char *desc = rnd_strdup_printf("Polygons on layer %s can not be exported in an element", ly->name);
					pcb_io_incompat_save(sc->data, NULL, "element-obj", desc, "only lines and arcs are exported");
					free(desc);
				}
				if (textlist_length(&ly->Text) > 1) {
					char *desc = rnd_strdup_printf("Text on layer %s can not be exported in an element", ly->name);
					pcb_io_incompat_save(sc->data, NULL, "element-obj", desc, "only lines and arcs are exported");
					free(desc);
				}
				continue;
			}

			if (!(ly->meta.bound.type & PCB_LYT_VIRTUAL) && (!pcb_layer_is_pure_empty(ly))) {
				char *desc = rnd_strdup_printf("Objects on layer %s can not be exported in an element", ly->name);
				pcb_io_incompat_save(sc->data, NULL, "element-layer", desc, "only top silk lines and arcs are exported; heavy terminals are not supported, use padstacks only");
				free(desc);
			}
		}
		fputs("\n)\n", FP);
}

static int io_pcb_WriteSubcData(pcb_plug_io_t *ctx, FILE *FP, pcb_data_t *Data, long subc_idx)
{
	gdl_iterator_t sit;
	pcb_subc_t *sc;

	rnd_printf_slot[0] = ((io_pcb_ctx_t *)(ctx->plugin_data))->write_coord_fmt;

	subclist_foreach(&Data->subc, &sit, sc) {
		if ((subc_idx != -1) && (subc_idx != sit.count))
			continue;

		io_pcb_print_subc(ctx, FP, sc);
	}
	return 0;
}

int io_pcb_write_subcs_head(pcb_plug_io_t *ctx, void **udata, FILE *f, int lib, long num_subcs)
{
	return 0;
}

int io_pcb_write_subcs_subc(pcb_plug_io_t *ctx, void **udata, FILE *f, pcb_subc_t *subc)
{
	rnd_printf_slot[0] = ((io_pcb_ctx_t *)(ctx->plugin_data))->write_coord_fmt;
	io_pcb_print_subc(ctx, f, subc);
	return 0;
}

int io_pcb_write_subcs_tail(pcb_plug_io_t *ctx, void **udata, FILE *f)
{
	return 0;
}

static const char *layer_name_hack(pcb_layer_t *layer, const char *name)
{
	unsigned long lflg = pcb_layer_flags_(layer);
	int purpi = pcb_layer_purpose_(layer, NULL);
	/* The old PCB format encodes some properties in layer names - have to
	   alter the real layer name before save to get the same effect */
	if (PCB_LAYER_IS_OUTLINE(lflg, purpi)) {
		if (rnd_strcasecmp(name, "outline") == 0)
			return name;
		return "Outline";
	}
	if (lflg & PCB_LYT_SILK) {
		if (rnd_strcasecmp(name, "silk") == 0)
			return name;
		return "silk";
	}

	/* plain layer */
	return name;
}

static void WriteLayerData(FILE * FP, rnd_cardinal_t Number, pcb_layer_t *layer)
{
	gdl_iterator_t it;
	pcb_line_t *line;
	pcb_arc_t *arc;
	pcb_text_t *text;
	pcb_poly_t *polygon;

	/* write information about non empty layers */
	if (!pcb_layer_is_empty_(PCB, layer) || (layer->name && *layer->name)) {
		fprintf(FP, "Layer(%i ", (int) Number + 1);
		pcb_print_quoted_string(FP, layer_name_hack(layer, RND_EMPTY(layer->name)));
		fputs(")\n(\n", FP);
		WriteAttributeList(FP, &layer->Attributes, "\t", NULL);

		linelist_foreach(&layer->Line, &it, line) {
			rnd_fprintf(FP, "\tLine[%[0] %[0] %[0] %[0] %[0] %[0] %s]\n",
									line->Point1.X, line->Point1.Y,
									line->Point2.X, line->Point2.Y, line->Thickness, line->Clearance, F2S(line, PCB_OBJ_LINE));
		}
		{
			pcb_gfx_t *gfx;
			for(gfx = gfxlist_first(&layer->Gfx); gfx != NULL; gfx = gfxlist_next(gfx))
				pcb_io_incompat_save(PCB->Data, (pcb_any_obj_t *)gfx, "gfx", "gfx can not be exported in this format", "please use the lihata format");
		}
		arclist_foreach(&layer->Arc, &it, arc) {
			rnd_fprintf(FP, "\tArc[%[0] %[0] %[0] %[0] %[0] %[0] %ma %ma %s]\n",
									arc->X, arc->Y, arc->Width,
									arc->Height, arc->Thickness, arc->Clearance, arc->StartAngle, arc->Delta, F2S(arc, PCB_OBJ_ARC));
		}
		textlist_foreach(&layer->Text, &it, text) {
			int dir;
			int scale;
			if (pcb_text_old_scale(text, &scale) != 0)
				pcb_io_incompat_save(PCB->Data, (pcb_any_obj_t *)text, "text-scale", "file format does not support different x and y direction text scale - using average scale", "Use the scale field, set scale_x and scale_y to 0");
			if (text->mirror_x)
				pcb_io_incompat_save(NULL, (pcb_any_obj_t *)text, "text-mirror-x", "file format does not support different mirroring text in the x direction", "do not mirror, or mirror in the y direction (with the ONSOLDER flag)");
			if (!pcb_text_old_direction(&dir, text->rot))
				pcb_io_incompat_save(NULL, (pcb_any_obj_t *)text, "text rotation", "text rotation angle rounded", "the gEDA/PCB file format does not support text rotation other than multiple of 90 degree");
			if (text->clearance != 0)
				pcb_io_incompat_save(NULL, (pcb_any_obj_t *)text, "text-clearance", "file format does not support custom text clearance value", "do not use text clearance or save in lihata >=v8");
			rnd_fprintf(FP, "\tText[%[0] %[0] %d %d ", text->X, text->Y, dir, scale);
			pcb_print_quoted_string(FP, (char *) RND_EMPTY(text->TextString));
			fprintf(FP, " %s]\n", F2S(text, PCB_OBJ_TEXT));
		}
		textlist_foreach(&layer->Polygon, &it, polygon) {
			int p, i = 0;
			rnd_cardinal_t hole = 0;
			fprintf(FP, "\tPolygon(%s)\n\t(", F2S(polygon, PCB_OBJ_POLY));
			for (p = 0; p < polygon->PointN; p++) {
				rnd_point_t *point = &polygon->Points[p];

				if (hole < polygon->HoleIndexN && p == polygon->HoleIndex[hole]) {
					if (hole > 0)
						fputs("\n\t\t)", FP);
					fputs("\n\t\tHole (", FP);
					hole++;
					i = 0;
				}

				if (i++ % 5 == 0) {
					fputs("\n\t\t", FP);
					if (hole)
						fputs("\t", FP);
				}
				rnd_fprintf(FP, "[%[0] %[0]] ", point->X, point->Y);
			}
			if (hole > 0)
				fputs("\n\t\t)", FP);
			fputs("\n\t)\n", FP);
		}
		fputs(")\n", FP);
	}
}

static rnd_layer_id_t pcb_layer_get_cached(pcb_board_t *pcb, unsigned int loc, unsigned int typ)
{
	pcb_layergrp_t *g = pcb_get_grp(&pcb->LayerGroups, loc, typ);
	if ((g == NULL) || (g->len == 0))
		return -1;
	if (g->len > 1)
		pcb_io_incompat_save(PCB->Data, NULL, "layer", "Multiple silk layers per side not supported by gEDA/PCB", "Merge your silk layers into one layer per group; if there are negative silk layers: layer compositing is not supported by gEDA/PCB");
	return g->lid[0];
}

static rnd_layer_id_t pcb_layer_get_bottom_silk(void)
{
	return pcb_layer_get_cached(PCB, PCB_LYT_BOTTOM, PCB_LYT_SILK);
}

static rnd_layer_id_t pcb_layer_get_top_silk(void)
{
	return pcb_layer_get_cached(PCB, PCB_LYT_TOP, PCB_LYT_SILK);
}

static void LayersFixup(void)
{
	rnd_layer_id_t bs, ts;
	int chg = 0;

	/* the PCB format requires 2 silk layers to be the last; swap layers to make that so */
	bs = pcb_layer_get_bottom_silk();
	ts = pcb_layer_get_top_silk();

	if ((bs < 0) || (ts < 0)) {
		rnd_message(RND_MSG_ERROR, "The geda/pcb file format requires top and bottom silk layers.\nExporting a board without those will not be usable in geda/pcb.\n");
		return;
	}

	if (bs != pcb_max_layer(PCB) - 2) {
		pcb_layer_swap(PCB, bs, pcb_max_layer(PCB) - 2);
		chg = 1;
	}

	bs = pcb_layer_get_bottom_silk();
	ts = pcb_layer_get_top_silk();

	if (ts != pcb_max_layer(PCB) - 1) {
		pcb_layer_swap(PCB, ts, pcb_max_layer(PCB) - 1);
		chg = 1;
	}

	if (chg) {
		rnd_message(RND_MSG_ERROR, "The geda/pcb file format requires top and bottom silk be the last layers in the list.\nI had to swap layers in your board _before_ the save.\nAt the moment this is not undoable!\n");
		rnd_event(&PCB->hidlib, PCB_EVENT_LAYERS_CHANGED, NULL);
	}
}

static void WriteLayers(FILE *FP, pcb_data_t *data)
{
	int i;
	for (i = 0; i < data->LayerN; i++) {
		pcb_layer_t *ly = &(data->Layer[i]);
		pcb_layer_type_t lyt = pcb_layer_flags_(ly);
		int purpi = pcb_layer_purpose_(ly, NULL);
		if ((!(lyt & (PCB_LYT_COPPER | PCB_LYT_SILK))) && (!PCB_LAYER_IS_ROUTE(lyt, purpi))) {
			if (!pcb_layer_is_pure_empty(ly)) {
				char *desc = rnd_strdup_printf("Layer %s can be exported only as a copper layer", ly->name);
				pcb_io_incompat_save(data, NULL, "layer", desc, NULL);
				free(desc);
			}
		}
		WriteLayerData(FP, i, ly);
	}
}

int io_pcb_WritePCB(pcb_plug_io_t *ctx, FILE * FP, const char *old_filename, const char *new_filename, rnd_bool emergency)
{
	pcb_attribute_put(&PCB->Attributes, "PCB::loader", ctx->description);

	LayersFixup();

	rnd_printf_slot[0] = ((io_pcb_ctx_t *)(ctx->plugin_data))->write_coord_fmt;
	WritePCBInfoHeader(FP);
	WritePCBDataHeader(FP);
	WritePCBFontData(FP);
	WriteAttributeList(FP, &PCB->Attributes, "", NULL);
	WriteViaData(FP, PCB->Data);
	io_pcb_WriteSubcData(ctx, FP, PCB->Data, -1);
	WritePCBRatData(FP);
	WriteLayers(FP, PCB->Data);
	WritePCBNetlistData(FP);
	WritePCBNetlistPatchData(FP);

	return 0;
}

/*** functions for loading elements-as-pcb ***/
extern pcb_board_t *yyPCB;
extern pcb_data_t *yyData;
extern	rnd_font_t *yyRndFont;
extern int yyElemFixLayers;

void PreLoadElementPCB()
{

	if (!yyPCB)
		return;

	yyRndFont = &yyPCB->fontkit.dflt;
	yyData = yyPCB->Data;
	PCB_SET_PARENT(yyData, board, yyPCB);
	yyData->LayerN = 0;
}

void PostLoadElementPCB()
{
	pcb_board_t *pcb_save = PCB;
	rnd_box_t dbb;
	pcb_subc_t *sc;

	if (!yyPCB)
		return;

	pcb_board_new_postproc(yyPCB, 0);
	pcb_layer_group_setup_default(yyPCB);
	PCB = yyPCB;
	pcb_layer_group_setup_silks(yyPCB);
	pcb_data_bbox(&dbb, yyPCB->Data, rnd_false);
	pcb_data_normalize_(yyPCB->Data, &dbb);
	PCB = pcb_save;
	yyPCB->hidlib.dwg.X2 = dbb.X2*2;
	yyPCB->hidlib.dwg.Y2 = dbb.Y2*2;
	yyPCB->is_footprint = 1;

	/* opening a footprint: we don't have a layer stack; make sure top and bottom copper exist */
	{
		rnd_layergrp_id_t gid;
		int res;
		
		res = pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_COPPER, &gid, 1);
		assert(res == 1);
		pcb_layer_create(PCB, gid, "top copper", 0);
		res = pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_COPPER, &gid, 1);
		assert(res == 1);
		pcb_layer_create(PCB, gid, "bottom copper", 0);
	}

	pcb_layergrp_upgrade_to_pstk(yyPCB);

	sc = pcb_subclist_first(&yyPCB->Data->subc);
	if (sc != NULL) {
		pcb_layer_create_all_for_recipe(yyPCB, sc->data->Layer, sc->data->LayerN);
		pcb_subc_rebind(yyPCB, sc);
		pcb_data_clip_polys(sc->data);
	}
}


int io_pcb_test_parse(pcb_plug_io_t *ctx, pcb_plug_iot_t typ, const char *Filename, FILE *f)
{
	char line[1024];
	int bad = 0;

/*
	Look for any of these in the top few lines of the file:
	# release: pcb-bin 20050609
	PCB["name" 600000 500000]

	or
	PCB("name" 600000 500000]
*/

	while(!(feof(f))) {
		if (fgets(line, sizeof(line), f) != NULL) {
			char *s = line;
			while(isspace(*s)) s++;
			if ((strncmp(s, "# release: pcb", 14) == 0) )
				return 1;
			if (strncmp(s, "PCB", 3) == 0) {
				char *b = s+3;
				while(isspace(*b)) b++;
				if ((*b == '(') || (*b == '['))
					return 1;
			}
			if (strncmp(s, "Element", 7) == 0) {
				char *b = s+7;
				while(isspace(*b)) b++;
				if ((*b == '(') || (*b == '['))
					return 1;
			}
			if ((*s == '\r') || (*s == '\n') || (*s == '#') || (*s == '\0')) /* ignore empty lines and comments */
				continue;
			/* non-comment, non-empty line: tolerate at most 16 of these before giving up */
			bad++;
			if (bad > 16)
				return 0;
		}
	}

	/* hit eof before finding anything familiar or too many bad lines: the file
	is surely not a .pcb */
	return 0;
}


rnd_layer_id_t static new_ly_end(pcb_board_t *pcb, const char *name)
{
	rnd_layer_id_t lid;
	if (pcb->Data->LayerN >= PCB_MAX_LAYER)
		return -1;
	lid = pcb->Data->LayerN;
	pcb->Data->Layer[lid].name = rnd_strdup(name);
	pcb->Data->Layer[lid].parent.data = pcb->Data;
	pcb->Data->Layer[lid].parent_type = PCB_PARENT_DATA;
	pcb->Data->Layer[lid].type = PCB_OBJ_LAYER;
	pcb->Data->LayerN++;
	return lid;
}

rnd_layer_id_t static existing_or_new_ly_end(pcb_board_t *pcb, const char *name)
{
	rnd_layer_id_t lid = pcb_layer_by_name(pcb->Data, name);
	if (lid >= 0) {
		if (pcb->Data->Layer[lid].meta.real.grp >= 0) {
			rnd_layergrp_id_t gid = pcb->Data->Layer[lid].meta.real.grp;
			pcb_layergrp_del_layer(pcb, gid, lid);
			pcb->Data->Layer[lid].meta.real.grp = -1;
		}
		return lid;
	}
	return new_ly_end(pcb, name);
}

rnd_layer_id_t static new_ly_old(pcb_board_t *pcb, const char *name)
{
	rnd_layer_id_t lid;
	for(lid = 0; lid < PCB_MAX_LAYER; lid++) {
		if (pcb->Data->Layer[lid].meta.real.grp == 0) {
			free((char *)pcb->Data->Layer[lid].name);
			pcb->Data->Layer[lid].name = rnd_strdup(name);
			pcb->Data->Layer[lid].parent.data = pcb->Data;
			pcb->Data->Layer[lid].parent_type = PCB_PARENT_DATA;
			pcb->Data->Layer[lid].type = PCB_OBJ_LAYER;
			return lid;
		}
	}
	return -1;
}

int pcb_layer_improvise(pcb_board_t *pcb, rnd_bool setup)
{
	rnd_layergrp_id_t gid;
	rnd_layer_id_t lid, silk = -1;

	if (setup) {
		pcb_layer_group_setup_default(pcb);

		/* make sure every layer has a name */
		for(lid = 0; lid < pcb->Data->LayerN; lid++)
			if (pcb->Data->Layer[lid].name == NULL)
				pcb->Data->Layer[lid].name = rnd_strdup_printf("anon_%d", lid);

		for(lid = 0; lid < pcb->Data->LayerN; lid++) {
			if (strcmp(pcb->Data->Layer[lid].name, "silk") == 0) {
				if (silk < 0)
					pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_SILK, &gid, 1);
				else
					pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_SILK, &gid, 1);
				pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
				silk = lid;
			}
			else {
				if (*pcb->Data->Layer[lid].name == '\0') {
					free((char *)pcb->Data->Layer[lid].name);
					pcb->Data->Layer[lid].name = rnd_strdup("anonymous");
				}
				if (lid == 0)
					pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_COPPER, &gid, 1);
				else
					pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_COPPER, &gid, 1);
				pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
			}
		}

		pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_SILK, &gid, 1);
		if (pcb->LayerGroups.grp[gid].len < 1) {
			lid = new_ly_end(pcb, "silk");
			if (lid < 0)
				return -1;
			pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
		}

		pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_SILK, &gid, 1);
		if (pcb->LayerGroups.grp[gid].len < 1) {
			lid = new_ly_end(pcb, "silk");
			if (lid < 0)
				return -1;
			pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
		}

		pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_COPPER, &gid, 1);
		if (pcb->LayerGroups.grp[gid].len < 1) {
			lid = new_ly_old(pcb, "top_copper");
			if (lid < 0)
				return -1;
			pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
		}

		pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_COPPER, &gid, 1);
		if (pcb->LayerGroups.grp[gid].len < 1) {
			lid = new_ly_old(pcb, "bottom_copper");
			if (lid < 0)
				return -1;
			pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
		}
	}

	pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_MASK, &gid, 1);
	if (pcb->LayerGroups.grp[gid].len < 1) {
		lid = existing_or_new_ly_end(pcb, "top-mask");
		if (lid < 0)
			return -1;
		pcb->Data->Layer[lid].comb = PCB_LYC_AUTO | PCB_LYC_SUB;
		pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
	}

	pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_MASK, &gid, 1);
	if (pcb->LayerGroups.grp[gid].len < 1) {
		lid = existing_or_new_ly_end(pcb, "bottom-mask");
		if (lid < 0)
			return -1;
		pcb->Data->Layer[lid].comb = PCB_LYC_AUTO | PCB_LYC_SUB;
		pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
	}

	pcb_layergrp_list(PCB, PCB_LYT_TOP | PCB_LYT_PASTE, &gid, 1);
	if (pcb->LayerGroups.grp[gid].len < 1) {
		lid = existing_or_new_ly_end(pcb, "top-paste");
		if (lid < 0)
			return -1;
		pcb->Data->Layer[lid].comb = PCB_LYC_AUTO;
		pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
	}

	pcb_layergrp_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_PASTE, &gid, 1);
	if (pcb->LayerGroups.grp[gid].len < 1) {
		lid = existing_or_new_ly_end(pcb, "bottom-paste");
		if (lid < 0)
			return -1;
		pcb->Data->Layer[lid].comb = PCB_LYC_AUTO;
		pcb_layer_add_in_group_(pcb, &pcb->LayerGroups.grp[gid], gid, lid);
	}

/*	rnd_action(&pcb->hidlib, "dumplayers");*/

	return 0;
}

/*** Compatibility wrapper: create blind/burried via ***/


pcb_pstk_t *pcb_old_via_new_bb(pcb_data_t *data, long int id, rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Thickness, rnd_coord_t Clearance, rnd_coord_t Mask, rnd_coord_t DrillingHole, const char *Name, pcb_flag_t Flags, int startli, int endli)
{
	pcb_pstk_t *pstk;

	if (startli == endli) {
		/* geda/pcb 4.3 takes this as a thru-hole */
		rnd_message(RND_MSG_ERROR, "io_pcb: invalid bb via at %$mm;%$mm: start and end layers are the same\n", X, Y);
		goto error_thru;
	}

	if ((startli < 0) || (endli < 0)) {
		/* geda/pcb 4.3 segfaults; take it as thru-hole */
		rnd_message(RND_MSG_ERROR, "io_pcb: invalid bb via at %$mm;%$mm: start or end layer negative\n", X, Y);
		goto error_thru;
	}

	if ((startli >= compat_pstk_cop_grps) || (endli >= compat_pstk_cop_grps)) {
		/* geda/pcb 4.3 segfaults; take it as thru-hole */
		rnd_message(RND_MSG_ERROR, "io_pcb: invalid bb via at %$mm;%$mm: start or end layer is above the number of copper layers\n", X, Y);
		error_thru:;
		pstk = pcb_old_via_new(data, id, X, Y, Thickness, Clearance, Mask, DrillingHole, Name, Flags);
		return pstk;
	}

	if (startli < endli)
		pstk = pcb_old_via_new_bbvia(data, id, X, Y, Thickness, Clearance, Mask, DrillingHole, Name, Flags, startli, -endli);
	else
		pstk = pcb_old_via_new_bbvia(data, id, X, Y, Thickness, Clearance, Mask, DrillingHole, Name, Flags, endli, -startli);

/*	rnd_trace("bbvia: %ml;%ml %d..%d %p\n", X, Y, startli, endli, pstk);*/

	return pstk;
}


/*** Compatibility wrappers: create padstack and subc as if they were vias and elements ***/

static int yysubc_bottom;
extern	pcb_subc_t *yysubc;
extern	rnd_coord_t yysubc_ox, yysubc_oy;

pcb_subc_t *io_pcb_element_new(pcb_data_t *Data, pcb_subc_t *subc,
	rnd_font_t *PCBFont, pcb_flag_t Flags, char *Description, char *NameOnPCB,
	char *Value, rnd_coord_t TextX, rnd_coord_t TextY, unsigned int Direction,
	int TextScale, pcb_flag_t TextFlags, rnd_bool uniqueName)
{
	pcb_subc_t *sc = pcb_subc_new();
	pcb_text_t *txt;
	pcb_subc_reg(Data, sc);
	if (Data->padstack_tree == NULL)
		rnd_rtree_init(Data->padstack_tree = malloc(sizeof(rnd_rtree_t)));
	sc->data->padstack_tree = Data->padstack_tree;

	yysubc_ox = 0;
	yysubc_oy = 0;
	yysubc_bottom = !!(Flags.f & PCB_FLAG_ONSOLDER);
	Flags.f &= ~PCB_FLAG_ONSOLDER;

	PCB_FLAG_SET(Flags.f, sc);

	if (Description != NULL)
		pcb_attribute_put(&sc->Attributes, "footprint", Description);
	if (NameOnPCB != NULL)
		pcb_attribute_put(&sc->Attributes, "refdes", NameOnPCB);
	if (Value != NULL)
		pcb_attribute_put(&sc->Attributes, "value", Value);

TODO("subc: TextFlags")
	if (!(Flags.f & PCB_FLAG_HIDENAME))
		txt = pcb_subc_add_refdes_text(sc, TextX, TextY, Direction, TextScale, yysubc_bottom);

	if (Flags.f & PCB_FLAG_HIDENAME) {
		char tmp[128];
		rnd_sprintf(tmp, "%$mm", TextX);
		pcb_attribute_put(&sc->Attributes, "io_pcb::hidename_x", tmp);
		rnd_sprintf(tmp, "%$mm", TextY);
		pcb_attribute_put(&sc->Attributes, "io_pcb::hidename_y", tmp);
		sprintf(tmp, "%d", Direction);
		pcb_attribute_put(&sc->Attributes, "io_pcb::hidename_direction", tmp);
		sprintf(tmp, "%d", TextScale);
		pcb_attribute_put(&sc->Attributes, "io_pcb::hidename_scale", tmp);
	}

	return sc;
}

void io_pcb_element_fin(pcb_data_t *Data)
{
	const char *cent;

	pcb_subc_xy_rot_pnp(yysubc, yysubc_ox, yysubc_oy, yysubc_bottom);

	pcb_subc_bbox(yysubc);
	if (Data->subc_tree == NULL)
		rnd_rtree_init(Data->subc_tree = malloc(sizeof(rnd_rtree_t)));
	rnd_rtree_insert(Data->subc_tree, yysubc, (rnd_rtree_box_t *)yysubc);
}

static pcb_layer_t *subc_silk_layer(pcb_subc_t *subc)
{
	pcb_layer_type_t side = yysubc_bottom ? PCB_LYT_BOTTOM : PCB_LYT_TOP;
	const char *name = yysubc_bottom ? "bottom-silk" : "top-silk";
	return pcb_subc_get_layer(subc, PCB_LYT_SILK | side, /*PCB_LYC_AUTO*/0, rnd_true, name, rnd_false);
}

pcb_line_t *io_pcb_element_line_new(pcb_subc_t *subc, rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, rnd_coord_t Thickness)
{
	pcb_layer_t *ly = subc_silk_layer(subc);
	return pcb_line_new(ly, X1, Y1, X2, Y2, Thickness, 0, pcb_no_flags());
}

pcb_arc_t *io_pcb_element_arc_new(pcb_subc_t *subc, rnd_coord_t X, rnd_coord_t Y,
	rnd_coord_t Width, rnd_coord_t Height, rnd_angle_t angle, rnd_angle_t delta, rnd_coord_t Thickness)
{
	pcb_layer_t *ly = subc_silk_layer(subc);
	return pcb_arc_new(ly, X, Y, Width, Height, angle, delta, Thickness, 0, pcb_no_flags(), rnd_true);
}

pcb_pstk_t *io_pcb_element_pin_new(pcb_subc_t *subc, rnd_coord_t X, rnd_coord_t Y, rnd_coord_t Thickness, rnd_coord_t Clearance, rnd_coord_t Mask, rnd_coord_t DrillingHole, const char *Name, const char *Number, pcb_flag_t Flags)
{
	pcb_pstk_t *p;
	p = pcb_old_via_new(subc->data, -1, X, Y, Thickness, Clearance, Mask, DrillingHole, Name, Flags);
	if (Number != NULL)
		pcb_attribute_put(&p->Attributes, "term", Number);
	if (Name != NULL)
		pcb_attribute_put(&p->Attributes, "name", Name);

	if (yysubc_bottom)
		pcb_pstk_mirror(p, PCB_PSTK_DONT_MIRROR_COORDS, 1, 0, 0);
	return p;
}

pcb_pstk_t *io_pcb_element_pad_new(pcb_subc_t *subc, rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, rnd_coord_t Thickness, rnd_coord_t Clearance, rnd_coord_t Mask, const char *Name, const char *Number, pcb_flag_t Flags)
{
	pcb_pstk_t *p;

	p = pcb_pstk_new_compat_pad(subc->data, -1, X1, Y1, X2, Y2, Thickness, Clearance, Mask, Flags.f & PCB_FLAG_SQUARE, Flags.f & PCB_FLAG_NOPASTE, (!!(Flags.f & PCB_FLAG_ONSOLDER)) != yysubc_bottom);
	if (Number != NULL)
		pcb_attribute_put(&p->Attributes, "term", Number);
	if (Name != NULL)
		pcb_attribute_put(&p->Attributes, "name", Name);

	if (yysubc_bottom) {
		pcb_data_t *old_hack = pcb_pstk_data_hack;
		pcb_pstk_data_hack = subc->parent.data;
		pcb_pstk_mirror(p, PCB_PSTK_DONT_MIRROR_COORDS, 1, 1, 0);
		pcb_pstk_data_hack = old_hack;
	}

	return p;
}

void io_pcb_preproc_board(pcb_board_t *pcb)
{
	int n;

	/* make postproc able to detect layers with broken link */
	for(n = 0; n < PCB_MAX_LAYER; n++)
		PCB->Data->Layer[n].meta.real.grp = -1;
}

void io_pcb_postproc_board(pcb_board_t *pcb)
{
	gdl_iterator_t it;
	pcb_subc_t *sc;
	int n;

	/* remove empty layer groups */
	for(n = 0; n < pcb->LayerGroups.len; n++) {
		if (pcb->LayerGroups.grp[n].len == 0) {
			pcb_layergrp_del(pcb, n, 0, 0);
			n--;
		}
	}

	/* check if we have layers that are not present in the group string (broken input) */
	for(n = 0; n < PCB->Data->LayerN; n++) {
		if (PCB->Data->Layer[n].meta.real.grp == -1) {
			pcb_layergrp_t *grp = pcb_get_grp_new_intern(pcb, -1);

			rnd_message(RND_MSG_WARNING, "Broken input file: layer group string doesn't contain layer %ld\n(Trying to fix it by introducing a new intern copper layer)\n", n);
			if (grp != NULL) {
				rnd_layergrp_id_t gid = grp - PCB->LayerGroups.grp;
				pcb_layer_move_to_group(pcb, n, gid);
			}
			else
				rnd_message(RND_MSG_ERROR, "Failed to add a new layer group - the board in memory IS BROKEN.\n");
		}
	}

	pcb_layergrp_create_missing_substrate(pcb);

	for(n = 0; n < pcb->LayerGroups.len; n++)
		if ((pcb->LayerGroups.grp[n].ltype & PCB_LYT_COPPER) && (pcb->LayerGroups.grp[n].ltype & PCB_LYT_INTERN))
			pcb_layergrp_fix_old_outline_detect(pcb, &pcb->LayerGroups.grp[n]);

	pcb_layergrp_fix_old_outline(pcb);

	/* have to rebind all subcircuits because the layer stack was not ready
	   when they got loaded */
	subclist_foreach(&pcb->Data->subc, &it, sc)
		pcb_subc_rebind(pcb, sc);

	pcb_layer_colors_from_conf(pcb, 1);
	pcb_rat_all_anchor_guess(pcb->Data);
}

#if !defined(RND_HAS_ATEXIT)
/* makes a temporary copy of the data. This is useful for systems which
 * doesn't support calling functions on exit. We use this to save the data
 * before LEX and YACC functions are called because they are able to abort
 * the program.*/
void pcb_tmp_data_save(void)
{
	char *fn = rnd_build_fn(&PCB->hidlib, conf_core.rc.emergency_name);
	pcb_write_pcb_file(fn, rnd_true, NULL, rnd_true, rnd_false, -1, 0);
	if (TMPFilename != NULL)
		free(TMPFilename);
	TMPFilename = fn;
}

void pcb_tmp_data_remove(void)
{
	if (TMPFilename != NULL)
		unlink(TMPFilename);
}
#endif


/* Decide about the type of a footprint file:
   - it is a file element if the first non-comment is "Element(" or "Element["
   - else it's not an element.
   - if a line of a file element starts with ## and doesn't contain @, it's a tag
   - if need_tags is set, tags are saved
*/
pcb_plug_fp_map_t *io_pcb_map_footprint(pcb_plug_io_t *ctx, FILE *f, const char *fn, pcb_plug_fp_map_t *head, int need_tags)
{
	int c, comment_len;
	int first_element = 1;
	long pos = 0;
	enum {
		ST_WS,
		ST_COMMENT,
		ST_ELEMENT,
		ST_TAG
	} state = ST_WS;
	gds_t tag;

	gds_init(&tag);
	while ((c = fgetc(f)) != EOF) {
		if (pos++ > 1024) break; /* header must be in the first kilobyte */
		switch (state) {
		case ST_ELEMENT:
			if (isspace(c))
				break;
			if ((c == '(') || (c == '[')) {
				head->type = PCB_FP_FILE;
				goto out;
			}
		case ST_WS:
			if (isspace(c))
				break;
			if (c == '#') {
				comment_len = 0;
				state = ST_COMMENT;
				break;
			}
			else if ((first_element) && (c == 'E')) {
				char s[8];
				/* Element */
				fgets(s, 7, f);
				s[6] = '\0';
				if (strcmp(s, "lement") == 0) {
					state = ST_ELEMENT;
					break;
				}
			}
			first_element = 0;
			/* fall-thru for detecting @ */
		case ST_COMMENT:
			comment_len++;
			if ((c == '#') && (comment_len == 1)) {
				state = ST_TAG;
				break;
			}
			if ((c == '\r') || (c == '\n'))
				state = ST_WS;
			break;
		case ST_TAG:
			if ((c == '\r') || (c == '\n')) { /* end of a tag */
				if (need_tags && (tag.used != 0))
					vts0_append(&head->tags, (char *)pcb_fp_tag(tag.array, 1));

				tag.used = 0;
				state = ST_WS;
				break;
			}
			gds_append(&tag, c);
		}
	}

out:;
	gds_uninit(&tag);
	return head;
}

/* old outline rules from geda/pcb */
#define LAYER_IS_OUTLINE(idx) ((pcb->Data->Layer[idx].name != NULL) && ((strcmp(pcb->Data->Layer[idx].name, "route") == 0 || rnd_strcasecmp(pcb->Data->Layer[(idx)].name, "outline") == 0)))

static int flush_item(const char *s, const char *start, rnd_layer_id_t *lids, int *lids_len, pcb_layer_type_t *loc)
{
	char *end;
	rnd_layer_id_t lid;
	switch (*start) {
		case 'c': case 'C': case 't': case 'T': *loc = PCB_LYT_TOP; break;
		case 's': case 'S': case 'b': case 'B': *loc = PCB_LYT_BOTTOM; break;
		default:
			lid = strtol(start, &end, 10)-1;
			if (end != s)
				return -1;
			if ((*lids_len) >= PCB_MAX_LAYER)
				return -1;
			lids[*lids_len] = lid;
			(*lids_len)++;
	}
	return 0;
}

int pcb_layer_parse_group_string(pcb_board_t *pcb, const char *grp_str, int LayerN, int oldfmt)
{
	const char *s, *start;
	rnd_layer_id_t lids[PCB_MAX_LAYER];
	int lids_len = 0;
	pcb_layer_type_t loc = PCB_LYT_INTERN;
	pcb_layergrp_t *g;
	int n;
	pcb_layer_stack_t *LayerGroup = &pcb->LayerGroups;

	pcb_layergrp_inhibit_inc();

	/* clear struct */
	pcb_layer_group_setup_default(pcb);

	for(start = s = grp_str; ; s++) {
		switch(*s) {
			case ',':
				if (flush_item(s, start, lids, &lids_len, &loc) != 0)
					goto error;
				start = s+1;
				break;
			case '\0':
			case ':':
				if (flush_item(s, start, lids, &lids_len, &loc) != 0)
					goto error;
				/* finalize group */
				if (loc & PCB_LYT_INTERN) {
					g = pcb_get_grp_new_intern(pcb, -1);
					if (g == NULL) {
						rnd_message(RND_MSG_ERROR, "pcb_layer_parse_group_string(): unable to insert layer groups for copper\n");
						goto error;
					}
				}
				else {
					g = pcb_get_grp(LayerGroup, loc, PCB_LYT_COPPER);
					if (g == NULL) {
						rnd_message(RND_MSG_ERROR, "pcb_layer_parse_group_string(): unable to insert layer groups for copper\n");
						goto error;
					}
				}

				for(n = 0; n < lids_len; n++) {
					if (lids[n] < 0)
						continue;
					if (LAYER_IS_OUTLINE(lids[n])) {
						if (g->ltype & PCB_LYT_INTERN) {
							pcb_layergrp_fix_turn_to_outline(g);
							pcb->Data->Layer[lids[n]].comb |= PCB_LYC_AUTO;
						}
						else
							rnd_message(RND_MSG_ERROR, "outline layer can not be on the solder or component side - converting it into a copper layer\n");
					}
					if (g == NULL) {
						rnd_message(RND_MSG_ERROR, "pcb_layer_parse_group_string(): unable to find copper group for creating the copper layer\n");
						goto error;
					}
					pcb_layer_add_in_group_(pcb, g, g - LayerGroup->grp, lids[n]);
				}

				lids_len = 0;

				/* prepare for next iteration */
				loc = PCB_LYT_INTERN;
				start = s+1;
				break;
		}
		if (*s == '\0')
			break;
	}

	pcb_layergrp_fix_old_outline(pcb);

	/* set the two silks */
	g = pcb_get_grp(LayerGroup, PCB_LYT_BOTTOM, PCB_LYT_SILK);
	if (g == NULL) {
		rnd_message(RND_MSG_ERROR, "pcb_layer_parse_group_string(): unable to find bottom silk layer group\n");
		goto error;
	}
	pcb_layer_add_in_group_(pcb, g, g - LayerGroup->grp, LayerN-2);


	g = pcb_get_grp(LayerGroup, PCB_LYT_TOP, PCB_LYT_SILK);
	if (g == NULL) {
		rnd_message(RND_MSG_ERROR, "pcb_layer_parse_group_string(): unable to find top silk layer group\n");
		goto error;
	}
	pcb_layer_add_in_group_(pcb, g, g - LayerGroup->grp, LayerN-1);

	pcb_layergrp_inhibit_dec();
	return 0;

	/* reset structure on error */
error:
	pcb_layergrp_inhibit_dec();
	memset(LayerGroup, 0, sizeof(pcb_layer_stack_t));
	return 1;
}
