/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,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
 */


#include "config.h"

#include <librnd/core/rnd_printf.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/math_helper.h>

#include "event.h"
#include "concrete.h"
#include "cnc_text.h"
#include "cnc_obj.h"
#include "cnc_grp.h"
#include "cnc_grp_child.h"
#include "op_common.h"
#include "operation.h"
#include "undo.h"
#include "rotate.h"
#include "project.h"
#include "triangle.h"

#include "gengeo2d/box.h"
#include "gengeo2d/xform.h"
#include "gengeo2d/cline.h"

const char *csch_halign_names[] = { "start", "center", "end", "word_justify", "justify", NULL };

const char *csch_halign2str(csch_halign_t halign)
{
	if ((halign >= 0) && (halign < sizeof(csch_halign_names)/sizeof(char *)))
		return csch_halign_names[halign];
	return "<invalid>";
}

csch_halign_t csch_str2halign(const char *s)
{
	switch(*s) {
		case 's': if (strcmp(s+1, "tart") == 0) return CSCH_HALIGN_START; break;
		case 'c': if (strcmp(s+1, "enter") == 0) return CSCH_HALIGN_CENTER; break;
		case 'e': if (strcmp(s+1, "nd") == 0) return CSCH_HALIGN_END; break;
		case 'w': if (strcmp(s+1, "ord_justify") == 0) return CSCH_HALIGN_WORD_JUST; break;
		case 'j': if (strcmp(s+1, "ustify") == 0) return CSCH_HALIGN_JUST; break;
	}
	return CSCH_HALIGN_invalid;
}


csch_text_t *csch_text_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_text_t *text;

	text = htip_get(&parent->id2obj, oid);
	if (text != NULL)
		return NULL;
	text = calloc(sizeof(csch_text_t), 1);

	csch_cobj_init(&text->hdr, sheet, parent, oid, CSCH_CTYPE_TEXT);
	return text;
}

/* Mirror and return rot_deg as mirx and miry directs */
RND_INLINE double text_mirror_rot(const csch_text_t *text, double rot_deg, int mirx, int miry)
{
	double rot, sx, sy;

	if ((mirx == 0) && (miry == 0))
		return rot_deg;

	rot = rot_deg / RND_RAD_TO_DEG;
	sx = cos(rot); sy = sin(rot);
	if (mirx) sx = -sx;
	if (miry) sy = -sy;
	return atan2(sy, sx) * RND_RAD_TO_DEG;
}

csch_text_t *csch_text_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_text_t *src, int keep_id, int inst2spec)
{
	csch_text_t *dst = csch_text_alloc(sheet, parent, CSCH_KEEP_OID(parent, src->hdr.oid));

	dst->has_bbox = src->has_bbox;
	dst->halign = src->halign;
	dst->text = rnd_strdup(src->text);
	dst->dyntext = src->dyntext;



	if (inst2spec) {
		g2d_xform_t mx;
		g2d_vect_t v1 = src->inst1, v2 = src->inst2;
		double ir;

		csch_cgrp_inverse_matrix(&mx, parent);

		/* undo the effect of text rotation on mirror;
		   Test the original bug: revert back to r7977, use
		   /work/manual_test/power.rs, select the right side circuit, drag&drop move
		   it one grid point to the right -> when using src->inst_rot instead of
		   ir, floater changes rotation by 180 deg */
		ir = text_mirror_rot(src, src->inst_rot, src->inst_mirx, src->inst_miry);

		dst->spec_rot = ir - parent->xform.rot;
		dst->spec_mirx = src->inst_mirx ^ parent->xform.mirx;
		dst->spec_miry = src->inst_miry ^ parent->xform.miry;

		v1 = g2d_xform_vect2vect(mx, v1);
		v2 = g2d_xform_vect2vect(mx, v2);
		dst->spec1 = v1;
		dst->spec2 = v2;
		csch_chdr_copy_meta4dup(&dst->hdr, &src->hdr); /* have to happen before update because of rtree (display layer change in this call) */
		csch_text_update(sheet, dst, 1);
	}
	else {
		dst->spec1 = src->spec1;
		dst->spec2 = src->spec2;

		dst->spec_rot = src->spec_rot;
		dst->spec_mirx = src->spec_mirx;
		dst->spec_miry = src->spec_miry;
		dst->inst_rot = src->inst_rot;
		dst->inst_raw_rot = src->inst_raw_rot;
		dst->inst_mirx = src->inst_mirx;
		dst->inst_miry = src->inst_miry;

		csch_chdr_copy_meta4dup(&dst->hdr, &src->hdr);
	}

	return dst;
}

