/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - property editor plugin
 *  Copyright (C) 2022, 2023 Tibor 'Igor2' Palinkas
 *  (Adapted from propsel code in pcb-rnd by the same author)
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022 and 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 <libcschem/config.h>

#include <ctype.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/core/error.h>
#include <librnd/core/math_helper.h>
#include <genregex/regex_se.h>

#include <libcschem/concrete.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_arc.h>
#include <libcschem/cnc_bitmap.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_grp_child.h>
#include <libcschem/cnc_obj.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/undo.h>

#include <sch-rnd/operation.h>
#include <sch-rnd/sheet.h>

#include "props.h"
#include "propsel.h"

/*********** map ***********/
#define type2field_String string
#define type2field_csch_coord_t coord
#define type2field_g2d_angle_t angle
#define type2field_double d
#define type2field_int i
#define type2field_bool i
#define type2field_comms comms
#define type2field_pen comms
#define type2field_enum i
#define type2field_Attrarr attrarr
#define type2field_uuid u


#define type2TYPE_String CSCH_PROPT_STRING
#define type2TYPE_csch_coord_t CSCH_PROPT_COORD
#define type2TYPE_g2d_angle_t CSCH_PROPT_ANGLE
#define type2TYPE_double CSCH_PROPT_DOUBLE
#define type2TYPE_int CSCH_PROPT_INT
#define type2TYPE_bool CSCH_PROPT_BOOL
#define type2TYPE_comms CSCH_PROPT_COMMS
#define type2TYPE_enum CSCH_PROPT_ENUM
#define type2TYPE_pen CSCH_PROPT_COMMS
#define type2TYPE_Attrarr CSCH_PROPT_ATTRARR
#define type2TYPE_uuid CSCH_PROPT_UUID


