/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018..2020,2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser 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
 *
 *  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
 */

/* Included from cnc_grp.c; contains grp_ref specific functions only */

csch_cgrp_t *csch_cgrp_ref_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_cgrp_t *grp;

	grp = htip_get(&parent->id2obj, oid);
	if (grp != NULL)
		return NULL;
	grp = calloc(sizeof(csch_cgrp_t), 1);
	minuid_gen(&csch_minuid, grp->uuid);

	csch_cobj_init(&grp->hdr, sheet, parent, oid, CSCH_CTYPE_GRP_REF);
	csch_attrib_init(&grp->attr);
	htip_init(&grp->id2obj, longhash, longkeyeq);
	htsp_init(&grp->name2pen, strhash, strkeyeq);
	csch_cgrp_xform_update(sheet, grp);
	return grp;
}


void csch_cgrp_ref_free(csch_cgrp_t *grpref)
{
	long n;

	assert(grpref->hdr.type == CSCH_CTYPE_GRP_REF);

	csch_cgrp_uninit(grpref);

	for(n = 0; n < grpref->data.ref.child_xform.used; n++) {
		csch_child_xform_t *cx = grpref->data.ref.child_xform.array[n];
		if (cx != NULL) {
			csch_oidpath_free(&cx->path);
			free(cx);
		}
	}
	vtp0_uninit(&grpref->data.ref.child_xform);

	free(grpref);
}

csch_cgrp_t *csch_cgrp_refget(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_cgrp_t *grp = htip_get(&parent->id2obj, oid);

	if ((grp != NULL) && (grp->hdr.type != CSCH_CTYPE_GRP))
		return NULL;
	return grp;
}

/* convert all connections back to oidpath format because the object is
   free'd but will be re-created later */
static void csch_cgrp_ref_clean_conns(csch_sheet_t *sheet, csch_chdr_t *o)
{
	long n;

	for(n = o->conn.used-1; n >= 0; n--) /* go backward because csch_conn_ptr2text() removes from this list */
		if (o->conn.array[n] != NULL)
			csch_conn_ptr2text(sheet, o->conn.array[n]);

	if (csch_obj_is_grp(o)) {
		htip_entry_t *e;
		csch_cgrp_t *g = (csch_cgrp_t *)o;

		for(e = htip_first(&g->id2obj); e != NULL; e = htip_next(&g->id2obj, e))
			csch_cgrp_ref_clean_conns(sheet, e->value);
	}

}

void csch_cgrp_ref_clean(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	htip_entry_t *e;

	assert(grpref->hdr.type == CSCH_CTYPE_GRP_REF);

	for(e = htip_first(&grpref->id2obj); e != NULL; e = htip_next(&grpref->id2obj, e)) {
		csch_chdr_t *o = e->value;
		csch_cgrp_ref_clean_conns(sheet, o);
		csch_cobj_free(o);
	}
}


void csch_cgrp_ref_update(csch_sheet_t *sheet, csch_cgrp_t *grpref, int do_xform)
{
	csch_cgrp_update(sheet, grpref, do_xform);
}

csch_attrib_t *csch_cgrp_ref_get_attr(csch_cgrp_t *grpref, const char *key)
{
	csch_attrib_t *instance, *refd = NULL;

	instance = htsp_get(&grpref->attr, key);

	assert(grpref->data.ref.grp != NULL); /* indicates a bug; stop in debug mode */
	if (grpref->data.ref.grp != NULL)
		refd = htsp_get(&grpref->data.ref.grp->attr, key);

	if (refd == NULL)       return instance;
	if (instance == NULL)   return refd;

	return (instance->prio <= refd->prio) ? instance : refd;
}

