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


#include "config.h"

#include <librnd/core/error.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_obj.h"
#include "cnc_any_obj.h"
#include "cnc_arc.h"
#include "operation.h"
#include "op_common.h"
#include "gengeo2d/box.h"
#include "gengeo2d/xform.h"
#include "undo.h"
#include "rotate.h"

#include "cnc_poly.h"

csch_cpoly_t *csch_cpoly_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_cpoly_t *poly;

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

	csch_cobj_init(&poly->hdr, sheet, parent, oid, CSCH_CTYPE_POLY);
	return poly;
}

csch_cpoly_t *csch_cpoly_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_cpoly_t *src, int keep_id)
{
	csch_cpoly_t *dst = csch_cpoly_alloc(sheet, parent, CSCH_KEEP_OID(parent, src->hdr.oid));

	dst->has_stroke = src->has_stroke;
	dst->has_fill = src->has_fill;

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

	csch_vtcoutline_enlarge(&dst->outline, src->outline.used);
	memcpy(dst->outline.array, src->outline.array, src->outline.used * sizeof(csch_coutline_t));
	dst->outline.used = src->outline.used;

	return dst;
}


void csch_cpoly_free(csch_cpoly_t *poly)
{
	csch_cobj_uninit(&poly->hdr);
	csch_vtcoutline_uninit(&poly->outline);
	free(poly);
}

csch_cpoly_t *csch_cpoly_get(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_oid_t oid)
{
	csch_cpoly_t *poly = htip_get(&grp->id2obj, oid);
	if ((poly != NULL) && (poly->hdr.type != CSCH_CTYPE_POLY))
		return NULL;
	return poly;
}

void csch_cpoly_center_bbox(csch_sheet_t *sheet, const csch_cpoly_t *poly, csch_rtree_box_t *dst)
{
	long n;
	csch_coutline_t *o;
	csch_rtree_box_t tmp;

	csch_bbox_reset(dst);

	for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE: csch_line_center_bbox(sheet, &o->line, &tmp); break;
			case CSCH_CTYPE_ARC: csch_arc_center_bbox(sheet, &o->arc, &tmp); break;
			default: break; /* already warned */
		}
		csch_bbox_bump(dst, x, tmp.x1);
		csch_bbox_bump(dst, x, tmp.x2);
		csch_bbox_bump(dst, y, tmp.y1);
		csch_bbox_bump(dst, y, tmp.y2);
	}
}

void csch_poly_update(csch_sheet_t *sheet, csch_cpoly_t *poly, int do_xform)
{
	long n;
	csch_coutline_t *o;

	csch_cobj_update_pen(&poly->hdr);

	poly->area = -1;

	if (do_xform) {
		for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) {
			csch_ctype_t type_save = o->hdr.type;
			o->hdr = poly->hdr;
			o->hdr.type = type_save;

			switch(o->hdr.type) {
				case CSCH_CTYPE_LINE: csch_line_update_xform(sheet, &o->line); break;
				case CSCH_CTYPE_ARC: csch_arc_update_xform(sheet, &o->arc); break;
				default: rnd_message(RND_MSG_ERROR, "Polygon #%ld contains an invalid object in its contour\n", poly->hdr.oid); break;
			}
		}
	}

	csch_cobj_rtree_del(sheet, &poly->hdr);

	csch_obj_bbox_reset(poly);
	for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE: csch_line_update_bbox(sheet, &o->line); break;
			case CSCH_CTYPE_ARC: csch_arc_update_bbox(sheet, &o->arc); break;
			default: break; /* already warned above */
		}
		csch_bbox_bump(&poly->hdr.bbox, x, o->hdr.bbox.x1);
		csch_bbox_bump(&poly->hdr.bbox, x, o->hdr.bbox.x2);
		csch_bbox_bump(&poly->hdr.bbox, y, o->hdr.bbox.y1);
		csch_bbox_bump(&poly->hdr.bbox, y, o->hdr.bbox.y2);
	}

	csch_cobj_rtree_add(sheet, &poly->hdr);

	csch_cobj_bbox_changed(sheet, &poly->hdr);
}

int csch_cpoly_has_fill(csch_cpoly_t *poly)
{
	return poly->has_fill;
}