void (*csch_cb_text_invalidate_font)(csch_sheet_t *sheet, csch_text_t *text) = NULL;
void (*csch_cb_text_calc_bbox)(csch_sheet_t *sheet, csch_text_t *text) = NULL;

void csch_text_invalidate_font(csch_text_t *text)
{
	if (csch_cb_text_invalidate_font != NULL)
		csch_cb_text_invalidate_font(text->hdr.sheet, text);
}

void csch_text_invalidate_all_grp(csch_cgrp_t *grp, int dynonly)
{
	htip_entry_t *e;
	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *obj = e->value;
		if (obj->type == CSCH_CTYPE_TEXT) {
			csch_text_t *text = (csch_text_t *)obj;
			if (!dynonly || text->dyntext) {
				if (dynonly)
					csch_text_dyntext_inval(text);
				csch_text_invalidate_font(text);
				csch_cobj_redraw(text);
			}
		}
		else if (csch_obj_is_grp(obj))
			csch_text_invalidate_all_grp((csch_cgrp_t *)obj, dynonly);
	}
}

void csch_text_invalidate_all_sheet(csch_sheet_t *sheet, int dynonly)
{
	csch_cobj_redraw_freeze(sheet);
	csch_text_invalidate_all_grp(&sheet->direct, dynonly);
	csch_cobj_redraw_unfreeze(sheet);
}

void csch_text_invalidate_all_project(csch_project_t *prj, int dynonly)
{
	long n;
	for(n = 0; n < prj->hdr.designs.used; n++)
		csch_text_invalidate_all_sheet(prj->hdr.designs.array[n], dynonly);
}



void csch_text_free(csch_text_t *text)
{
	csch_cobj_uninit(&text->hdr);
	csch_text_invalidate_font(text);
	free(text->rtext);
	free(text->text);
	free(text);
}

csch_text_t *csch_text_get(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_oid_t oid)
{
	csch_text_t *text = htip_get(&grp->id2obj, oid);
	if ((text != NULL) && (text->hdr.type != CSCH_CTYPE_TEXT))
		return NULL;
	return text;
}

void csch_text_update_xform(csch_sheet_t *sheet, csch_text_t *text)
{
	if (text->hdr.parent != NULL) {
		csch_child_xform_t *cx;
		double cxrot, irot;
		g2d_xform_t mx = csch_grp_ref_parent_mx(&text->hdr, &cx);

		text->inst1  = g2d_xform_vect2vect(mx, text->inst1);
		text->inst2  = g2d_xform_vect2vect(mx, text->inst2);
		text->inst15 = g2d_xform_vect2vect(mx, text->inst15);
		text->inst25 = g2d_xform_vect2vect(mx, text->inst25);
		text->inst_mirx = text->hdr.parent->xform.mirx ^ cx->mirx ^ text->spec_mirx;
		text->inst_miry = text->hdr.parent->xform.miry ^ cx->miry ^ text->spec_miry;


		if (text->inst_mirx ^ text->inst_miry) {
			cxrot = -cx->rot;
			irot = -text->inst_rot;
		}
		else {
			cxrot = cx->rot;
			irot = text->inst_rot;
		}

		text->inst_raw_rot = irot + text->hdr.parent->xform.rot + cxrot;
		text->inst_rot = text_mirror_rot(text, text->inst_rot + text->hdr.parent->xform.rot + cx->rot, text->inst_mirx, text->inst_miry);
	}
}

void csch_text_update_bbox(csch_sheet_t *sheet, csch_text_t *text)
{
	g2d_box_t bb = g2d_box_invalid();

	g2d_box_bump_pt(&bb, text->inst1);
	g2d_box_bump_pt(&bb, text->inst15);
	g2d_box_bump_pt(&bb, text->inst2);
	g2d_box_bump_pt(&bb, text->inst25);

	text->hdr.bbox.x1 = bb.p1.x; text->hdr.bbox.y1 = bb.p1.y;
	text->hdr.bbox.x2 = bb.p2.x; text->hdr.bbox.y2 = bb.p2.y;
}