#define map_add_prop_exec(ctx, name, type, val, exec) \
do { \
	csch_propval_t v; \
	csch_props_t *prp; \
	const char *stype = #type; \
	v.type2field_ ## type = (val);  \
	prp = csch_props_add(ctx, name, type2TYPE_ ## type, v); \
	if ((stype[0] == 'p') && (stype[1] == 'e') && (stype[2] == 'n')) \
		prp->comms_type = CSCH_PROPCT_PEN; \
	exec; \
} while(0)

#define map_add_prop_exec_memcpy(ctx, name, type, val, exec) \
do { \
	csch_propval_t v; \
	csch_props_t *prp; \
	const char *stype = #type; \
	memcpy(v.type2field_ ## type, (val), sizeof(v.type2field_ ## type)); \
	prp = csch_props_add(ctx, name, type2TYPE_ ## type, v); \
	(void)stype; \
	(void)prp; \
	exec; \
} while(0)

#define map_add_prop(ctx, name, type, val) map_add_prop_exec(ctx, name, type, val, ;)
#define map_add_prop_memcpy(ctx, name, type, val) map_add_prop_exec_memcpy(ctx, name, type, val, ;)

static void map_attr(void *ctx, htsp_t *attrs)
{
	int bl = 0;
	char small[256];
	char *big = NULL;
	htsp_entry_t *e;

	small[0] = 'a';
	small[1] = '/';

	for(e = htsp_first(attrs); e != NULL; e = htsp_next(attrs, e)) {
		int len = strlen(e->key);
		char *nm;
		const char *valstr;
		csch_attrib_t *av = e->value;

		if (len >= sizeof(small)-3) {
			if (len > bl) {
				bl = len + 128;
				if (big != NULL)
					free(big);
				big = malloc(bl);
				big[0] = 'a';
				big[1] = '/';
			}
			nm = big;
		}
		else
			nm = small;
		strcpy(nm+2, e->key);

		if (av->val != NULL) {
			valstr = av->val;
			map_add_prop(ctx, nm, String, valstr);
		}
		else
			map_add_prop(ctx, nm, Attrarr, 0);

	}
	if (big != NULL)
		free(big);
}

static void map_grp(csch_propedit_t *ctx, csch_cgrp_t *grp, int nogeo);

static void map_sheet(csch_propedit_t *ctx, csch_sheet_t *sheet)
{
	map_add_prop(ctx, "p/sheet/loadname", String, sheet->hidlib.loadname);
	map_add_prop(ctx, "p/sheet/filename", String, sheet->hidlib.fullpath);
	map_add_prop(ctx, "p/sheet/loose_sym", bool, sheet->loose_sym);
	map_attr(ctx, &sheet->direct.attr);
	if (sheet->is_symbol)
		map_grp(ctx, &sheet->direct, 1);
}

/* Common properties as per {des3:81} and some extras */
static void map_common_properites(csch_propedit_t *ctx, csch_chdr_t *obj)
{
	map_add_prop(ctx, "p/lock", bool, obj->lock);
	map_add_prop(ctx, "p/floater", bool, obj->floater);

	/* extras */
	if (ctx->geo) {
		map_add_prop(ctx, "p/bbox/x1", csch_coord_t, obj->bbox.x1);
		map_add_prop(ctx, "p/bbox/y1", csch_coord_t, obj->bbox.y1);
		map_add_prop(ctx, "p/bbox/x2", csch_coord_t, obj->bbox.x2);
		map_add_prop(ctx, "p/bbox/y2", csch_coord_t, obj->bbox.y2);
		map_add_prop(ctx, "p/bbox/cx", csch_coord_t, rnd_round((obj->bbox.x1 + obj->bbox.x2)/2.0));
		map_add_prop(ctx, "p/bbox/cy", csch_coord_t, rnd_round((obj->bbox.y1 + obj->bbox.y2)/2.0));
	}
}

static void map_line(csch_propedit_t *ctx, csch_line_t *line)
{
	map_common_properites(ctx, &line->hdr);
	map_add_prop(ctx, "p/stroke", pen, line->hdr.stroke_name);
	if (ctx->geo) {
		map_add_prop(ctx, "p/line/x1", csch_coord_t, line->spec.p1.x);
		map_add_prop(ctx, "p/line/y1", csch_coord_t, line->spec.p1.y);
		map_add_prop(ctx, "p/line/x2", csch_coord_t, line->spec.p2.x);
		map_add_prop(ctx, "p/line/y2", csch_coord_t, line->spec.p2.y);
	}
}

static void map_arc(csch_propedit_t *ctx, csch_arc_t *arc)
{
	map_common_properites(ctx, &arc->hdr);
	map_add_prop(ctx, "p/stroke", pen, arc->hdr.stroke_name);
	map_add_prop(ctx, "p/arc/r", csch_coord_t, arc->spec.r);
	map_add_prop(ctx, "p/arc/start", double, arc->spec.start * RND_RAD_TO_DEG);
	map_add_prop(ctx, "p/arc/delta", double, arc->spec.delta * RND_RAD_TO_DEG);
	if (ctx->geo) {
		map_add_prop(ctx, "p/arc/cx", csch_coord_t, arc->spec.c.x);
		map_add_prop(ctx, "p/arc/cy", csch_coord_t, arc->spec.c.y);
	}
}

static void map_bitmap(csch_propedit_t *ctx, csch_cbitmap_t *bitmap)
{
	map_common_properites(ctx, &bitmap->hdr);
	TODO("bitmap: implement");
}

static void map_text(csch_propedit_t *ctx, csch_text_t *text)
{
	map_common_properites(ctx, &text->hdr);
	map_add_prop(ctx, "p/stroke", pen, text->hdr.stroke_name);
	map_add_prop(ctx, "p/text/rot",  double, text->spec_rot);
	map_add_prop(ctx, "p/text/mirx",  bool, text->spec_mirx);
	map_add_prop(ctx, "p/text/miry",  bool, text->spec_miry);
	map_add_prop_exec(ctx, "p/text/halign", enum, text->halign, prp->enum_names = csch_halign_names);
	map_add_prop(ctx, "p/text/text", String, text->text);
	map_add_prop(ctx, "p/text/dyntext", bool, text->dyntext);
	map_add_prop(ctx, "p/text/has_bbox", bool, text->has_bbox);
	if (ctx->geo) {
		map_add_prop(ctx, "p/text/inst/rot",  double, text->inst_rot);
		map_add_prop(ctx, "p/text/inst/mirx",  bool, text->inst_mirx);
		map_add_prop(ctx, "p/text/inst/miry",  bool, text->inst_miry);
		map_add_prop(ctx, "p/text/x1", csch_coord_t, text->spec1.x);
		map_add_prop(ctx, "p/text/y1", csch_coord_t, text->spec1.y);
		map_add_prop(ctx, "p/text/x2", csch_coord_t, text->spec2.x);
		map_add_prop(ctx, "p/text/y2", csch_coord_t, text->spec2.y);
	}
}

static void map_poly(csch_propedit_t *ctx, csch_cpoly_t *poly)
{
	map_common_properites(ctx, &poly->hdr);
	map_add_prop(ctx, "p/stroke",        pen,   poly->hdr.stroke_name);
	map_add_prop(ctx, "p/has_stroke",    bool,  poly->has_stroke);
	map_add_prop(ctx, "p/fill",          pen,   poly->hdr.fill_name);
	map_add_prop(ctx, "p/has_fill",      bool,  poly->has_fill);
}

static void map_grp(csch_propedit_t *ctx, csch_cgrp_t *grp, int nogeo)
{
	map_attr(ctx, &grp->attr);

TODO("recurse");

	map_common_properites(ctx, &grp->hdr);
	map_add_prop_memcpy(ctx, "p/grp/uuid", uuid, grp->uuid);

	if (grp->hdr.type == CSCH_CTYPE_GRP)
		map_add_prop_memcpy(ctx, "p/grp/src_uuid", uuid, grp->data.grp.src_uuid);

	if (nogeo)
		return;

	map_add_prop(ctx, "p/grp/rot", double, grp->spec_rot);
	map_add_prop(ctx, "p/grp/mirx", bool, grp->mirx);
	map_add_prop(ctx, "p/grp/miry", bool, grp->miry);
	if (ctx->geo) {
		map_add_prop(ctx, "p/grp/x", csch_coord_t, grp->x);
		map_add_prop(ctx, "p/grp/y", csch_coord_t, grp->y);
	}

}

static long csch_propsel_do(csch_sheet_t *sheet, csch_cgrp_t *grp, int data_in_grp, void *ctx, long (*cb)(void *ctx, csch_chdr_t *hdr, const void *udata), const void *udata)
{
	htip_entry_t *e;
	long res = 0;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *h = e->value;
		if (data_in_grp) {
			TODO("rethink this: propset() should work on objects under group lock; example Erich's case on removing all pin numbers require to change them to floater so they can be removed; pcb-rnd also allows propset() to bypass group lock; pool node with the script: sch-rnd remove_pinnum")
#if 0
			if (floater_or_parent_group_atomic?(h))
				continue; /* grp lock: skip any selected object within a grp */
#endif
		}

		if (csch_obj_is_grp(h)) {
			csch_cgrp_t *grp = (csch_cgrp_t *)h;
			res += csch_propsel_do(sheet, grp, 1, ctx, cb, udata); /* recurse to groups */
		}

		if (h->selected)
			res += cb(ctx, h, udata);
	}

	return res;
}

static long map_any(void *ctx_, csch_chdr_t *h, const void *udata)
{
	csch_propedit_t *ctx = ctx_;
	if (h == NULL)
		return 0;
	switch(h->type) {
		case CSCH_CTYPE_ARC:    map_arc(ctx, (csch_arc_t *)h); break;
		case CSCH_CTYPE_BITMAP: map_bitmap(ctx, (csch_cbitmap_t *)h); break;
		case CSCH_CTYPE_LINE:   map_line(ctx, (csch_line_t *)h); break;
		case CSCH_CTYPE_POLY:  map_poly(ctx, (csch_cpoly_t *)h); break;
		case CSCH_CTYPE_TEXT:  map_text(ctx, (csch_text_t *)h); break;
		case CSCH_CTYPE_GRP_REF:
		case CSCH_CTYPE_GRP:    map_grp(ctx, (csch_cgrp_t *)h, 0); break;
		default: return 0;
	}
	return 1;
}

void csch_propsel_map_core(csch_propedit_t *ctx)
{
	csch_oidpath_t *idp;

	for(idp = csch_oidpath_list_first(&ctx->objs); idp != NULL; idp = csch_oidpath_list_next(idp))
		map_any(ctx, csch_oidpath_resolve(ctx->sheet, idp), NULL);

	if (ctx->use_selection)
		csch_propsel_do(ctx->sheet, ctx->grp, 0, ctx, map_any, NULL);

	if (ctx->use_sheet)
		map_sheet(ctx, ctx->sheet);
}

/*******************/


static csch_source_arg_t *propsel_src(void)
{
	return csch_attrib_src_c(NULL, 0, 0, "propsel user input");
}

static void toggle_attr(csch_propset_ctx_t *st, csch_cgrp_t *grp)
{
	const char *newval, *key = st->name+2;
	const char *orig = csch_attrib_get_str(&grp->attr, key);

	if (orig == NULL) {
		if (st->toggle_create) {
			newval = "true";
			goto do_set;
		}
		/* else do not create non-existing attributes */
		return;
	}
	if (rnd_istrue(orig)) {
		newval = "false";
		goto do_set;
	}
	else if (rnd_isfalse(orig)) {
		newval = "true";
		goto do_set;
	}
	return;

	do_set:;
	csch_attr_modify_str(st->sheet, grp, CSCH_ATP_USER_DEFAULT, key, newval, propsel_src(), 1);
	st->set_cnt++;
}

static void set_attr_obj(csch_propset_ctx_t *st, csch_chdr_t *hdr)
{
	const char *key = st->name+2;
	const char *orig;
	csch_attrib_t *orig_a;
	csch_cgrp_t *grp = (csch_cgrp_t *)hdr;

	assert(csch_obj_is_grp(hdr));

	if (st->toggle) {
		toggle_attr(st, grp);
		return;
	}

	orig_a = csch_attrib_get(&grp->attr, key);
	if ((orig_a == NULL) || orig_a->deleted)
		orig = NULL;
	else
		orig = orig_a->val;

	if (st->arename) {
		if ((orig_a == NULL) || (strcmp(key, st->s) == 0))
			return;
	}
	else {
		if ((orig != NULL) && (strcmp(orig, st->s) == 0))
			return;
	}


	if (st->arename)
		csch_attr_modify_rename(st->sheet, grp, orig_a, st->s, propsel_src(), 1);
	else
		csch_attr_modify_str(st->sheet, grp, CSCH_ATP_USER_DEFAULT, key, st->s, propsel_src(), 1);
	st->set_cnt++;
}

#define DONE  { st->set_cnt++; uundo_restore_serial(&st->sheet->undo); return; }
#define DONE0 { st->set_cnt++; uundo_restore_serial(&st->sheet->undo); return 0; }
#define DONE1 { st->set_cnt++; uundo_restore_serial(&st->sheet->undo); return 1; }

static void set_grp(csch_propset_ctx_t *st, csch_cgrp_t *grp);

static void set_sheet(csch_propset_ctx_t *st, csch_sheet_t *sheet)
{
	if (st->is_attr) {
		set_attr_obj(st, &sheet->direct.hdr);
		return;
	}


	if (strncmp(st->name, "p/sheet/", 8) == 0) {
	const char *pn = st->name + 8;

		if (strcmp(pn, "loose_sym") == 0) {
			if (st->toggle)
				st->sheet->loose_sym = !st->sheet->loose_sym;
			else
				st->sheet->loose_sym = st->c;
			DONE;
		}

		if (st->toggle)
			return; /* can't toggle anything else */

		if (strcmp(pn, "loadname") == 0) {
			sch_rnd_sheet_change_loadname(&st->sheet->hidlib, st->s);
			DONE;
		}

		if ((strcmp(pn, "filename") == 0)) {
			sch_rnd_sheet_change_fullpath(&st->sheet->hidlib, st->s);
			DONE;
		}
	}

	if (sheet->is_symbol)
		set_grp(st, &sheet->direct);
}

/* Common object properties (as in {des3:81}) */
static int set_common_properties_(csch_propset_ctx_t *st, csch_chdr_t *obj)
{
	int itmp = st->c;

	if (strcmp(st->name, "p/lock") == 0) {
		csch_commprp_modify(st->sheet, obj, &itmp, 0, 1);
		return 1;
	}
	if (strcmp(st->name, "p/floater") == 0) {
		csch_commprp_modify(st->sheet, obj, 0, &itmp, 1);
		return 1;
	}

	return 0;
}

#define set_common_properties(st, obj) \
do { \
	if (set_common_properties_(st, obj)) { DONE; } \
} while(0)

static void set_line(csch_propset_ctx_t *st, csch_line_t *line)
{
	const char *pn = st->name + 7;

	if (st->toggle)
		return; /* can't toggle anything else */

	set_common_properties(st, &line->hdr);

	if (strcmp(st->name, "p/stroke") == 0) {
		csch_chdr_pen_name_modify(st->sheet, &line->hdr, &st->comms, NULL, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "x1") == 0)) {
		csch_line_modify(st->sheet, line, &st->c, NULL, NULL, NULL, 1, !st->c_absolute, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "y1") == 0)) {
		csch_line_modify(st->sheet, line, NULL, &st->c, NULL, NULL, 1, !st->c_absolute, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "x2") == 0)) {
		csch_line_modify(st->sheet, line, NULL, NULL, &st->c, NULL, 1, !st->c_absolute, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "y2") == 0)) {
		csch_line_modify(st->sheet, line, NULL, NULL, NULL, &st->c, 1, !st->c_absolute, 1);
		DONE;
	}

}

