/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  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., 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
 */


#ifndef CSCH_CONCRETE_GRP_CHILD_H
#define CSCH_CONCRETE_GRP_CHILD_H
#include <libcschem/concrete.h>
#include <libcschem/oidpath.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/rotate.h>

#include <librnd/core/math_helper.h>
#include <librnd/core/misc_util.h>
#include <gengeo2d/xform.h>

/* child entry transformation of a group ref */
typedef struct csch_child_xform_s {
	csch_oidpath_t path;
	double rot;
	csch_coord_t movex, movey;
	unsigned remove:1;
	unsigned mirx:1;
	unsigned miry:1;

	unsigned cache:1;   /* if 1, this entry is only a copy cached from some sub-ref and shall not be saved */
	unsigned removed:1; /* if 1, this entry got removed (free slot) */
} csch_child_xform_t;


/* For internal use: undoable modification of a child xform entry */
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);



RND_INLINE csch_child_xform_t *csch_grp_ref_get_child_xform(csch_cgrp_t *grp, long idx)
{
	if (grp->hdr.type != CSCH_CTYPE_GRP_REF) return NULL;
	if ((idx < 0) || (idx >= grp->data.ref.child_xform.used)) return NULL;
	return grp->data.ref.child_xform.array[idx];
}

RND_INLINE g2d_xform_t csch_grp_ref_parent_mx(csch_chdr_t *obj, csch_child_xform_t **cx_out)
{
	g2d_xform_t mx;
	csch_child_xform_t *cx;
	static csch_child_xform_t zero_cx = {0};

	if (obj->grp_ref_xform.g == NULL) {
		if (cx_out != NULL) *cx_out = &zero_cx;
		return obj->parent->xform.mx;
	}

	cx = csch_grp_ref_get_child_xform(obj->grp_ref_xform.g, obj->grp_ref_xform.idx);
	if (cx_out != NULL) *cx_out = cx;

	if (cx == NULL) return obj->parent->xform.mx;

	mx = obj->parent->xform.mx;

	if ((cx->movex != 0) || (cx->movey != 0))
		mx = g2d_xform_translate(mx, cx->movex, cx->movey);
	if (cx->mirx)
		mx = g2d_xform_mirrory(mx);
	if (cx->miry)
		mx = g2d_xform_mirrorx(mx);
	if (cx->rot != 0)
		mx = g2d_xform_rotate(mx, -cx->rot / RND_RAD_TO_DEG);

	/* ->remove is not yet implemented because it may be confusing */

	return mx;
}

RND_INLINE int csch_grp_ref_child_xform_is_empty(csch_child_xform_t *cx)
{
	if (cx->movex != 0) return 0;
	if (cx->movey != 0) return 0;
	if (cx->rot != 0) return 0;
	if (cx->mirx != 0) return 0;
	if (cx->miry != 0) return 0;
	if (cx->remove != 0) return 0;
	return 1;
}


/* find the topmost group reference above obj (or return NULL if no group ref
   is found in the ancestry) */
RND_INLINE csch_cgrp_t *csch_grp_ref_get_top(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	csch_cgrp_t *g, *last = NULL;

	g = (csch_cgrp_t *)obj;
	if ((g == &sheet->direct) || (g == &sheet->indirect) || (obj->parent == NULL))
		return NULL;

	for(g = obj->parent; (g != &sheet->direct) && (g != &sheet->indirect); g = g->hdr.parent)
		if (g->hdr.type == CSCH_CTYPE_GRP_REF)
			last = g;

	return last;
}