int csch_cgrp_ref_text2ptr(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	csch_oidpath_t ref_oidp = {0};

	csch_vtoid_init(&ref_oidp.vt);
	grpref->data.ref.grp = NULL;
	if (csch_oidpath_parse(&ref_oidp, grpref->data.ref.ref_str) != 0) {
		rnd_msg_error("Invalid oid path in group ref: '%s' (syntax)\n", grpref->data.ref.ref_str);
		return -1;
	}

	/* resolve the target group */
	grpref->data.ref.grp = (csch_cgrp_t *)csch_oidpath_resolve(sheet, &ref_oidp);
	csch_oidpath_free(&ref_oidp);
	if (grpref->data.ref.grp == NULL) {
		rnd_msg_error("Invalid oid path in group ref: '%s' (does not resolve)\n", grpref->data.ref.ref_str);
		return -1;
	}
	if (grpref->data.ref.grp->hdr.type != CSCH_CTYPE_GRP) {
		grpref->data.ref.grp = NULL;
		rnd_msg_error("Invalid oid path in group ref: '%s' (refered object is not a group)\n", grpref->data.ref.ref_str);
		return -1;
	}

	/* xorcache: free text version */
	free(grpref->data.ref.ref_str);
	grpref->data.ref.ref_str = NULL;

	return 0;
}

int csch_cgrp_ref_ptr2text(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	csch_oidpath_t oidp = {0};

	if (grpref->data.ref.grp == NULL)
		return -1;

	csch_oidpath_from_obj(&oidp, &grpref->data.ref.grp->hdr);
	grpref->data.ref.ref_str = csch_oidpath_to_str(&oidp);
	csch_oidpath_free(&oidp);

	if (grpref->data.ref.ref_str == NULL)
		return -1;

	grpref->data.ref.grp = NULL;
	return 0;
}


RND_INLINE void csch_cgrp_childr_xform_apply(csch_sheet_t *sheet, csch_cgrp_t *grpref, csch_child_xform_t *cx, csch_chdr_t *child, long cx_idx)
{
	child->grp_ref_xform.g = grpref;
	child->grp_ref_xform.idx = cx_idx;
}

RND_INLINE void csch_cgrp_children_xform_apply(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	long n;
	for(n = 0; n < grpref->data.ref.child_xform.used; n++) {
		csch_child_xform_t *cx = grpref->data.ref.child_xform.array[n];
		if ((cx != NULL) && !cx->removed) {
			csch_chdr_t *child = csch_oidpath_resolve_in(grpref, &cx->path);
			if ((child == NULL) || (child == &grpref->hdr)) {
				/* referenced child not found */
				if (!cx->remove)
					rnd_message(RND_MSG_ERROR, "csch_cgrp_children_xform_apply(): child not found\n");
			}
			else {
				/* referenced child found */
				if (cx->remove)
					csch_cobj_free(child);
				else
					csch_cgrp_childr_xform_apply(sheet, grpref, cx, child, n);
			}
		}
	}
}

TODO(
	"TODO#38: cache sub-ref-child-xforms in top level group_ref;"
	"for now just disallow and assume the editor will do it on top and/or"
	"there will be only loclib kind of single-level group refs")
static void csch_cgrp_ref_cache_deep_child_xforms(csch_sheet_t *sheet, csch_cgrp_t *top, csch_cgrp_t *from)
{
	htip_entry_t *e;
	for(e = htip_first(&from->id2obj); e != NULL; e = htip_next(&from->id2obj, e)) {
		csch_cgrp_t *grp = e->value;
		if (!csch_obj_is_grp(&grp->hdr)) continue;
		if ((grp->data.ref.child_xform.used > 0) && (from != top)) {
			rnd_message(RND_MSG_ERROR, "csch_cgrp_ref_cache_deep_child_xforms(): grpref in grpref with child transformations is not yet supported\n");
		}
	}
}