static csch_chdr_t *poly_create(csch_sheet_t *sheet, csch_cgrp_t *parent)
{
	csch_cpoly_t *poly = csch_cpoly_alloc(sheet, parent, csch_oid_new(sheet, parent));
	if (poly == NULL) return NULL;
	return &poly->hdr;
}

static void poly_remove_alloc(csch_undo_remove_t *slot)
{
}

static void poly_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 poly_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);
}

/* obj_idx is at most 2 items long but can be NULL to skip the search */
RND_INLINE int poly_isc_with_box_(csch_chdr_t *obj, csch_rtree_box_t *box, long *obj_idx)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)obj;
	g2d_box_t gbox, pbox, obox;
	long n;
	int found = 0;
	csch_coutline_t *o;

	gbox.p1.x = box->x1; gbox.p1.y = box->y1;
	gbox.p2.x = box->x2; gbox.p2.y = box->y2;
	pbox.p1.x = poly->hdr.bbox.x1; pbox.p1.y = poly->hdr.bbox.y1;
	pbox.p2.x = poly->hdr.bbox.x2; pbox.p2.y = poly->hdr.bbox.y2;

	/* poly fully within the box (cheapest) */
	if (g2d_box_in_box(&gbox, &pbox)) return 1;

	/* expect reasonably small polygons; typical use case ranges from triangle
	   to octagon. Linear search will be good enough for this */
	for(n = 0, o = poly->outline.array; n < poly->outline.used; n++, o++) {

		/* optimization: don't go and check the object if its bbox doesn't intersect with target bbox */
		obox.p1.x = o->hdr.bbox.x1; obox.p1.y = o->hdr.bbox.y1;
		obox.p2.x = o->hdr.bbox.x2; obox.p2.y = o->hdr.bbox.y2;
		if (!g2d_isc_box_box(&gbox, &obox))
			continue;

		if (csch_isc_with_box(&o->hdr, box)) {
			if (obj_idx != NULL) {
				obj_idx[found] = n;
				found++;
				if (found == 2)
					return 2;
			}
			else
				return 1;
		}
	}

	return found;
}

static int poly_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box)
{
	return poly_isc_with_box_(obj, box, NULL);
}

int csch_poly_contour_objs_at(csch_cpoly_t *poly, csch_coord_t x, csch_coord_t y, csch_coord_t r, long out[2])
{
	csch_rtree_box_t box;
	long obj_idx[2];
	int len;

	box.x1 = x - r; box.y1 = y - r;
	box.x2 = x + r; box.y2 = y + r;
	len = poly_isc_with_box_(&poly->hdr, &box, obj_idx);
	switch(len) {
		case 0:
			out[0] = out[1] = -1;
			return 0;
		case 1:
			out[0] = obj_idx[0];
			out[1] = -1;
			return 1;
		case 2:
			/* need to sort objects */
			if (obj_idx[1] == obj_idx[0]+1) {
				out[0] = obj_idx[0];
				out[1] = obj_idx[1];
				return 2;
			}
			else if ((obj_idx[0] == 0) && (obj_idx[1] == poly->outline.used - 1)) {
				out[0] = obj_idx[1];
				out[1] = obj_idx[0];
				return 2;
			}
			else { /* self-isect poly? */
				out[0] = obj_idx[0];
				out[1] = -1;
				return 1;
			}
			break;
	}

	assert(!"internal error poly_isc_with_box_()");
	return 0;
}

int csch_poly_contour_obj_ends_at(csch_cpoly_t *poly, long idx, csch_coord_t x, csch_coord_t y)
{
	csch_coutline_t *o;
	csch_coord_t ex, ey;

	if ((idx < 0) || (idx >= poly->outline.used))
		return 0;

	o = &poly->outline.array[idx];

	switch(o->hdr.type) {
		case CSCH_CTYPE_LINE:
			if ((o->line.inst.c.p1.x == x) && (o->line.inst.c.p1.y == y)) return 1;
			if ((o->line.inst.c.p2.x == x) && (o->line.inst.c.p2.y == y)) return 1;
			return 0;
		case CSCH_CTYPE_ARC:
			if ((csch_arc_get_endxy(&o->arc, 0, &ex, &ey) == 0) && (ex == x) && (ey == y)) return 1;
			if ((csch_arc_get_endxy(&o->arc, 1, &ex, &ey) == 0) && (ex == x) && (ey == y)) return 1;
			return 0;
		default: break; /* Invalid object in polygon, already warned on create/load */
	}
	return 0;
}