static void set_arc(csch_propset_ctx_t *st, csch_arc_t *arc)
{
	const char *pn = st->name + 6;

	if (st->toggle)
		return; /* can't toggle anything else */

	set_common_properties(st, &arc->hdr);

	if (strcmp(st->name, "p/stroke") == 0) {
		csch_chdr_pen_name_modify(st->sheet, &arc->hdr, &st->comms, NULL, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "cx") == 0)) {
		csch_arc_modify(st->sheet, arc, &st->c, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "cy") == 0)) {
		csch_arc_modify(st->sheet, arc, NULL, &st->c, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "r") == 0)) {
		csch_arc_modify(st->sheet, arc, NULL, NULL, &st->c, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}


	if (st->d_valid && (strcmp(pn, "start") == 0)) {
		double rad = st->d / RND_RAD_TO_DEG;
		csch_arc_modify(st->sheet, arc, NULL, NULL, NULL, &rad, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (st->d_valid && (strcmp(pn, "delta") == 0)) {
		double rad = st->d / RND_RAD_TO_DEG;
		csch_arc_modify(st->sheet, arc, NULL, NULL, NULL, NULL, &rad, 1, !st->d_absolute);
		DONE;
	}

}

static void set_bitmap(csch_propset_ctx_t *st, csch_cbitmap_t *bitmap)
{
	TODO("bitmap: implement");
}


static void set_text(csch_propset_ctx_t *st, csch_text_t *text)
{
	const char *pn = st->name + 7;

	if (st->toggle)
		return; /* can't toggle anything else */

	set_common_properties(st, &text->hdr);

	if (strcmp(st->name, "p/stroke") == 0) {
		csch_chdr_pen_name_modify(st->sheet, &text->hdr, &st->comms, NULL, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "x1") == 0)) {
		csch_coord_t crd = st->c;
		csch_text_modify(st->sheet, text, &crd, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "y1") == 0)) {
		csch_coord_t crd = st->c;
		csch_text_modify(st->sheet, text, NULL, &crd, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "x2") == 0)) {
		csch_coord_t crd = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, &crd, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "y2") == 0)) {
		csch_coord_t crd = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, &crd, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->d_valid && (strcmp(pn, "rot") == 0)) {
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, &st->d, NULL, NULL, NULL, NULL, NULL, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "mirx") == 0)) {
		int itmp = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, NULL, &itmp, NULL, NULL, NULL, NULL, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "miry") == 0)) {
		int itmp = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, NULL, NULL, &itmp, NULL, NULL, NULL, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (strcmp(pn, "halign") == 0) {
		csch_halign_t tmp;
		if (st->c_valid)
			tmp = st->c;
		else
			tmp = csch_str2halign(st->s);

		if ((tmp < 0) || (tmp >= CSCH_HALIGN_invalid))
			return;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &tmp, NULL, NULL, NULL, 1, 0);
		DONE;
	}

	if (strcmp(pn, "text") == 0) {
		sch_rnd_op_text_edit(st->sheet, text, st->s);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "dyntext") == 0)) {
		int tmp = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &tmp, 1, 0);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "has_bbox") == 0)) {
		int tmp = st->c;
		csch_text_modify(st->sheet, text, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &tmp, NULL, 1, 0);
		DONE;
	}
}