void csch_text_update(csch_sheet_t *sheet, csch_text_t *text, int do_xform)
{
	if (!text->bbox_calcing)
		csch_cobj_rtree_del(sheet, &text->hdr);

	if (!text->bbox_calcing) {
		csch_cobj_redraw_freeze(sheet);
		csch_cobj_update_pen(&text->hdr);

		if (!text->has_bbox && !text->bbox_calced) {
			text->bbox_calcing = 1;
			if (csch_cb_text_calc_bbox != NULL)
				csch_cb_text_calc_bbox(sheet, text);
			text->bbox_calced = 1;
			text->bbox_calcing = 0;
		}
		csch_cobj_redraw_unfreeze(sheet);
	}

	if (do_xform) {
		text->spec15.x = text->spec2.x; text->spec15.y = text->spec1.y;
		text->spec25.x = text->spec1.x; text->spec25.y = text->spec2.y;

		/* initial values of instance is copy of spec... */
		text->inst1  = text->spec1;
		text->inst2  = text->spec2;
		text->inst15 = text->spec15;
		text->inst25 = text->spec25;
		text->inst_rot = text->spec_rot;
		text->inst_raw_rot = text->spec_rot;
		text->inst_mirx = text->spec_mirx;
		text->inst_miry = text->spec_miry;

		if (text->spec_mirx || text->spec_miry) {
			csch_mirror_pt(&text->inst2.x, &text->inst2.y, text->inst1.x, text->inst1.y, text->spec_mirx, text->spec_miry);
			csch_mirror_pt(&text->inst15.x, &text->inst15.y, text->inst1.x, text->inst1.y, text->spec_mirx, text->spec_miry);
			csch_mirror_pt(&text->inst25.x, &text->inst25.y, text->inst1.x, text->inst1.y, text->spec_mirx, text->spec_miry);
		}

		/* ... then add local text rotation keeping x1;y1 in place */
		if (text->inst_rot != 0) {
			double cs, sn, rot = -fmod(text->inst_rot, 360.0) / RND_RAD_TO_DEG;

			if (text->spec_mirx ^ text->spec_miry)
				rot = -rot;

			cs = cos(rot); sn = sin(rot);
			csch_rotate_pt(&text->inst2.x, &text->inst2.y, text->inst1.x, text->inst1.y, cs, sn);
			csch_rotate_pt(&text->inst15.x, &text->inst15.y, text->inst1.x, text->inst1.y, cs, sn);
			csch_rotate_pt(&text->inst25.x, &text->inst25.y, text->inst1.x, text->inst1.y, cs, sn);
		}

		/* ... and then parent transformation */
		csch_text_update_xform(sheet, text);
	}

	csch_text_update_bbox(sheet, text);

	if (!text->bbox_calcing) {
		csch_cobj_rtree_add(sheet, &text->hdr);
		csch_cobj_bbox_changed(sheet, &text->hdr);
	}
}

int csch_text_invalid_chars(csch_text_t *txt)
{
	const char *s = csch_text_get_rtext(txt);
	int ctr;

	if (s == NULL)
		return 0;

	for(ctr = 0; *s != '\0'; s++)
		if ((*s < 32) || (*s > 126))
			ctr++;

	return ctr;
}

static csch_chdr_t *text_create(csch_sheet_t *sheet, csch_cgrp_t *parent)
{
	csch_text_t *text = csch_text_alloc(sheet, parent, csch_oid_new(sheet, parent));
	if (text == NULL) return NULL;
	return &text->hdr;
}

static void text_remove_alloc(csch_undo_remove_t *slot)
{
}

static void text_remove_redo(csch_undo_remove_t *slot)
{
	csch_cnc_common_remove_redo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT);
}

static void text_remove_undo(csch_undo_remove_t *slot)
{
	csch_cnc_common_remove_undo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT);
}