static void poly_outline_move(csch_vtcoutline_t *outline, csch_coord_t dx, csch_coord_t dy)
{
	csch_coutline_t *o;
	long n;

	for(n = 0, o = outline->array; n < outline->used; n++,o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE:
				o->line.spec.p1.x += dx;
				o->line.spec.p1.y += dy;
				o->line.spec.p2.x += dx;
				o->line.spec.p2.y += dy;
				break;
			case CSCH_CTYPE_ARC:
				o->arc.spec.c.x += dx;
				o->arc.spec.c.y += dy;
				break;
			default: break; /* Invalid object in polygon, already warned on create/load */
		}
	}
}

static void poly_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)obj;
	csch_vtcoutline_t *outline;
	void *cookie;

	outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable);
	poly_outline_move(outline, dx, dy);
	csch_cpoly_modify_geo_end(sheet, poly, &cookie);
}

static void poly_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_cpoly_t *poly = csch_cpoly_dup(sheet, obj->parent, (csch_cpoly_t *)obj, 0);
	if (poly != NULL) {
		poly_outline_move(&poly->outline, dx, dy);
		csch_poly_update(sheet, poly, 1);
		csch_op_inserted(sheet, obj->parent, &poly->hdr);
	}
}

static void poly_outline_rotate(csch_vtcoutline_t *outline, csch_coord_t rcx, csch_coord_t rcy, double da)
{
	csch_coutline_t *o;
	double rad = -da / RND_RAD_TO_DEG;
	double cs = cos(rad), sn = sin(rad);
	long n;

	for(n = 0, o = outline->array; n < outline->used; n++,o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE:
				csch_rotate_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, rcx, rcy, cs, sn);
				csch_rotate_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, rcx, rcy, cs, sn);
				break;
			case CSCH_CTYPE_ARC:
				csch_arc_rotate_(&o->arc, rcx, rcy, da, cs, sn, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start);
				break;
			default: break; /* Invalid object in polygon, already warned on create/load */
		}
	}
}

static void poly_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)obj;
	csch_vtcoutline_t *outline;
	void *cookie;

	outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable);
	poly_outline_rotate(outline, rcx, rcy, da);
	csch_cpoly_modify_geo_end(sheet, poly, &cookie);
}

static void poly_outline_rotate90(csch_vtcoutline_t *outline, csch_coord_t rcx, csch_coord_t rcy, int steps)
{
	csch_coutline_t *o;
	long n;

	for(n = 0, o = outline->array; n < outline->used; n++,o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE:
				csch_rotate90_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, rcx, rcy, steps);
				csch_rotate90_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, rcx, rcy, steps);
				break;
			case CSCH_CTYPE_ARC:
				csch_arc_rotate90_(&o->arc, rcx, rcy, steps, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start);
				break;
			default: break; /* Invalid object in polygon, already warned on create/load */
		}
	}
}

static void poly_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int steps, int undoable)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)obj;
	csch_vtcoutline_t *outline;
	void *cookie;

	outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable);
	poly_outline_rotate90(outline, rcx, rcy, steps);
	csch_cpoly_modify_geo_end(sheet, poly, &cookie);
}


static void poly_outline_mirror(csch_vtcoutline_t *outline, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry)
{
	csch_coutline_t *o;
	long n;

	for(n = 0, o = outline->array; n < outline->used; n++,o++) {
		switch(o->hdr.type) {
			case CSCH_CTYPE_LINE:
				csch_mirror_pt(&o->line.spec.p1.x, &o->line.spec.p1.y, mcx, mcy, mirx, miry);
				csch_mirror_pt(&o->line.spec.p2.x, &o->line.spec.p2.y, mcx, mcy, mirx, miry);
				break;
			case CSCH_CTYPE_ARC:
				csch_arc_mirror_(&o->arc, mcx, mcy, mirx, miry, &o->arc.spec.c.x, &o->arc.spec.c.y, &o->arc.spec.start, &o->arc.spec.delta);
				break;
			default: break; /* Invalid object in polygon, already warned on create/load */
		}
	}
}