static void set_poly(csch_propset_ctx_t *st, csch_cpoly_t *poly)
{
	set_common_properties(st, &poly->hdr);

	if (strcmp(st->name, "p/stroke") == 0) {
		csch_chdr_pen_name_modify(st->sheet, &poly->hdr, &st->comms, NULL, 1);
		DONE;
	}

	if (strcmp(st->name, "p/fill") == 0) {
		csch_chdr_pen_name_modify(st->sheet, &poly->hdr, NULL, &st->comms, 1);
		DONE;
	}

	if (strcmp(st->name, "p/has_stroke") == 0) {
		if (st->toggle) {
			int i = !poly->has_stroke;
			csch_cpoly_modify(st->sheet, poly, &i, NULL, 1);
			DONE;
		}
		else if (st->c_valid) {
			int i = st->c;
			csch_cpoly_modify(st->sheet, poly, &i, NULL, 1);
			DONE;
		}
	}

	if (strcmp(st->name, "p/has_fill") == 0) {
		if (st->toggle) {
			int i = !poly->has_fill;
			csch_cpoly_modify(st->sheet, poly, NULL, &i, 1);
			DONE;
		}
		else if (st->c_valid ) {
			int i = st->c;
			csch_cpoly_modify(st->sheet, poly, NULL, &i, 1);
			DONE;
		}
	}

}

