/*
    easyeda file format parser - low level, sch-specific
    Copyright (C) 2023,2024 Tibor 'Igor2' Palinkas

    (Supported by NLnet NGI0 Entrust Fund in 2024)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/* These functions parse the easyeda-specific packed string fields into
   DoM subtrees so the high level parser won't need to deal with string
   parsing. Works on the sch file exported from easyeda std (the pro version
   uses a different format) */

#include "read_low_std.h"

#include <assert.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/error.h>

#include <libnanojson/nanojson.h>
#include <libnanojson/semantic.h>

#include <rnd_inclib/lib_easyeda/gendom.c>
#include <rnd_inclib/lib_easyeda/gendom_json.c>
#include <rnd_inclib/lib_easyeda/easyeda_low.c>

#define SEP3 "#@$"

#include "io_easyeda_conf.h"
extern conf_io_easyeda_t io_easyeda_conf;

/*** helpers ***/
static void fixup_poly_coords(gdom_node_t *parent)
{
	gdom_node_t *coords, *old_coords;
	static const str_tab_t ctab[] = {
		{easy_crd, GDOM_DOUBLE},
		{-1}
	};

	/* parse coords */
	old_coords = gdom_hash_get(parent, easy_coords);
	if (old_coords != NULL) {
		coords = gdom_alloc(easy_coords, GDOM_ARRAY);
		parse_str_by_tab(old_coords->value.str, coords, ctab, ' ');
		replace_node(old_coords, coords);
	}
}

/*** shapes ***/

static int parse_shape_any(gdom_node_t **shape);