static void poly_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)obj;
	csch_vtcoutline_t *outline;
	void *cookie;

	outline = csch_cpoly_modify_geo_begin(sheet, poly, &cookie, undoable);
	poly_outline_mirror(outline, mcx, mcy, mirx, miry);
	csch_cpoly_modify_geo_end(sheet, poly, &cookie);
}

static void poly_outline_inst2spec(csch_sheet_t *sheet, csch_vtcoutline_t *dst, const csch_vtcoutline_t *src)
{
	const csch_coutline_t *s;
	csch_coutline_t *d;
	long n;

	for(n = 0, s = src->array, d = dst->array; n < src->used; n++,s++,d++) {
		switch(s->hdr.type) {
			case CSCH_CTYPE_LINE:
				d->line.spec = s->line.inst.c;
				break;
			case CSCH_CTYPE_ARC:
				d->arc.spec = s->arc.inst.c;
				d->arc.svalid = d->arc.evalid = 0;
				break;
			default: break; /* Invalid object in polygon, already warned on create/load */
		}
	}
}

static void poly_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *in, int undoable)
{
	csch_cpoly_t *dst = (csch_cpoly_t *)obj;
	const csch_cpoly_t *src = (csch_cpoly_t *)in;
	csch_vtcoutline_t *outline;
	void *cookie;

	outline = csch_cpoly_modify_geo_begin(sheet, dst, &cookie, undoable);
	poly_outline_inst2spec(sheet, outline, &src->outline);
	csch_cpoly_modify_geo_end(sheet, dst, &cookie);
}

const csch_ops_t csch_ops_poly = {
	poly_create, poly_remove_alloc, poly_remove_redo, poly_remove_undo,
	poly_isc_with_box,
	poly_move, poly_copy, poly_rotate, poly_rotate90, poly_mirror,
	poly_inst2spec
};

RND_INLINE unsigned csch_contour_hash(const csch_chdr_t *obj)
{
	switch(obj->type) {
		case CSCH_CTYPE_LINE: return csch_line_hash_((const csch_line_t *)obj, 0, 1);
		case CSCH_CTYPE_ARC:  return csch_arc_hash_((const csch_arc_t *)obj, 0, 1);
		default: break;
	}
	return 0;
}

RND_INLINE int csch_contour_keyeq(const csch_chdr_t *p1, const csch_chdr_t *p2)
{
	if (p1->type != p2->type) return 0;
	switch(p1->type) {
		case CSCH_CTYPE_LINE: return csch_line_keyeq_((const csch_line_t *)p1, (const csch_line_t *)p2, 0, 1);
		case CSCH_CTYPE_ARC:  return csch_arc_keyeq_((const csch_arc_t *)p1, (const csch_arc_t *)p2, 0, 1);
		default: break;
	}
	return 1;
}

unsigned csch_cpoly_hash(const csch_cpoly_t *poly, csch_hash_ignore_t ignore)
{
	unsigned res = csch_chdr_hash(&poly->hdr);
	long n;

	res ^= ((unsigned)poly->has_stroke) << 13;
	res ^= ((unsigned)poly->has_fill) << 15;
	res ^= ((unsigned)poly->outline.used) << 3;

	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !poly->hdr.floater)
		for(n = 0; n < poly->outline.used; n++)
			res ^= csch_contour_hash(&poly->outline.array[n].hdr);

	return res;
}

int csch_cpoly_keyeq(const csch_cpoly_t *p1, const csch_cpoly_t *p2, csch_hash_ignore_t ignore)
{
	long n;
	if (!csch_chdr_eq(&p1->hdr, &p2->hdr)) return 0;
	if (p1->has_stroke != p2->has_stroke) return 0;
	if (p1->has_fill != p2->has_fill) return 0;
	if (p1->outline.used != p2->outline.used) return 0;

	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !p1->hdr.floater || !p2->hdr.floater)
		for(n = 0; n < p1->outline.used; n++)
			if (!csch_contour_keyeq(&p1->outline.array[n].hdr, &p2->outline.array[n].hdr))
				return 0;

	return 1;
}