static void set_grp(csch_propset_ctx_t *st, csch_cgrp_t *grp)
{
	const char *pn = st->name + 6;

	if ((st->name[0] != 'a') || (st->name[1] != '/')) { /* attributes are not recursive */
TODO("set property recursively on all child using set_any()");
	}

	if (st->is_attr) {
		set_attr_obj(st, &grp->hdr);
		return;
	}

	if (st->toggle)
		return; /* can't toggle anything else */

	set_common_properties(st, &grp->hdr);

	if (strcmp(pn, "uuid") == 0) {
		csch_cgrp_modify_uuid(st->sheet, grp, &st->u, NULL, 1);
		DONE;
	}

	if ((grp->hdr.type == CSCH_CTYPE_GRP) && (strcmp(pn, "src_uuid") == 0)) {
		csch_cgrp_modify_uuid(st->sheet, grp, NULL, &st->u, 1);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "x") == 0)) {
		csch_cgrp_modify(st->sheet, grp, &st->c, NULL, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "y") == 0)) {
		csch_cgrp_modify(st->sheet, grp, NULL, &st->c, NULL, NULL, NULL, 1, !st->c_absolute);
		DONE;
	}

	if (st->d_valid && (strcmp(pn, "rot") == 0)) {
		csch_cgrp_modify(st->sheet, grp, NULL, NULL, &st->d, NULL, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "mirx") == 0)) {
		int itmp = st->c;
		csch_cgrp_modify(st->sheet, grp, NULL, NULL, NULL, &itmp, NULL, 1, !st->d_absolute);
		DONE;
	}

	if (st->c_valid && (strcmp(pn, "miry") == 0)) {
		int itmp = st->c;
		csch_cgrp_modify(st->sheet, grp, NULL, NULL, NULL, NULL, &itmp, 1, !st->d_absolute);
		DONE;
	}
}