static int text_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box)
{
	csch_text_t *text = (csch_text_t *)obj;
	g2d_box_t gbox;
	g2d_vect_t gbv[4];
	g2d_cline_t tedge[4];
	int n;

	/* (execute cheap tests first) */

	gbox.p1.x = box->x1; gbox.p1.y = box->y1;
	gbox.p2.x = box->x2; gbox.p2.y = box->y2;

	/* isc if any corner point of the text rectangle is within gbox */
	if (g2d_point_in_box(&gbox, text->inst1)) return 1;
	if (g2d_point_in_box(&gbox, text->inst15)) return 1;
	if (g2d_point_in_box(&gbox, text->inst2)) return 1;
	if (g2d_point_in_box(&gbox, text->inst25)) return 1;


	/* isc if any of the text rectangle edges are hit by any of the box edges */
	tedge[0].p1 = text->inst1; tedge[0].p2 = text->inst15;
	tedge[1].p1 = text->inst15; tedge[1].p2 = text->inst2;
	tedge[2].p1 = text->inst2; tedge[2].p2 = text->inst25;
	tedge[3].p1 = text->inst25; tedge[3].p2 = text->inst1;
	for(n = 0; n < 4; n++) {
		if (g2d__iscp_cline_hcline(&tedge[n], box->y1, box->x1, box->x2, NULL, NULL)) return 1;
		if (g2d__iscp_cline_hcline(&tedge[n], box->y2, box->x1, box->x2, NULL, NULL)) return 1;
		if (g2d__iscp_cline_vcline(&tedge[n], box->x1, box->y1, box->y2, NULL, NULL)) return 1;
		if (g2d__iscp_cline_vcline(&tedge[n], box->x2, box->y1, box->y2, NULL, NULL)) return 1;
	}

	/* isc if any of the corner points of gbox is within the text; do this
	   by triangles of the text for efficiency */
	gbv[0].x = box->x1; gbv[0].y = box->y1;
	gbv[1].x = box->x2; gbv[1].y = box->y1;
	gbv[2].x = box->x2; gbv[2].y = box->y2;
	gbv[3].x = box->x1; gbv[3].y = box->y2;
	if (csch_any_point_in_triangle(text->inst1, text->inst15, text->inst2, gbv, 4)) return 1;
	if (csch_any_point_in_triangle(text->inst1, text->inst25, text->inst2, gbv, 4)) return 1;

	return 0;
}


static void text_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_text_t *text = (csch_text_t *)obj;
	csch_text_modify(sheet, text, &dx, &dy, &dx, &dy, NULL, NULL, NULL, NULL, NULL, NULL, NULL, undoable, 1);
}

static void text_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_text_t *text = csch_text_dup(sheet, obj->parent, (csch_text_t *)obj, 0, 0);
	if (text != NULL) {
		text->spec1.x += dx; text->spec1.y += dy;
		text->spec2.x += dx; text->spec2.y += dy;
		csch_text_update(sheet, text, 1);
		csch_op_inserted(sheet, obj->parent, &text->hdr);
	}
}

static void text_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable)
{
	csch_text_t *text = (csch_text_t *)obj;
	double rad, rot = fmod(text->spec_rot - da, 360.0);
	csch_coord_t sx = text->spec2.x - text->spec1.x, sy = text->spec2.y - text->spec1.y;
	csch_coord_t x1 = text->spec1.x, y1 = text->spec1.y, x2, y2;

	if (text->spec_mirx ^ text->spec_miry)
		rot = -rot;

	rad = -rot / RND_RAD_TO_DEG;

	csch_rotate_pt(&x1, &y1, rcx, rcy, cos(rad), sin(rad));
	x2 = x1 + sx; y2 = y1 + sy;

	csch_text_modify(sheet, text, &x1, &y1, &x2, &y2, &rot, NULL, NULL, NULL, NULL, NULL, NULL, undoable, 0);
}

static void text_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int n, int undoable)
{
	csch_text_t *text = (csch_text_t *)obj;
	double rot = fmod(text->spec_rot + (double)n * 90.0, 360.0);
	csch_coord_t sx = text->spec2.x - text->spec1.x, sy = text->spec2.y - text->spec1.y;
	csch_coord_t x1 = text->spec1.x, y1 = text->spec1.y, x2, y2;

	if (text->spec_mirx ^ text->spec_miry)
		n = -n;

	csch_rotate90_pt(&x1, &y1, rcx, rcy, n);
	x2 = x1 + sx; y2 = y1 + sy;
	csch_text_modify(sheet, text, &x1, &y1, &x2, &y2, &rot, NULL, NULL, NULL, NULL, NULL, NULL, undoable, 0);
}