/*** Modify ***/
typedef struct {
	csch_cpoly_t *poly; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	csch_vtcoutline_t outline;
	unsigned swap_outline:1;
	unsigned has_stroke:1;
	unsigned has_fill:1;
	unsigned swap_has:1;
} undo_poly_modify_t;


static int undo_poly_modify_swap(void *udata)
{
	undo_poly_modify_t *u = udata;

	csch_cobj_redraw(u->poly);

	if (u->swap_has) {
		csch_sheet_t *sheet = u->poly->hdr.sheet;

		/* if has_fill changes the fill layer rtree needs to be updated accordingly */
		if (u->poly->has_fill && !u->has_fill)
			csch_rtree_delete(&sheet->dsply_fill[u->poly->hdr.dsply], &u->poly->hdr, &u->poly->hdr.bbox);
		else if (!u->poly->has_fill && u->has_fill)
			csch_rtree_insert(&sheet->dsply_fill[u->poly->hdr.dsply], &u->poly->hdr, &u->poly->hdr.bbox);

		rnd_swap(int, u->has_stroke, u->poly->has_stroke);
		rnd_swap(int, u->has_fill, u->poly->has_fill);

	}
	if (u->swap_outline) {
		rnd_swap(csch_vtcoutline_t, u->outline, u->poly->outline);
		csch_poly_update(u->poly->hdr.sheet, u->poly, 1);
	}

	csch_op_geo_edited(u->poly->hdr.sheet, &u->poly->hdr);

	return 0;
}

static void undo_poly_modify_print(void *udata, char *dst, size_t dst_len)
{
	undo_poly_modify_t *u = udata;
	rnd_snprintf(dst, dst_len, "poly modification has_stroke %d <-> %d, has_fill %d <-> %d", u->poly->has_stroke, u->has_stroke, u->poly->has_fill, u->has_fill);
}

static const char core_cpoly_cookie[] = "libcschem/core/cnc_poly.c";

static const uundo_oper_t undo_poly_modify = {
	core_cpoly_cookie,
	NULL,
	undo_poly_modify_swap,
	undo_poly_modify_swap,
	undo_poly_modify_print
};

void csch_cpoly_modify(csch_sheet_t *sheet, csch_cpoly_t *poly, int *has_stroke, int *has_fill, int undoable)
{
	undo_poly_modify_t utmp, *u = &utmp;

	if (undoable) u = uundo_append(&sheet->undo, &undo_poly_modify, sizeof(undo_poly_modify_t));

	u->poly = poly;
	u->has_stroke = CSCH_UNDO_MODIFY(poly, has_stroke);
	u->has_fill = CSCH_UNDO_MODIFY(poly, has_fill);
	u->swap_has = 1;
	u->swap_outline = 0;

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


static char not_undoable[] = "not undoable";

csch_vtcoutline_t *csch_cpoly_modify_geo_begin(csch_sheet_t *sheet, csch_cpoly_t *poly, void **cookie, int undoable)
{
	undo_poly_modify_t utmp, *u = &utmp;
	long len;

	if (undoable)
		u = uundo_append(&sheet->undo, &undo_poly_modify, sizeof(undo_poly_modify_t));

	u->poly = poly;
	u->swap_has = 0;
	u->swap_outline = 1;

	if (undoable) {
		/* clone the outline */
		u->outline.alloced = poly->outline.used;
		u->outline.used = poly->outline.used;
		len = sizeof(csch_coutline_t) * u->outline.alloced;
		u->outline.array = malloc(len);
		memcpy(u->outline.array, poly->outline.array, len);
		*cookie = u;
		return &u->outline;
	}

	*cookie = (void *)not_undoable;
	return &poly->outline;
}


void csch_cpoly_modify_geo_end(csch_sheet_t *sheet, csch_cpoly_t *poly, void **cookie)
{
	undo_poly_modify_t *u = *cookie;

	if (u == (void *)not_undoable) {
		csch_poly_update(sheet, poly, 1);
		csch_op_geo_edited(sheet, &poly->hdr);
	}
	else {
		undo_poly_modify_swap(u);
		csch_undo_inc_serial(sheet);
	}

	*cookie = NULL;
}