static long set_any(void *ctx_, csch_chdr_t *h, const void *udata)
{
	csch_propset_ctx_t *ctx = ctx_;
	if (h == NULL)
		return 0;

	if (csch_grp_ref_get_top(h->sheet, h) != NULL) {
		rnd_message(RND_MSG_ERROR, "Can not change properties of a group_ref child\nbecause it would change the referenced group's children (probably in local lib)\n");
		return 0;
	}

	switch(h->type) {
		case CSCH_CTYPE_ARC:    set_arc(ctx, (csch_arc_t *)h); break;
		case CSCH_CTYPE_BITMAP: set_bitmap(ctx, (csch_cbitmap_t *)h); break;
		case CSCH_CTYPE_LINE:   set_line(ctx, (csch_line_t *)h); break;
		case CSCH_CTYPE_POLY:   set_poly(ctx, (csch_cpoly_t *)h); break;
		case CSCH_CTYPE_TEXT:   set_text(ctx, (csch_text_t *)h); break;
		case CSCH_CTYPE_GRP_REF:
		case CSCH_CTYPE_GRP:    set_grp(ctx, (csch_cgrp_t *)h); break;
		default: return 0;
	}
	return 1;
}

int csch_propsel_set(csch_propedit_t *ctx, const char *prop, csch_propset_ctx_t *sctx)
{
	csch_oidpath_t *idp;

	sctx->sheet = ctx->sheet;
	sctx->grp = ctx->grp;
	sctx->is_attr = (prop[0] == 'a');
	sctx->name = prop;

	uundo_save_serial(&ctx->sheet->undo);

	for(idp = csch_oidpath_list_first(&ctx->objs); idp != NULL; idp = csch_oidpath_list_next(idp)) {
		csch_chdr_t *obj = csch_oidpath_resolve(ctx->sheet, idp);
		assert(obj != NULL);
		set_any(sctx, obj, NULL);
	}


	if (ctx->use_selection)
		csch_propsel_do(ctx->sheet, ctx->grp, 0, sctx, set_any, NULL);

	if (ctx->use_sheet)
		set_sheet(sctx, ctx->sheet);

	csch_sheet_set_changed(ctx->sheet, 1);
	uundo_inc_serial(&ctx->sheet->undo);
	return sctx->set_cnt;
}