RND_INLINE csch_child_xform_t *csch_grp_ref_child_get_cx(csch_cgrp_t *grpref, csch_chdr_t *obj, long *idx_out)
{
	csch_oidpath_t path = {0};
	csch_cgrp_t *g;
	long n, v;
	csch_child_xform_t *cx;

	csch_vtoid_append(&path.vt, obj->oid);

	for(g = obj->parent; g != grpref; g = g->hdr.parent) {
		assert(g != NULL); /* happens if grpgref is not an ancestor of obj - caller messed up */
		csch_vtoid_append(&path.vt, g->hdr.oid);
	}


	/* need to reverse the list so it goes from the group to the object */
	for(n = 0, v = path.vt.used/2; n < v; n++)
		rnd_swap(csch_oid_t, path.vt.array[n], path.vt.array[path.vt.used-n-1]);

	/* look for an existing entry by path; assume there are only a few, linear
	   search is okay */
	for(n = 0; n < grpref->data.ref.child_xform.used; n++) {
		csch_child_xform_t *cx = grpref->data.ref.child_xform.array[n];
		if (csch_oidpath_eq(&path, &cx->path)) {
			csch_vtoid_uninit(&path.vt);
			*idx_out = n;
			return cx;
		}
	}

	/* not found, need to alloc a new item */
	cx = calloc(sizeof(csch_child_xform_t), 1);
	cx->path.vt = path.vt;
	*idx_out = grpref->data.ref.child_xform.used;
	vtp0_append(&grpref->data.ref.child_xform, cx);
	obj->grp_ref_xform.g = grpref;
	obj->grp_ref_xform.idx = *idx_out;

	return cx;
}

#define CSCH_PREPARE_GRPREF \
	csch_cgrp_t *grpref = obj->grp_ref_xform.g; \
	long idx = obj->grp_ref_xform.idx; \
	csch_child_xform_t *cx; \
	if (grpref == NULL) { \
		grpref = csch_grp_ref_get_top(sheet, obj); \
		idx = -1; \
	} \
	if (grpref == NULL) \
		return 0; \
	if ((idx >= 0) && (idx < grpref->data.ref.child_xform.used)) \
		cx = grpref->data.ref.child_xform.array[idx]; \
	else \
		cx = csch_grp_ref_child_get_cx(grpref, obj, &idx); \
	assert(cx != NULL);

/* returns 1 if child is part of a group ref (directly or indirectly)  */
RND_INLINE int csch_obj_is_grp_ref_child(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	CSCH_PREPARE_GRPREF;
	return grpref != NULL;
}

/* returns 0 if obj is not affected and normal move should be performed */
RND_INLINE int csch_grp_ref_child_moved(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	int zero = 0;
	CSCH_PREPARE_GRPREF;

	csch_child_xform_modify(sheet, grpref, idx, obj,
		NULL, &dx, &dy, NULL, NULL, NULL, &zero, 1, 1);

	csch_cobj_update(sheet, obj, 1);

	return 1;
}

/* returns 0 if obj is not affected and normal move should be performed */
RND_INLINE int csch_grp_ref_child_rotated(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable)
{
	int zero = 0;
	csch_coord_t x, y;
	double rot, rad;
	CSCH_PREPARE_GRPREF;

	rot = cx->rot + da;
	rad = -rot / RND_RAD_TO_DEG;
	x = cx->movex;
	y = cx->movey;
	csch_rotate_pt(&x, &y, rcx, rcy, cos(rad), sin(rad));

	csch_child_xform_modify(sheet, grpref, idx, obj,
		&rot, &x, &y, NULL, NULL, NULL, &zero, 1, 0);

	csch_cobj_update(sheet, obj, 1);

	return 1;
}

/* returns 0 if obj is not affected and normal move should be performed */
RND_INLINE int csch_grp_ref_child_rotated90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int steps, int undoable)
{
	int zero = 0;
	csch_coord_t x, y;
	double rot;
	CSCH_PREPARE_GRPREF;

	rot = fmod(cx->rot + (double)steps * 90.0, 360.0);
	x = cx->movex;
	y = cx->movey;
	csch_rotate90_pt(&x, &y, rcx, rcy, steps);

	csch_child_xform_modify(sheet, grpref, idx, obj,
		&rot, &x, &y, NULL, NULL, NULL, &zero, 1, 0);

	csch_cobj_update(sheet, obj, 1);

	return 1;
}

/* returns 0 if obj is not affected and normal move should be performed */
RND_INLINE int csch_grp_ref_child_mirrored(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable)
{
	int zero = 0;
	csch_coord_t x, y;
	CSCH_PREPARE_GRPREF;

	x = cx->movex;
	y = cx->movey;
	csch_mirror_pt(&x, &y, mcx, mcy, mirx, miry);

	csch_child_xform_modify(sheet, grpref, idx, obj,
		NULL, &x, &y, NULL, &mirx, &miry, &zero, 1, 0);

	csch_cobj_update(sheet, obj, 1);

	return 1;
}

#undef CSCH_PREPARE_GRPREF

#endif