/* Remove each entry marked as cache */
void csch_cgrp_ref_clean_child_xform_cache(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	long n;

	for(n = 0; n < grpref->data.ref.child_xform.used; n++) {
		csch_child_xform_t *cx = grpref->data.ref.child_xform.array[n];
		if (cx->cache) {
			csch_chdr_t *child = csch_oidpath_resolve_in(grpref, &cx->path);
			if ((child != NULL) && (child != &grpref->hdr)) {
				child->grp_ref_xform.g = NULL;
				child->grp_ref_xform.idx = -1;
			}
			cx->cache = 0;
			cx->removed = 1;
		}
	}
}


void csch_cgrp_ref_render(csch_sheet_t *sheet, csch_cgrp_t *grpref)
{
	csch_attrib_t *arole;
	assert(grpref->hdr.type == CSCH_CTYPE_GRP_REF);

	/* update the referenced grp cache from the string version, if needed */
	if (grpref->data.ref.grp == NULL)
		if (csch_cgrp_ref_text2ptr(sheet, grpref) != 0)
			return;

	/* render: copy and transform all objects from the target group */
	csch_cgrp_ref_clean(sheet, grpref);
	csch_cgrp_dup_into(sheet, grpref, grpref->data.ref.grp, 0);

	arole = csch_cgrp_ref_get_attr(grpref, "role");
	if (arole != NULL)
		csch_role_set(grpref, arole->val);
	csch_cgrp_role_update(sheet, grpref);

	csch_cgrp_ref_clean_child_xform_cache(sheet, grpref);
	csch_cgrp_ref_cache_deep_child_xforms(sheet, grpref, grpref);

	csch_cgrp_children_xform_apply(sheet, grpref);

	csch_cgrp_ref_update(sheet, grpref, 1);
}

static void grp_ref_child_embed(csch_chdr_t *obj, csch_cgrp_t *egrp)
{
	csch_sheet_t *sheet = obj->sheet;
	if (obj->grp_ref_xform.g == egrp) {
		obj->grp_ref_xform.g = NULL;

		TODO("loclib: need to apply cx transformations on the object as if it was its own");
#if 0
		csch_child_xform_t *cx = csch_grp_ref_get_child_xform(obj->grp_ref_xform.g, obj->grp_ref_xform.idx);
		egrp->hdr.type = CSCH_CTYPE_GRP; /* need to fake this temporarily so that the transformation is not captured and made to the grp ref we are trying to remove */
		csch_rotate(...)
		egrp->hdr.type = CSCH_CTYPE_GRP_REF;
#endif
	}

	/* recurse */
	if (csch_obj_is_grp(obj)) {
		csch_cgrp_t *g = (csch_cgrp_t *)obj;
		htip_entry_t *e;

		for(e = htip_first(&g->id2obj); e != NULL; e = htip_next(&g->id2obj, e))
			grp_ref_child_embed(e->value, egrp);
	}

	csch_sheet_set_changed(sheet, 1);
}

void csch_grp_ref_embed(csch_cgrp_t *grpref)
{
	csch_oid_t nid = 0;
	htip_entry_t *e;

	assert(grpref->hdr.type == CSCH_CTYPE_GRP_REF);

	/* copy attributes that were originally inherited */
	{
		htsp_entry_t *e;
		csch_cgrp_t *referee = grpref->data.ref.grp;

		for(e = htsp_first(&referee->attr); e; e = htsp_next(&referee->attr, e)) {
			csch_attrib_t *asrc = e->value;
			csch_attrib_t *adst = csch_attrib_get(&grpref->attr, e->key);
			if (adst == NULL) {
				csch_attrib_t *anew = csch_attrib_dup(asrc);
				htsp_set(&grpref->attr, anew->key, anew);
			}
		}
	}

	/* find the largest ID */
	for(e = htip_first(&grpref->id2obj); e != NULL; e = htip_next(&grpref->id2obj, e))
		if (e->key > nid)
			nid = e->key;

	/* remove group ref transformation */
	grp_ref_child_embed(&grpref->hdr, grpref);

	grpref->hdr.type = CSCH_CTYPE_GRP;
	grpref->data.grp.next_id = nid+1;
	csch_cgrp_update(grpref->hdr.sheet, grpref, 1);
}