int csch_propsel_set_str(csch_propedit_t *ctx, const char *prop, const char *value)
{
	csch_propset_ctx_t sctx = {0};
	char *end;
	const char *start;

	/* sanity checks for invalid props */
	if ((prop[0] == 'r') && (strncmp(prop, "rename/a/", 9)) == 0) {
		sctx.arename = 1;
		prop += 7;
	}

	if ((prop[1] != '/') || ((prop[0] != 'a') && (prop[0] != 'p'))) {
		rnd_message(RND_MSG_ERROR, "Invalid property path: '%s':\n must start with p/ for property or a/ for attribute\n", prop);
		return 0;
	}

	if (value == NULL)
		value = "";
	sctx.s = value;
	start = value;
	while(isspace(*start)) start++;
	if (*start == '#') {
		sctx.d_absolute = 1;
		start++;
	}
	else
		sctx.d_absolute = ((*start != '-') && (*start != '+'));
	sctx.toggle = 0;
	sctx.c = rnd_get_value_ex(start, NULL, &sctx.c_absolute, NULL, NULL, &sctx.c_valid);
	sctx.d = strtod(start, &end);
	sctx.d_valid = (*end == '\0');
	sctx.set_cnt = 0;

	return csch_propsel_set(ctx, prop, &sctx);
}