static void text_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx_in, int miry_in, int undoable)
{
	csch_text_t *text = (csch_text_t *)obj;
	int mirx = text->spec_mirx, miry = text->spec_miry;
	csch_coord_t x1 = text->spec1.x, y1 = text->spec1.y, x2 = text->spec2.x, y2 = text->spec2.y, w, h;

	if (!mirx_in && !miry_in)
		return;

	if (mirx_in)
		mirx = !mirx;

	if (miry_in)
		miry = !miry;

	w = x2 - x1; h = y2 - y1;
	csch_mirror_pt(&x1, &y1, mcx, mcy, mirx_in, miry_in);
	x2 = x1 + w; y2 = y1 + h;

	csch_text_modify(sheet, text, &x1, &y1, &x2, &y2, NULL, &mirx, &miry, NULL, NULL, NULL, NULL, undoable, 0);
}

static void text_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *in, int undoable)
{
	csch_text_t *text = (csch_text_t *)in;
	csch_coord_t x2, y2, sx, sy;
	csch_coord_t x1 = text->inst1.x, y1 = text->inst1.y;
	int mirx = text->inst_mirx, miry = text->inst_miry;
	double rot = text->inst_raw_rot;

	if (mirx ^ miry)
		rot = -rot;

	sx = text->spec2.x - text->spec1.x; if (sx < 0) sx = -sx;
	sy = text->spec2.y - text->spec1.y; if (sy < 0) sy = -sy;
	x2 = x1 + sx;
	y2 = y1 + sy;

	csch_text_modify(sheet, (csch_text_t *)obj, &x1, &y1, &x2, &y2, &rot, &mirx, &miry, NULL, NULL, NULL, NULL, undoable, 0);
}

const csch_ops_t csch_ops_text = {
	text_create, text_remove_alloc, text_remove_redo, text_remove_undo,
	text_isc_with_box,
	text_move, text_copy, text_rotate, text_rotate90, text_mirror,
	text_inst2spec
};

/*** hash ***/

unsigned csch_text_hash(const csch_text_t *text, csch_hash_ignore_t ignore)
{
	unsigned res = csch_chdr_hash(&text->hdr);

	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !text->hdr.floater) {
		res ^= csch_coord_hash(text->spec1.x) + csch_coord_hash(text->spec1.y);
		if (text->has_bbox)
			res ^= csch_coord_hash(text->spec2.x) + csch_coord_hash(text->spec2.y);
		res ^= csch_angle_hash(text->spec_rot);
		res ^= ((unsigned)text->spec_mirx) << 7;
		res ^= ((unsigned)text->spec_miry) << 8;
	}
	res ^= ((unsigned)text->has_bbox) << 3;
	res ^= ((unsigned)text->halign);
	res ^= ((unsigned)text->dyntext) << 5;
	res ^= strhash(text->text);

	return res;
}

int csch_text_keyeq(const csch_text_t *t1, const csch_text_t *t2, csch_hash_ignore_t ignore)
{
	if (!csch_chdr_eq(&t1->hdr, &t2->hdr)) return 0;

	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !t1->hdr.floater || !t2->hdr.floater) {
		if (t1->spec1.x != t2->spec1.x) return 0;
		if (t1->spec1.y != t2->spec1.y) return 0;
		if (t1->has_bbox) {
			if (t1->spec2.x != t2->spec2.x) return 0;
			if (t1->spec2.y != t2->spec2.y) return 0;
		}
		if (t1->spec_rot != t2->spec_rot) return 0;
		if (t1->spec_mirx != t2->spec_mirx) return 0;
		if (t1->spec_miry != t2->spec_miry) return 0;
	}
	if (t1->has_bbox != t2->has_bbox) return 0;
	if (t1->halign != t2->halign) return 0;
	if (t1->dyntext != t2->dyntext) return 0;
	if (strcmp(t1->text, t2->text) != 0) return 0;
	return 1;
}