/* Process wires:
    W~80 -65 80 -245 150 -245 150 -350~#008800~1~0~none~gge232~0
    W~x1 -y1 x2 -y2  x3  -y3  x4  -y4 ~stroke ~stroke-width ~stroke-style? ~fill ~ID ~locked?
*/
static int parse_shape_wire(char *str, gdom_node_t **shape)
{
	gdom_node_t *wire;
	static const str_tab_t fields[] = {
		{easy_coords, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{-1}
	};

	wire = gdom_alloc(easy_wire, GDOM_HASH);
	parse_str_by_tab(str, wire, fields, '~');

	fixup_poly_coords(wire);

	replace_node(*shape, wire);

	return 0;
}

/* Process polylines:
    PL~1059.63 -50.75 1059.63 -30.75~#880000~1~0~none~gge139~0
    PG~1335 106 1345 100 1335 94~#880000~1~0~#880000~gge56049~0
    W~x1 -y1 x2 -y2  x3  -y3  x4  -y4 ~stroke ~stroke-width ~stroke-style? ~fill ~ID ~locked?
*/
static int parse_shape_polyline_(char *str, gdom_node_t **shape, easy_keys_t name)
{
	gdom_node_t *polyline;
	static const str_tab_t fields[] = {
		{easy_coords, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	polyline = gdom_alloc(name, GDOM_HASH);
	parse_str_by_tab(str, polyline, fields, '~');

	fixup_poly_coords(polyline);

	replace_node(*shape, polyline);

	return 0;
}

static int parse_shape_polyline(char *str, gdom_node_t **shape)
{
	return parse_shape_polyline_(str, shape, easy_polyline);
}


static int parse_shape_polygon(char *str, gdom_node_t **shape)
{
	return parse_shape_polyline_(str, shape, easy_polygon);
}

static int parse_shape_bus(char *str, gdom_node_t **shape)
{
	return parse_shape_polyline_(str, shape, easy_bus);
}

/* bus entry: BE~0~660~150~670~140~gge15 */
static int parse_shape_busentry(char *str, gdom_node_t **shape)
{
	gdom_node_t *busentry;
	static const str_tab_t fields[] = {
		{easy_rot, GDOM_LONG},
		{easy_x1, GDOM_DOUBLE},
		{easy_y1, GDOM_DOUBLE},
		{easy_x2, GDOM_DOUBLE},
		{easy_y2, GDOM_DOUBLE},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	busentry = gdom_alloc(easy_busentry, GDOM_HASH);
	parse_str_by_tab(str, busentry, fields, '~');

	replace_node(*shape, busentry);

	return 0;
}


/* Process paths: PT~M 206 -796 L 206 -806...~#880000~1~0~none~gge10~0~frame_tick */
static int parse_shape_path(char *str, gdom_node_t **shape)
{
	gdom_node_t *path;
	static const str_tab_t fields[] = {
		{easy_path, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	path = gdom_alloc(easy_path, GDOM_HASH);
	parse_str_by_tab(str, path, fields, '~');

	replace_node(*shape, path);

	return 0;
}

/* Process arc: A~M380.41,-245.31 A170,75 0 1 1 484.18,-373.48~~#000000~1~0~none~gge236~0 */
static int parse_shape_arc(char *str, gdom_node_t **shape)
{
	gdom_node_t *arc;
	static const str_tab_t fields[] = {
		{easy_path, GDOM_STRING},
		{easy_helper_dots, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};


	arc = gdom_alloc(easy_arc, GDOM_HASH);
	parse_str_by_tab(str, arc, fields, '~');

	replace_node(*shape, arc);

	return 0;
}

/* Process pie: PI~M 970 40 L 1189.9 34.4509 A 220 180 0 0 1 923.103 215.863 Z~970,40,1190,220,1327.7106323242188,30.973068237304688,923.1032104492188,215.86282348632812~#FF0000~3~0~#CCCCCC~gge22 */
static int parse_shape_pie(char *str, gdom_node_t **shape)
{
	gdom_node_t *pie;
	static const str_tab_t fields[] = {
		{easy_path, GDOM_STRING},
		{easy_helper_dots, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	pie = gdom_alloc(easy_pie, GDOM_HASH);
	parse_str_by_tab(str, pie, fields, '~');

	replace_node(*shape, pie);

	return 0;
}

/* Process arrowhead: AR~part_arrowhead~1060~120~gge23~180~M 1060 120 L 1063 126 L 1055 120 L 1063 114 Z~#FF0000 */
static int parse_shape_arrowhead(char *str, gdom_node_t **shape)
{
	gdom_node_t *arrowhead;
	static const str_tab_t fields[] = {
		{easy_part_type, GDOM_STRING},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_id, GDOM_STRING},
		{easy_rot, GDOM_LONG},
		{easy_path, GDOM_STRING},
		{easy_fill_color, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	arrowhead = gdom_alloc(easy_arrowhead, GDOM_HASH);
	parse_str_by_tab(str, arrowhead, fields, '~');

	replace_node(*shape, arrowhead);

	return 0;
}



/* Process rectangles: R~694.99995~-90~~~444~80~#880000~1~0~none~gge127~0~frame_hitarea */
static int parse_shape_rect(char *str, gdom_node_t **shape)
{
	gdom_node_t *rect;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rx, GDOM_DOUBLE},
		{easy_ry, GDOM_DOUBLE},
		{easy_width, GDOM_DOUBLE},
		{easy_height, GDOM_DOUBLE},
		{easy_coords, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	rect = gdom_alloc(easy_rect, GDOM_HASH);
	parse_str_by_tab(str, rect, fields, '~');

	replace_node(*shape, rect);

	return 0;
}


/* Process line: L~360~160~510~160~#FF0000~2~0~none~gge11 */
static int parse_shape_line(char *str, gdom_node_t **shape)
{
	gdom_node_t *line;
	static const str_tab_t fields[] = {
		{easy_x1, GDOM_DOUBLE},
		{easy_y1, GDOM_DOUBLE},
		{easy_x2, GDOM_DOUBLE},
		{easy_y2, GDOM_DOUBLE},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	line = gdom_alloc(easy_line, GDOM_HASH);
	parse_str_by_tab(str, line, fields, '~');

	replace_node(*shape, line);

	return 0;
}

/* Process circle: C~710~170~105~#FF0000~2~0~#0000FF~gge12 */
static int parse_shape_circle(char *str, gdom_node_t **shape)
{
	gdom_node_t *circle;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_r, GDOM_DOUBLE},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	circle = gdom_alloc(easy_circle, GDOM_HASH);
	parse_str_by_tab(str, circle, fields, '~');

	replace_node(*shape, circle);

	return 0;
}


/* Process ellipse: E~420~-367.5~140~47.5~#000000~1~0~none~gge242~0 */
static int parse_shape_ellipse(char *str, gdom_node_t **shape)
{
	gdom_node_t *ellipse;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rx, GDOM_DOUBLE},
		{easy_ry, GDOM_DOUBLE},
		{easy_stroke_color, GDOM_STRING},
		{easy_stroke_width, GDOM_DOUBLE},
		{easy_stroke_style, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	ellipse = gdom_alloc(easy_ellipse, GDOM_HASH);
	parse_str_by_tab(str, ellipse, fields, '~');

	replace_node(*shape, ellipse);

	return 0;
}

/* Process text objects: T~L~892~-1.5~0~#880000~~~~~~comment~5~1~start~gge115~0~frame_tick */
static int parse_shape_text(char *str, gdom_node_t **shape)
{
	gdom_node_t *text;
	static const str_tab_t fields[] = {
		{easy_mark, GDOM_STRING},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_font_family, GDOM_STRING},
		{easy_font_size, GDOM_STRING},
		{easy_font_weight, GDOM_STRING},
		{easy_font_style, GDOM_STRING},
		{easy_dominant_baseline, GDOM_STRING},
		{easy_text_type, GDOM_STRING},
		{easy_text, GDOM_STRING},
		{easy_visible, GDOM_LONG},
		{easy_text_anchor, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	text = gdom_alloc(easy_text, GDOM_HASH);
	parse_str_by_tab(str, text, fields, '~');

	replace_node(*shape, text);

	return 0;
}

static void not_locked(gdom_node_t *subtree)
{
	gdom_node_t *locked = gdom_alloc(easy_locked, GDOM_LONG);
	locked->value.lng = 0;

	assert(subtree->type == GDOM_HASH);

	gdom_append(subtree, locked);
}

/* Process Pimage objects: Pimage~L~1~gge217~0~gge229~696~-40.5~102~20~data:image/svg+xml;base64,PD94b...g== */
static int parse_shape_pimage(char *str, gdom_node_t **shape)
{
	gdom_node_t *pimage;
	static const str_tab_t fields[] = {
		{easy_mark, GDOM_STRING},
		{easy_visible, GDOM_LONG},
		{easy_id, GDOM_STRING},
		{easy_visible2, GDOM_LONG},
		{easy_id2, GDOM_STRING},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_width, GDOM_DOUBLE},
		{easy_height, GDOM_DOUBLE},
		{easy_data, GDOM_STRING},
		{-1}
	};

	pimage = gdom_alloc(easy_pimage, GDOM_HASH);
	parse_str_by_tab(str, pimage, fields, '~');

	not_locked(pimage);

	replace_node(*shape, pimage);

	return 0;
}

/* Process image objects: "I~1350~-25~794~1123~0~data:image/svg+xml;base64,PD9...Pgo=~gge117920~0~" */
static int parse_shape_image(char *str, gdom_node_t **shape)
{
	gdom_node_t *image;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_width, GDOM_DOUBLE},
		{easy_height, GDOM_DOUBLE},
		{easy_rot, GDOM_DOUBLE},
		{easy_data, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	image = gdom_alloc(easy_image, GDOM_HASH);
	parse_str_by_tab(str, image, fields, '~');

	replace_node(*shape, image);

	return 0;
}

/* Process noconn: O~210~-245~gge325~M 206 -249 L 214 -241 M 214 -249 L 206 -241~#33cc33~0 */
static int parse_shape_noconn(char *str, gdom_node_t **shape)
{
	gdom_node_t *noconn;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_id, GDOM_STRING},
		{easy_path, GDOM_STRING},
		{easy_stroke_color, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	noconn = gdom_alloc(easy_noconn, GDOM_HASH);
	parse_str_by_tab(str, noconn, fields, '~');

	replace_node(*shape, noconn);

	return 0;
}

/* Process junction: J~150~-245~2.5~#CC0000~gge304~0 */
static int parse_shape_junction(char *str, gdom_node_t **shape)
{
	gdom_node_t *junction;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_r, GDOM_DOUBLE},
		{easy_fill_color, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	junction = gdom_alloc(easy_junction, GDOM_HASH);
	parse_str_by_tab(str, junction, fields, '~');

	replace_node(*shape, junction);

	return 0;
}


/* Process netlabel: N~123~456~0~#FF0000~VCC~gge13~start~131~466~Times New Roman~ */
static int parse_shape_netlabel(char *str, gdom_node_t **shape)
{
	gdom_node_t *netlabel;
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_fill_color, GDOM_STRING},
		{easy_text, GDOM_STRING},
		{easy_id, GDOM_STRING},
		{easy_text_anchor, GDOM_STRING},
		{easy_text_x, GDOM_DOUBLE},
		{easy_text_y, GDOM_DOUBLE},
		{easy_font_family, GDOM_STRING},
		{easy_font_size, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	netlabel = gdom_alloc(easy_netlabel, GDOM_HASH);
	parse_str_by_tab(str, netlabel, fields, '~');

	replace_node(*shape, netlabel);

	return 0;
}

/*** net flags ***/

/* generic/config fields: "part_netLabel_VCC~150~-350~0~gge236~~0" */
static int parse_shape_netflag_0_cfg(char *str, gdom_node_t *nf)
{
	static const str_tab_t fields[] = {
		{easy_part_id, GDOM_STRING},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	return parse_str_by_tab(str, nf, fields, '~');
}

/* dot fields: "150~-350" */
static int parse_shape_netflag_1_dot(char *str, gdom_node_t *nf)
{
	static const str_tab_t fields[] = {
		{easy_dot_x, GDOM_DOUBLE},
		{easy_dot_y, GDOM_DOUBLE},
		{-1}
	};

	return parse_str_by_tab(str, nf, fields, '~');
}


/* mark string fields: "VCC~#000000~138~-362~0~start~1~Times New Roman~9pt~flag_gge13" */
static int parse_shape_netflag_2_mark(char *str, gdom_node_t *nf)
{
	gdom_node_t *label;
	static const str_tab_t fields[] = {
		{easy_text, GDOM_STRING},
		{easy_color, GDOM_STRING},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_text_anchor, GDOM_STRING},
		{easy_visible, GDOM_LONG},
		{easy_font_family, GDOM_STRING},
		{easy_font_size, GDOM_STRING},
		{-1}
	};

	label = gdom_alloc(easy_label, GDOM_HASH);
	gdom_hash_put(nf, label);

	not_locked(label);

	return parse_str_by_tab(str, label, fields, '~');
}

static int parse_shape_netflag_shape(char *str, gdom_node_t *shapes)
{
	gdom_node_t *tmp = gdom_alloc(easy_shape, GDOM_STRING);
	tmp->value.str = rnd_strdup(str);
	if (gdom_array_append(shapes, tmp) != 0)
		return -1;

	return parse_shape_any(&tmp);
}

/* Process pins: G~ cfg ^^ dot ^^ mark ^^ shapes */
static int parse_shape_netflag(char *str, gdom_node_t **nf_orig)
{
	gdom_node_t *nf, *shp = NULL;
	char *s, *next;
	int n, res = 0;

	nf = gdom_alloc(easy_netflag, GDOM_HASH);
	for(n = 0, s = str; s != NULL; n++, s = next) {
		next = strstr(s, "^^");
		if (next != NULL) {
			*next = '\0';
			next += 2;
		}
		switch(n) {
			case 0: res |= parse_shape_netflag_0_cfg(s, nf); break;
			case 1: res |= parse_shape_netflag_1_dot(s, nf); break;
			case 2: res |= parse_shape_netflag_2_mark(s, nf); break;
			default:
				if (shp == NULL) {
					shp = gdom_alloc(easy_shapes, GDOM_ARRAY);
					gdom_hash_put(nf, shp);
				}
				res |= parse_shape_netflag_shape(s, shp);
				break;
		}
	}

	replace_node(*nf_orig, nf);

	return res;
}



/*** pin ***/

/* generic/config fields: "show~0~1~670~30~~gge23" */
static int parse_shape_pin_0_cfg(char *str, gdom_node_t *pin)
{
	static const str_tab_t fields[] = {
		{easy_show, GDOM_STRING},             /* shoow or empty */
		{easy_electric, GDOM_STRING},         /* 0, "Undefined", "Input", "Output", "I/O" or "Power" */
		{easy_spice_pinno, GDOM_LONG},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_id, GDOM_STRING},
		{easy_locked, GDOM_LONG},
		{-1}
	};

	return parse_str_by_tab(str, pin, fields, '~');
}

/* dot at the end of the pin: "670~30" */
static int parse_shape_pin_1_dot(char *str, gdom_node_t *pin)
{
	static const str_tab_t fields[] = {
		{easy_dot_x, GDOM_DOUBLE},
		{easy_dot_y, GDOM_DOUBLE},
		{-1}
	};

	return parse_str_by_tab(str, pin, fields, '~');
}

/* M 670 30 h -20~#880000 */
static int parse_shape_pin_2_path(char *str, gdom_node_t *pin)
{
	static const str_tab_t fields[] = {
		{easy_pin_path, GDOM_STRING},
		{easy_pin_path_color, GDOM_STRING},
		{-1}
	};

	return parse_str_by_tab(str, pin, fields, '~');
}

/* 1~648~33~0~1~end~~11pt */
static int parse_shape_pin_34(char *str, gdom_node_t *pin, long type)
{
	gdom_node_t *subtree;
	static const str_tab_t fields[] = {
		{easy_visible, GDOM_LONG},
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_rot, GDOM_LONG},
		{easy_text, GDOM_STRING},
		{easy_text_anchor, GDOM_STRING},
		{easy_font_family, GDOM_STRING},
		{easy_font_size, GDOM_STRING},
		{-1}
	};

	subtree = gdom_alloc(type, GDOM_HASH);
	gdom_hash_put(pin, subtree);
	not_locked(subtree);
	return parse_str_by_tab(str, subtree, fields, '~');
}

/* 1~648~33~0~1~end~~11pt */
static int parse_shape_pin_3_name(char *str, gdom_node_t *pin)
{
	return parse_shape_pin_34(str, pin, easy_name);
}

/* 1~655~29~0~1~start~~11pt */
static int parse_shape_pin_4_num(char *str, gdom_node_t *pin)
{
	return parse_shape_pin_34(str, pin, easy_num);
}

/* 0~653~30 */
static int parse_shape_pin_5_not(char *str, gdom_node_t *pin)
{
	static const str_tab_t fields[] = {
		{easy_not_visible, GDOM_LONG},
		{easy_not_x, GDOM_DOUBLE},
		{easy_not_y, GDOM_DOUBLE},
		{-1}
	};

	return parse_str_by_tab(str, pin, fields, '~');
}

/* 0~M 650 27 L 647 30 L 650 33 */
static int parse_shape_pin_6_clk(char *str, gdom_node_t *pin)
{
	static const str_tab_t fields[] = {
		{easy_clk_visible, GDOM_LONG},
		{easy_clk_path, GDOM_STRING},
		{easy_not_y, GDOM_LONG},
		{-1}
	};

	return parse_str_by_tab(str, pin, fields, '~');
}


/* Process pins: P~ cfg ^^ dot ^^ path ^^ name ^^ num ^^ not ^^ clk */
static int parse_shape_pin(char *str, gdom_node_t **pin_orig)
{
	gdom_node_t *pin;
	char *s, *next;
	int n, res = 0;

	pin = gdom_alloc(easy_pin, GDOM_HASH);
	for(n = 0, s = str; s != NULL; n++, s = next) {
		next = strstr(s, "^^");
		if (next != NULL) {
			*next = '\0';
			next += 2;
		}
		switch(n) {
			case 0: res |= parse_shape_pin_0_cfg(s, pin); break;
			case 1: res |= parse_shape_pin_1_dot(s, pin); break;
			case 2: res |= parse_shape_pin_2_path(s, pin); break;
			case 3: res |= parse_shape_pin_3_name(s, pin); break;
			case 4: res |= parse_shape_pin_4_num(s, pin); break;
			case 5: res |= parse_shape_pin_5_not(s, pin); break;
			case 6: res |= parse_shape_pin_6_clk(s, pin); break;
		}
	}

	replace_node(*pin_orig, pin);

	return res;
}

/*** symbol (LIB) ***/

/* mark string fields: "0~-806~package`NONE`Manufacturer Part`?`spicePre`.`~~0~frame_lib_1~~~0~72e832730d5a64f9~yes~yes~~~" */
static int parse_shape_lib_0_cfg(char *str, gdom_node_t *sym)
{
	static const str_tab_t fields[] = {
		{easy_x, GDOM_DOUBLE},
		{easy_y, GDOM_DOUBLE},
		{easy_attributes, GDOM_STRING},
		{easy_rot, GDOM_LONG},
		{easy_import, GDOM_LONG},
		{easy_id, GDOM_STRING},
		{easy_id2, GDOM_STRING}, /* the spec doesn't have id2 and id3 ... */
		{easy_id3, GDOM_STRING}, /* ... but editorVersion==6.5.37 writes these */
		{easy_locked, GDOM_LONG},
		{-1}
	};

	return parse_str_by_tab(str, sym, fields, '~');
}

static int parse_shape_lib_shape(char *str, gdom_node_t *shapes)
{
	gdom_node_t *tmp = gdom_alloc(easy_shape, GDOM_STRING);
	tmp->value.str = rnd_strdup(str);
	if (gdom_array_append(shapes, tmp) != 0)
		return -1;

	return parse_shape_any(&tmp);
}

/* Process LIBs: LIB~ cfg SEP3 shape SEP3 shape ... SEP3 shape */
static int parse_shape_lib(char *str, gdom_node_t **lib)
{
	gdom_node_t *sym = NULL, *shapes = NULL;
	char *s, *next;
	int n, res = 0;

	for(n = 0, s = str; s != NULL; n++, s = next) {
		next = strstr(s, SEP3);
		if (next != NULL) {
			*next = '\0';
			next += 3;
		}
		if (n == 0) {
			sym = gdom_alloc(easy_symbol, GDOM_HASH);
			shapes = gdom_alloc(easy_shapes, GDOM_ARRAY);
			if (gdom_hash_put(sym, shapes) != 0)
				res = -1;
			parse_shape_lib_0_cfg(s, sym);
		}
		else
			res |= parse_shape_lib_shape(s, shapes);
	}

	if (sym == NULL)
		return res;

	replace_node(*lib, sym);

	return res;
}

/* CA~1000~1000~#FFFFFF~yes~#CCCCCC~5~1000~1000~line~5~pixel~5~0~0 */
static int parse_canvas(gdom_node_t **canvas_orig)
{
	gdom_node_t *canvas;
	static const str_tab_t fields[] = {
		{easy_viewbox_width, GDOM_DOUBLE},
		{easy_viewbox_height, GDOM_DOUBLE},
		{easy_bg_color, GDOM_STRING},
		{easy_visible, GDOM_STRING},
		{easy_grid_color, GDOM_STRING},
		{easy_grid_size, GDOM_DOUBLE},
		{easy_canvas_width, GDOM_DOUBLE},
		{easy_canvas_height, GDOM_DOUBLE},
		{easy_grid_style, GDOM_STRING},
		{easy_snap_size, GDOM_DOUBLE},
		{easy_grid_unit, GDOM_STRING},
		{easy_snap_size_alt, GDOM_DOUBLE},
		{easy_origin_x, GDOM_DOUBLE},
		{easy_origin_y, GDOM_DOUBLE},
		{-1}
	};

	canvas = gdom_alloc(easy_canvas, GDOM_HASH);
	parse_str_by_tab((*canvas_orig)->value.str+3, canvas, fields, '~');

	replace_node(*canvas_orig, canvas);

	return 0;
}


/*** entry ***/

/* Dispatch shape by content, replace shape node with parsed one in the tree */
static int parse_shape_any(gdom_node_t **shape)
{
	char *str;

	if ((*shape)->type != GDOM_STRING)
		return -1;

	str = (*shape)->value.str;
	if (str[0] == '\0')
		return -1;

	if (str[1] == '~') {
		if (str[0] == 'W') return parse_shape_wire(str+2, shape);
		if (str[0] == 'R') return parse_shape_rect(str+2, shape);
		if (str[0] == 'P') return parse_shape_pin(str+2, shape);
		if (str[0] == 'T') return parse_shape_text(str+2, shape);
		if (str[0] == 'E') return parse_shape_ellipse(str+2, shape);
		if (str[0] == 'A') return parse_shape_arc(str+2, shape);
		if (str[0] == 'N') return parse_shape_netlabel(str+2, shape);
		if (str[0] == 'F') return parse_shape_netflag(str+2, shape);
		if (str[0] == 'O') return parse_shape_noconn(str+2, shape);
		if (str[0] == 'J') return parse_shape_junction(str+2, shape);
		if (str[0] == 'I') return parse_shape_image(str+2, shape);
		if (str[0] == 'B') return parse_shape_bus(str+2, shape);
		if (str[0] == 'L') return parse_shape_line(str+2, shape);
		if (str[0] == 'C') return parse_shape_circle(str+2, shape);
	}
	else {
		if (strncmp(str, "PL~", 3) == 0) return parse_shape_polyline(str+3, shape);
		if (strncmp(str, "PG~", 3) == 0) return parse_shape_polygon(str+3, shape);
		if (strncmp(str, "PT~", 3) == 0) return parse_shape_path(str+3, shape);
		if (strncmp(str, "PI~", 3) == 0) return parse_shape_pie(str+3, shape);
		if (strncmp(str, "BE~", 3) == 0) return parse_shape_busentry(str+3, shape);
		if (strncmp(str, "AR~", 3) == 0) return parse_shape_arrowhead(str+3, shape);
		if (strncmp(str, "LIB~", 4) == 0) return parse_shape_lib(str+4, shape);
		if (strncmp(str, "Pimage~", 7) == 0) return parse_shape_pimage(str+7, shape);
	}

	return -1;
}

static void parse_sch_shape_canvas(gdom_node_t *data_str)
{
	gdom_node_t *shape, *canvas;
	long n;

	shape = gdom_hash_get(data_str, easy_shape);
	if ((shape != NULL) && (shape->type == GDOM_ARRAY))
		for(n = 0; n < shape->value.array.used; n++)
			parse_shape_any(&shape->value.array.child[n]);

	canvas = gdom_hash_get(data_str, easy_canvas);
	if ((canvas != NULL) && (canvas->type == GDOM_STRING))
		parse_canvas(&canvas);
}

/* Replace string encoded nodes with dom subtrees */
static void parse_sch_strs(gdom_node_t *sch)
{
	gdom_node_t *data_str;

	data_str = gdom_hash_get(sch, easy_dataStr);
	if (data_str == NULL)
		return;

	parse_sch_shape_canvas(data_str);
}

/* Returns 1 if an unresolved node name in easyeda_gdom_str2name() is okay:
   some subtrees will have custom node names (e.g. attribute key=val pairs) */
RND_INLINE int easyeda_gdom_subtree_has_custom_keys(gdom_node_t *parent)
{
	if (parent == NULL)
		return 0;

	switch(parent->name) {
		case easy_components:
			return 1;
	}

	if ((parent->parent == NULL) || (parent->parent->parent == NULL))
		return 0;

	switch(parent->parent->parent->name) {
		case easy_BOM:
			return 1;
	}

	return 0;
}

/* uctx is ignored and parent may be NULL */
long easyeda_gdom_str2name(void *uctx, gdom_node_t *parent, const char *str)
{
	long res = easy_sphash(str);
	if (res < 0) {
		if (easyeda_gdom_subtree_has_custom_keys(parent))
			return GDOM_CUSTOM_NAME;
		rnd_message(RND_MSG_ERROR, "Internal error: missing easyeda keyword '%s'\n", str);
	}
	return res;
}

gdom_node_t *easystd_low_sch_parse(FILE *f, int is_sym)
{
	gdom_node_t *root, *schs;

	/* low level json parse -> initial dom */
	root = gdom_json_parse(f, easyeda_gdom_str2name);
	if (root == NULL)
		return NULL;

	/* parse strings into dom subtrees */
	if (!is_sym) {
		schs = gdom_hash_get(root, easy_schematics);
		if ((schs != NULL) && (schs->type == GDOM_ARRAY)) {
			long n;
			for(n = 0; n < schs->value.array.used; n++)
				parse_sch_strs(schs->value.array.child[n]);
		}
	}
	else
		parse_sch_shape_canvas(root);

	return root;
}



static const char *name2str(long name)
{
	return easy_keyname(name);
}

void easyeda_dump_tree(FILE *f, gdom_node_t *tree)
{
	if (tree != NULL)
		gdom_dump(f, tree, 0, name2str);
	else
		fprintf(f, "<NULL tree>\n");
}

gdom_node_t *easystd_low_parse(FILE *f, int is_sym)
{
	gdom_node_t *tree = easystd_low_sch_parse(f, is_sym);

	if (io_easyeda_conf.plugins.io_easyeda.debug.dump_dom)
		easyeda_dump_tree(stdout, tree);

	return tree;
}