int csch_propsel_toggle(csch_propedit_t *ctx, const char *prop, rnd_bool create)
{
	csch_propset_ctx_t sctx;

	/* sanity checks for invalid props */
	if (prop[1] != '/')
		return 0;
	if ((prop[0] != 'a') && (prop[0] != 'p'))
		return 0;

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

	sctx.toggle = 1;
	sctx.toggle_create = create;
	sctx.set_cnt = 0;

	return csch_propsel_set(ctx, prop, &sctx);
}

/*******************/

static long del_attr(csch_propedit_t *ctx, csch_chdr_t *obj, const char *key)
{
	csch_cgrp_t *grp = (csch_cgrp_t *)obj;
	if (!csch_obj_is_grp(obj))
		return 0;

	if (ctx->aregex) {
		htsp_entry_t *e;

		if (ctx->rx == NULL)
			ctx->rx = re_se_comp(key);

		restart:;
		for(e = htsp_first(&grp->attr); e != NULL; e = htsp_next(&grp->attr, e)) {
			csch_attrib_t *a = e->value;
			if (re_se_exec(ctx->rx, a->key)) {
				csch_attr_modify_del(ctx->sheet, grp, a->key, 1);
				goto restart; /* the hash table iterator breaks; we expect only a few matching attributes so it is probably cheap to restart */
			}
		}
	}
	else
		csch_attr_modify_del(ctx->sheet, grp, key, 1);
	return 1;
}

static long del_any(void *ctx, csch_chdr_t *h, const void *key)
{
	return del_attr(ctx, h, key);
}

static long del_sheet(void *ctx, csch_sheet_t *sheet, const char *key)
{
	return del_attr(ctx, &sheet->direct.hdr, key);
}

int csch_propsel_del(csch_propedit_t *ctx, const char *key)
{
	csch_oidpath_t *idp;
	long del_cnt = 0;

	if ((key[0] != 'a') || (key[1] != '/')) /* do not attempt to remove anything but attributes */
		return 0;

	key += 2;

	for(idp = csch_oidpath_list_first(&ctx->objs); idp != NULL; idp = csch_oidpath_list_next(idp))
		del_cnt += del_any(ctx, csch_oidpath_resolve(ctx->sheet, idp), key);

	if (ctx->use_selection)
		del_cnt += csch_propsel_do(ctx->sheet, ctx->grp, 0, ctx, del_any, key);

	if (ctx->use_sheet)
		del_cnt += del_sheet(ctx, ctx->sheet, key);

	csch_sheet_set_changed(ctx->sheet, 1);
	return del_cnt;
}


char *csch_propsel_printval(csch_props_t *p, const csch_propval_t *val)
{
	minuid_str_t utmp;

	switch(p->type) {
		case CSCH_PROPT_STRING: return val->string == NULL ? rnd_strdup("") : rnd_strdup(val->string);
		case CSCH_PROPT_COORD:  return rnd_strdup_printf("%$rc", val->coord);
		case CSCH_PROPT_ANGLE:  return rnd_strdup_printf("%f", val->angle);
		case CSCH_PROPT_DOUBLE: return rnd_strdup_printf("%f", val->d);
		case CSCH_PROPT_INT:    return rnd_strdup_printf("%d", val->i);
		case CSCH_PROPT_ENUM:   return rnd_strdup(p->enum_names[val->i]);
		case CSCH_PROPT_BOOL:   return rnd_strdup(val->i ? "true" : "false");
		case CSCH_PROPT_COMMS:  return val->comms.str == NULL ? rnd_strdup("") : rnd_strdup(val->comms.str);
		case CSCH_PROPT_UUID:   minuid_bin2str(utmp, val->u); return rnd_strdup(utmp);
		case CSCH_PROPT_ATTRARR:return rnd_strdup("<array>");
		default:
			return rnd_strdup("<error>");
	}
}