/*** Modify ***/
typedef struct {
	csch_text_t *texto; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	g2d_vect_t spec1, spec2;
	double spec_rot;
	csch_halign_t halign;
	char *text;
	unsigned spec_mirx:1;
	unsigned spec_miry:1;
	unsigned has_bbox:1;
	unsigned dyntext:1;
} undo_text_modify_t;


static int undo_text_modify_swap(void *udata)
{
	undo_text_modify_t *u = udata;

	csch_cobj_redraw(u->texto);

	rnd_swap(g2d_vect_t, u->spec1, u->texto->spec1);
	rnd_swap(g2d_vect_t, u->spec2, u->texto->spec2);
	rnd_swap(double, u->spec_rot, u->texto->spec_rot);
	rnd_swap(int, u->spec_mirx, u->texto->spec_mirx);
	rnd_swap(int, u->spec_miry, u->texto->spec_miry);
	rnd_swap(csch_halign_t, u->halign, u->texto->halign);
	rnd_swap(char *, u->text, u->texto->text);
	rnd_swap(int, u->has_bbox, u->texto->has_bbox);
	rnd_swap(int, u->dyntext, u->texto->dyntext);

	csch_text_update(u->texto->hdr.sheet, u->texto, 1);
	csch_sheet_set_changed(u->texto->hdr.sheet, 1);
	csch_text_invalidate_font(u->texto);
	csch_text_dyntext_inval(u->texto);

	csch_cobj_redraw(u->texto);

	return 0;
}

static void undo_text_modify_print(void *udata, char *dst, size_t dst_len)
{
	undo_text_modify_t *u = udata;

	if (u->text == u->texto->text)
		rnd_snprintf(dst, dst_len, "text change");
	else
		rnd_snprintf(dst, dst_len, "text change (%s<->%s)", u->text, u->texto->text);
}

static void undo_text_modify_free(void *udata)
{
	undo_text_modify_t *u = udata;
	if (u->text != u->texto->text)
		free(u->text);
}

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

static const uundo_oper_t undo_text_modify = {
	core_text_cookie,
	undo_text_modify_free,
	undo_text_modify_swap,
	undo_text_modify_swap,
	undo_text_modify_print
};


void csch_text_modify(csch_sheet_t *sheet, csch_text_t *texto,
	csch_coord_t *x1, csch_coord_t *y1, csch_coord_t *x2, csch_coord_t *y2,
	double *spec_rot, int *spec_mirx, int *spec_miry, csch_halign_t *halign,
	const char **text_, int *has_bbox, int *dyntext, int undoable, int relative)
{
	undo_text_modify_t utmp, *u = &utmp;
	char **text = (char **)text_, *tmp;

	if (undoable) u = uundo_append(&sheet->undo, &undo_text_modify, sizeof(undo_text_modify_t));

	if (text != NULL) {
		tmp = rnd_strdup(*text);
		text = &tmp;
	}

	u->texto = texto;
	u->spec1.x = (x1 == NULL) ? texto->spec1.x : (relative ? (texto->spec1.x + *x1) : *x1);
	u->spec1.y = (y1 == NULL) ? texto->spec1.y : (relative ? (texto->spec1.y + *y1) : *y1);
	u->spec2.x = (x2 == NULL) ? texto->spec2.x : (relative ? (texto->spec2.x + *x2) : *x2);
	u->spec2.y = (y2 == NULL) ? texto->spec2.y : (relative ? (texto->spec2.y + *y2) : *y2);
	u->spec_rot = CSCH_UNDO_RMODIFY(texto, spec_rot);
	u->spec_mirx = CSCH_UNDO_MODIFY(texto, spec_mirx);
	u->spec_miry = CSCH_UNDO_MODIFY(texto, spec_miry);
	u->halign = CSCH_UNDO_MODIFY(texto, halign);
	u->text = CSCH_UNDO_MODIFY(texto, text);
	u->has_bbox = CSCH_UNDO_MODIFY(texto, has_bbox);
	u->dyntext = CSCH_UNDO_MODIFY(texto, dyntext);

	undo_text_modify_swap(u);
	if (undoable) csch_undo_inc_serial(sheet);
	else undo_text_modify_free(u);
}