/*** Undoable child xform edit ***/
typedef struct {
	csch_sheet_t *sheet;
	csch_cgrp_t *grpref;
	long idx;
	csch_chdr_t *obj;
	csch_child_xform_t cx;
} undo_child_xform_modify_t;


static int undo_child_xform_modify_swap(void *udata)
{
	undo_child_xform_modify_t *u = udata;
	csch_child_xform_t *cx = u->grpref->data.ref.child_xform.array[u->idx];

	rnd_swap(double, cx->rot, u->cx.rot);
	rnd_swap(csch_coord_t, cx->movex, u->cx.movex);
	rnd_swap(csch_coord_t, cx->movey, u->cx.movey);
	rnd_swap(int, cx->mirx, u->cx.mirx);
	rnd_swap(int, cx->miry, u->cx.miry);
	rnd_swap(int, cx->remove, u->cx.remove);
	rnd_swap(int, cx->cache, u->cx.cache);
	rnd_swap(int, cx->removed, u->cx.removed);

	csch_cobj_update(u->sheet, u->obj, 1);

	return 0;
}

static void undo_child_xform_modify_print(void *udata, char *dst, size_t dst_len)
{
	undo_child_xform_modify_t *u = udata;
	csch_child_xform_t *cx = u->grpref->data.ref.child_xform.array[u->idx];

	rnd_snprintf(dst, dst_len, "group ref child xform move=%d;%d<->%d;%d rot=%.02f<->%.02f mir=%d;%d<->%d;%d remove=%d<->%d cache=%d<->%d removed=%d<->%d",
		u->cx.movex, u->cx.movey, cx->movex, cx->movey, u->cx.rot, cx->rot, 
		u->cx.mirx, cx->mirx, u->cx.miry, cx->miry,
		u->cx.remove, cx->remove, u->cx.cache, cx->cache, u->cx.removed, cx->removed);
}

static const char core_child_xform_cookie[] = "libcschem/core/cnc_text.c";

static const uundo_oper_t undo_child_xform_modify = {
	core_child_xform_cookie,
	NULL,
	undo_child_xform_modify_swap,
	undo_child_xform_modify_swap,
	undo_child_xform_modify_print
};


void csch_child_xform_modify(csch_sheet_t *sheet, csch_cgrp_t *grpref, long idx, csch_chdr_t *obj,
	double *rot, csch_coord_t *movex, csch_coord_t *movey, int *remove,
	int *mirx, int *miry, int *cache,    int undoable, int relative)
{
	undo_child_xform_modify_t utmp, *u = &utmp;
	csch_child_xform_t *cx;

	if (undoable) u = uundo_append(&sheet->undo, &undo_child_xform_modify, sizeof(undo_child_xform_modify_t));

	cx = grpref->data.ref.child_xform.array[idx];

	u->sheet = sheet;
	u->grpref = grpref;
	u->idx = idx;
	u->obj = obj;

	u->cx.movex = (movex == NULL) ? cx->movex : (relative ? (cx->movex + *movex) : *movex);
	u->cx.movey = (movey == NULL) ? cx->movey : (relative ? (cx->movey + *movey) : *movey);
	u->cx.rot = CSCH_UNDO_RMODIFY(cx, rot);
	u->cx.mirx = CSCH_UNDO_MODIFY(cx, mirx);
	u->cx.miry = CSCH_UNDO_MODIFY(cx, miry);
	u->cx.remove = CSCH_UNDO_MODIFY(cx, remove);
	u->cx.cache = CSCH_UNDO_MODIFY(cx, cache);
	u->cx.removed = cx->removed;

	undo_child_xform_modify_swap(u);
	if (undoable) csch_undo_inc_serial(sheet);
}

