/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  Copyright (C) 2017,2018 Tibor 'Igor2' Palinkas
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include "config.h"

#include <assert.h>

#include "board.h"
#include "conf_core.h"
#include "data.h"
#include "data_list.h"
#include "draw.h"
#include "draw_wireframe.h"
#include "flag.h"
#include "obj_pstk.h"
#include "obj_pstk_draw.h"
#include "obj_pstk_list.h"
#include "obj_pstk_inlines.h"
#include "obj_pstk_op.h"
#include "obj_subc_parent.h"
#include "obj_hash.h"
#include "operation.h"
#include "search.h"
#include "undo.h"
#include "vtpadstack.h"
#include "event.h"
#include <librnd/core/hid_inlines.h>
#include <librnd/poly/offset.h>

#define SQR(o) ((o)*(o))

static const char core_pstk_cookie[] = "padstack";

const pcb_proto_layer_t pcb_proto_layers[pcb_proto_num_layers] = {
	{"top paste",            PCB_LYT_TOP | PCB_LYT_PASTE,     PCB_LYC_AUTO,             {2,-1}, 0},
	{"top mask",             PCB_LYT_TOP | PCB_LYT_MASK,      PCB_LYC_SUB|PCB_LYC_AUTO, {2,-1}, PCB_PROTO_MASK_BLOAT},
	{"top copper",           PCB_LYT_TOP | PCB_LYT_COPPER,    0,                        {4,3},  0},
	{"any internal copper",  PCB_LYT_INTERN | PCB_LYT_COPPER, 0,                        {2,4},  0},
	{"bottom copper",        PCB_LYT_BOTTOM | PCB_LYT_COPPER, 0,                        {2,3},  0},
	{"bottom mask",          PCB_LYT_BOTTOM | PCB_LYT_MASK,   PCB_LYC_SUB|PCB_LYC_AUTO, {4,-1}, PCB_PROTO_MASK_BLOAT},
	{"bottom paste",         PCB_LYT_BOTTOM | PCB_LYT_PASTE,  PCB_LYC_AUTO,             {4,-1}, 0},
	{"slot",                 PCB_LYT_MECH,                    PCB_LYC_AUTO,             {-1,-1},0}
};


void pcb_pstk_reg(pcb_data_t *data, pcb_pstk_t *pstk)
{
	padstacklist_append(&data->padstack, pstk);
	pcb_obj_id_reg(data, pstk);
	PCB_SET_PARENT(pstk, data, data);
}

void pcb_pstk_unreg(pcb_pstk_t *pstk)
{
	pcb_data_t *data = pstk->parent.data;
	assert(pstk->parent_type == PCB_PARENT_DATA);
	padstacklist_remove(pstk);
	pcb_obj_id_del(data, pstk);
	PCB_CLEAR_PARENT(pstk);
}

pcb_pstk_t *pcb_pstk_alloc_id(pcb_data_t *data, long int id)
{
	pcb_pstk_t *ps;

	ps = calloc(sizeof(pcb_pstk_t), 1);
	ps->ID = id;
	ps->protoi = -1;
	ps->type = PCB_OBJ_PSTK;
	ps->Attributes.post_change = pcb_obj_attrib_post_change;

	pcb_pstk_reg(data, ps);

	return ps;
}

pcb_pstk_t *pcb_pstk_alloc(pcb_data_t *data)
{
 return pcb_pstk_alloc_id(data, pcb_create_ID_get());
}


void pcb_pstk_free(pcb_pstk_t *ps)
{
	if ((ps->parent.data != NULL) && (ps->parent.data->padstack_tree != NULL))
		rnd_r_delete_entry(ps->parent.data->padstack_tree, (rnd_box_t *)ps);
	pcb_attribute_free(&ps->Attributes);
	pcb_pstk_unreg(ps);
	free(ps->thermals.shape);
	pcb_obj_common_free((pcb_any_obj_t *)ps);
	free(ps);
}

pcb_pstk_t *pcb_pstk_new_tr(pcb_data_t *data, long int id, rnd_cardinal_t proto, rnd_coord_t x, rnd_coord_t y, rnd_coord_t clearance, pcb_flag_t Flags, double rot, int xmirror, int smirror)
{
	pcb_pstk_t *ps;

	if (proto >= pcb_vtpadstack_proto_len(&data->ps_protos))
		return NULL;

	if (id <= 0)
		id = pcb_create_ID_get();

	ps = pcb_pstk_alloc_id(data, id);

	/* copy values */
	ps->proto = proto;
	ps->x = x;
	ps->y = y;
	ps->Clearance = clearance;
	ps->Flags = Flags;
	ps->rot = rot;
	ps->xmirror = xmirror;
	ps->smirror = smirror;
	pcb_pstk_add(data, ps);
	rnd_event(&PCB->hidlib, PCB_EVENT_NEW_PSTK, "p", ps);
	pcb_poly_clear_from_poly(data, PCB_OBJ_PSTK, NULL, ps);
	return ps;
}

pcb_pstk_t *pcb_pstk_new(pcb_data_t *data, long int id, rnd_cardinal_t proto, rnd_coord_t x, rnd_coord_t y, rnd_coord_t clearance, pcb_flag_t Flags)
{
	return pcb_pstk_new_tr(data, id, proto, x, y, clearance, Flags, 0, 0, 0);
}


void pcb_pstk_add(pcb_data_t *data, pcb_pstk_t *ps)
{
	pcb_pstk_bbox(ps);
	if (!data->padstack_tree)
		data->padstack_tree = rnd_r_create_tree();
	rnd_r_insert_entry(data->padstack_tree, (rnd_box_t *)ps);
	PCB_SET_PARENT(ps, data, data);
}

static void pcb_pstk_bbox_(rnd_box_t *dst, pcb_pstk_t *ps, rnd_bool copper_only)
{
	int n, sn;
	pcb_line_t line;
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);
	pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape(ps);
	assert(proto != NULL);

	dst->X1 = dst->X2 = ps->x;
	dst->Y1 = dst->Y2 = ps->y;

	if (ts != NULL)
	for(sn = 0; sn < ts->len; sn++) {
		pcb_pstk_shape_t *shape = ts->shape + sn;
		switch(shape->shape) {
			case PCB_PSSH_POLY:
				for(n = 0; n < shape->data.poly.len; n++)
					rnd_box_bump_point(dst, shape->data.poly.x[n] + ps->x, shape->data.poly.y[n] + ps->y);
				break;
			case PCB_PSSH_LINE:
				line.Point1.X = shape->data.line.x1 + ps->x;
				line.Point1.Y = shape->data.line.y1 + ps->y;
				line.Point2.X = shape->data.line.x2 + ps->x;
				line.Point2.Y = shape->data.line.y2 + ps->y;
				line.Thickness = shape->data.line.thickness;
				line.Clearance = 0;
				line.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0);
				pcb_line_bbox(&line);
				rnd_box_bump_box(dst, &line.BoundingBox);
				break;
			case PCB_PSSH_CIRC:
				rnd_box_bump_point(dst, ps->x - shape->data.circ.dia/2, ps->y - shape->data.circ.dia/2);
				rnd_box_bump_point(dst, ps->x + shape->data.circ.dia/2, ps->y + shape->data.circ.dia/2);
				break;
			case PCB_PSSH_HSHADOW:
				break;
		}
	}

	if (!copper_only && PCB_NONPOLY_HAS_CLEARANCE(ps)) {
		dst->X1 -= ps->Clearance;
		dst->Y1 -= ps->Clearance;
		dst->X2 += ps->Clearance;
		dst->Y2 += ps->Clearance;
	}

	if (proto->hdia != 0) {
		/* corner case: no copper around the hole all 360 deg - let the hole stick out */
		rnd_box_bump_point(dst, ps->x - proto->hdia/2, ps->y - proto->hdia/2);
		rnd_box_bump_point(dst, ps->x + proto->hdia/2, ps->y + proto->hdia/2);
	}

	rnd_close_box(dst);
}

void pcb_pstk_bbox(pcb_pstk_t *ps)
{
	pcb_pstk_bbox_(&ps->BoundingBox, ps, rnd_false);
	pcb_pstk_bbox_(&ps->bbox_naked, ps, rnd_true);
}

void pcb_pstk_copper_bbox(rnd_box_t *dst, pcb_pstk_t *ps)
{
	pcb_pstk_bbox_(dst, ps, rnd_true);
}

/* hash */
int pcb_pstk_eq(const pcb_host_trans_t *tr1, const pcb_pstk_t *p1, const pcb_host_trans_t *tr2, const pcb_pstk_t *p2)
{
	double rotdir1 = tr1->on_bottom ? -1.0 : 1.0, rotdir2 = tr2->on_bottom ? -1.0 : 1.0;

TODO("padstack: should compare shape by shape: a 180 deg rotated or mirrored rectangle is still just the same rectangle!")

	if ((p1->smirror ^ tr1->on_bottom) != (p2->smirror ^ tr2->on_bottom)) return 0;
	if ((p1->xmirror ^ tr1->on_bottom) != (p2->xmirror ^ tr2->on_bottom)) return 0;
	if (floor(fmod((p1->rot * rotdir1) + tr1->rot, 360.0)*10000) != floor(fmod((p2->rot * rotdir2) + tr2->rot, 360.0)*10000)) return 0;

	if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, p1) && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, p2)) {
		rnd_coord_t x1, y1, x2, y2;
		
		pcb_hash_tr_coords(tr1, &x1, &y1, p1->x, p1->y);
		pcb_hash_tr_coords(tr2, &x2, &y2, p2->x, p2->y);
		if ((x1 != x2) || (y1 != y2)) return 0;
	}

	if (pcb_neqs(p1->term, p2->term)) return 0;
	return 1;
}

unsigned int pcb_pstk_hash(const pcb_host_trans_t *tr, const pcb_pstk_t *p)
{
	unsigned int crd = 0;
	double rotdir = tr->on_bottom ? -1.0 : 1.0;

	if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, p)) {
		rnd_coord_t x, y;
		
		pcb_hash_tr_coords(tr, &x, &y, p->x, p->y);
		crd = pcb_hash_coord(x) ^ pcb_hash_coord(y);
	}

	return pcb_hash_angle(tr, p->rot * rotdir) ^ pcb_hash_coord(p->Clearance) ^
		pcb_hash_str(p->term) ^ crd ^
		murmurhash(&p->smirror, sizeof(p->smirror)) ^
		murmurhash(&p->xmirror, sizeof(p->xmirror));
}

/*** utils ***/

pcb_pstk_t *pcb_pstk_copy_meta(pcb_pstk_t *dst, pcb_pstk_t *src)
{
	if (dst == NULL)
		return NULL;
	pcb_attribute_copy_all(&dst->Attributes, &src->Attributes);
	dst->thermals.used = src->thermals.used;
	if (dst->thermals.used > 0) {
		dst->thermals.shape = malloc(dst->thermals.used * sizeof(dst->thermals.shape[0]));
		memcpy(dst->thermals.shape, src->thermals.shape, src->thermals.used * sizeof(src->thermals.shape[0]));
	}
	else
		dst->thermals.shape = 0;
	memcpy(&dst->Flags, &src->Flags, sizeof(dst->Flags));
	return dst;
}

pcb_pstk_t *pcb_pstk_copy_orient(pcb_pstk_t *dst, pcb_pstk_t *src)
{
	if (dst == NULL)
		return NULL;
	dst->rot = src->rot;
	dst->xmirror = src->xmirror;
	dst->smirror = src->smirror;
	dst->protoi = -1; /* invalidate the transformed index to get it recalculated */
	return dst;
}

void pcb_pstk_move_(pcb_pstk_t *ps, rnd_coord_t dx, rnd_coord_t dy)
{
	ps->x += dx;
	ps->y += dy;
	ps->BoundingBox.X1 += dx;
	ps->BoundingBox.Y1 += dy;
	ps->BoundingBox.X2 += dx;
	ps->BoundingBox.Y2 += dy;
	ps->bbox_naked.X1 += dx;
	ps->bbox_naked.Y1 += dy;
	ps->bbox_naked.X2 += dx;
	ps->bbox_naked.Y2 += dy;
}

void pcb_pstk_move(pcb_pstk_t *ps, rnd_coord_t dx, rnd_coord_t dy, rnd_bool more_to_come)
{
	pcb_opctx_t ctx;
	ctx.move.pcb = NULL;
	ctx.move.dx = dx;
	ctx.move.dy = dy;
	ctx.move.more_to_come = more_to_come;
	pcb_pstkop_move(&ctx, ps);
}

pcb_pstk_t *pcb_pstk_by_id(pcb_data_t *base, long int ID)
{
	/* We can not have an rtree based search here: we are often called
	   in the middle of an operation, after the pstk got already removed
	   from the rtree. It happens in e.g. undoable padstack operations
	   where the padstack tries to look up its parent subc by ID, while
	   the subc is being sent to the other side.
	
	   The solution will be the ID hash. */
	PCB_PADSTACK_LOOP(base);
	{
		if (padstack->ID == ID)
			return padstack;
	}
	PCB_END_LOOP;

	return NULL;
}

/*** draw ***/

static void set_ps_color(pcb_pstk_t *ps, int is_current, pcb_layer_type_t lyt, const pcb_layer_t *ly1, rnd_xform_t *xform)
{
	const rnd_color_t *color, *layer_color = NULL;
	rnd_color_t buf;

	if ((lyt & PCB_LYT_PASTE) || (lyt & PCB_LYT_MASK) || (lyt & PCB_LYT_SILK)) {
		if (ly1 == NULL)
			layer_color = pcb_layer_default_color(0, lyt);
		else
			layer_color = &ly1->meta.real.color;
	}

	if (xform->invis_other_groups && !is_current) {
		color = &conf_core.appearance.color.invisible_objects;
	}
	else if (xform->black_current_group && is_current) {
		color = rnd_color_black;
	}
	else if (ps->term == NULL) {
		/* normal via, not a terminal */
		if (xform->flag_color && !pcb_draw_force_termlab && PCB_FLAG_TEST(PCB_FLAG_WARN | PCB_FLAG_SELECTED | PCB_FLAG_FOUND, ps)) {
			if (PCB_FLAG_TEST(PCB_FLAG_WARN, ps))
				color = &conf_core.appearance.color.warn;
			else if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps))
				color = &conf_core.appearance.color.selected;
			else
				color = &conf_core.appearance.color.connected;

			if (ps->ind_onpoint) {
				pcb_lighten_color(color, &buf, 1.75);
				color = &buf;
			}
		}
		else {
			if (layer_color != NULL)
				color = layer_color;
			else if (PCB_HAS_COLOROVERRIDE(ps))
				color = ps->override_color;
			else if (is_current)
				color = &conf_core.appearance.color.via;
			else
				color = &conf_core.appearance.color.via_far;
		}
	}
	else {
		/* terminal */
		if (xform->flag_color && !pcb_draw_force_termlab && PCB_FLAG_TEST(PCB_FLAG_WARN | PCB_FLAG_SELECTED | PCB_FLAG_FOUND, ps)) {
			if (PCB_FLAG_TEST(PCB_FLAG_WARN, ps))
				color = &conf_core.appearance.color.warn;
			else if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps))
				color = &conf_core.appearance.color.selected;
			else
				color = &conf_core.appearance.color.connected;

			if (ps->ind_onpoint) {
				pcb_lighten_color(color, &buf, 1.75);
				color = &buf;
			}
		}
		else
			if (layer_color != NULL)
				color = layer_color;
			else if (PCB_HAS_COLOROVERRIDE(ps))
				color = ps->override_color;
			else if (is_current)
				color = &conf_core.appearance.color.pin;
			else
				color = &conf_core.appearance.color.pin_far;

	}

	rnd_render->set_color(pcb_draw_out.fgGC, color);
}

static void set_ps_annot_color(rnd_hid_gc_t gc, pcb_pstk_t *ps)
{
	rnd_render->set_color(pcb_draw_out.fgGC, PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps) ?
		&conf_core.appearance.color.selected : &conf_core.appearance.color.padstackmark);
}

static void pcb_pstk_draw_poly(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape, int fill, rnd_coord_t dthick)
{
	long n;

	if (shape->data.poly.pa == NULL)
		pcb_pstk_shape_update_pa(&shape->data.poly);
	rnd_hid_set_line_cap(gc, rnd_cap_round);
	if (dthick != 0) {
		/* slow - but would be used on export mostly, not on-screen drawing */
		rnd_polo_t *p, p_st[32];
		rnd_coord_t *x, *y, xy_st[64];
		double vl = rnd_round(-dthick/2);
		long n;

		if (shape->data.poly.inverted)
			dthick = -dthick;
		vl = rnd_round(dthick/2);
		
		if (shape->data.poly.len >= sizeof(p_st) / sizeof(p_st[0])) {
			p = malloc(sizeof(rnd_polo_t) * shape->data.poly.len);
			x = malloc(sizeof(rnd_coord_t) * shape->data.poly.len);
		}
		else {
			p = p_st;
			x = xy_st;
		}
		y = x + sizeof(p_st) / sizeof(p_st[0]);

		/* relative: move each point radially */
		for(n = 0; n < shape->data.poly.len; n++) {
			p[n].x = shape->data.poly.x[n];
			p[n].y = shape->data.poly.y[n];
		}
		rnd_polo_norms(p, shape->data.poly.len);
		rnd_polo_offs(vl, p, shape->data.poly.len);
		for(n = 0; n < shape->data.poly.len; n++) {
			x[n] = rnd_round(p[n].x);
			y[n] = rnd_round(p[n].y);
		}

		if (!fill) {
			for(n = 1; n < shape->data.poly.len; n++)
				rnd_render->draw_line(gc, ps->x + x[n-1], ps->y + y[n-1], ps->x + x[n], ps->y + y[n]);
			rnd_render->draw_line(gc, ps->x + x[n-1], ps->y + y[n-1], ps->x + x[0], ps->y + y[0]);
		}
		else
			rnd_render->fill_polygon_offs(gc, shape->data.poly.len, x, y, ps->x, ps->y);
		if (p != p_st) {
			free(p);
			free(x);
		}
	}
	else {
		if (!fill) {
			for(n = 1; n < shape->data.poly.len; n++)
				rnd_render->draw_line(gc, ps->x + shape->data.poly.x[n-1], ps->y + shape->data.poly.y[n-1], ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n]);
			rnd_render->draw_line(gc, ps->x + shape->data.poly.x[n-1], ps->y + shape->data.poly.y[n-1], ps->x + shape->data.poly.x[0], ps->y + shape->data.poly.y[0]);
		}
		else
			rnd_render->fill_polygon_offs(gc, shape->data.poly.len, shape->data.poly.x, shape->data.poly.y, ps->x, ps->y);
	}
}

static void pcb_pstk_draw_shape_solid(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape)
{
	rnd_coord_t r, dthick = 0;
	if ((info != NULL) && (info->xform != NULL) && (info->xform->bloat != 0))
		dthick = info->xform->bloat;

	switch(shape->shape) {
		case PCB_PSSH_POLY:
			pcb_pstk_draw_poly(info, gc, ps, shape, 1, dthick);
			break;
		case PCB_PSSH_LINE:
			rnd_hid_set_line_cap(gc, shape->data.line.square ? rnd_cap_square : rnd_cap_round);
			rnd_hid_set_line_width(gc, MAX(shape->data.line.thickness + dthick, 1));
			rnd_render->draw_line(gc, ps->x + shape->data.line.x1, ps->y + shape->data.line.y1, ps->x + shape->data.line.x2, ps->y + shape->data.line.y2);
			break;
		case PCB_PSSH_CIRC:
			r = MAX(shape->data.circ.dia/2 + dthick/2, 1);
			rnd_hid_set_line_cap(gc, rnd_cap_round);
			rnd_render->fill_circle(gc, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, r);
			break;
		case PCB_PSSH_HSHADOW:
			break;
	}
}

static void pcb_pstk_draw_shape_thin(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape)
{
	rnd_coord_t r, dthick = 0;
	rnd_hid_set_line_cap(gc, rnd_cap_round);

	if ((info != NULL) && (info->xform != NULL) && (info->xform->bloat != 0))
		dthick = info->xform->bloat;

	switch(shape->shape) {
		case PCB_PSSH_POLY:
			pcb_pstk_draw_poly(info, gc, ps, shape, 0, dthick);
			break;
		case PCB_PSSH_LINE:
			pcb_draw_wireframe_line(gc, ps->x + shape->data.line.x1, ps->y + shape->data.line.y1, ps->x + shape->data.line.x2, ps->y + shape->data.line.y2, MAX(shape->data.line.thickness + dthick, 1), shape->data.line.square);
			break;
		case PCB_PSSH_CIRC:
			r = MAX(shape->data.circ.dia/2 + dthick/2, 1);
			rnd_render->draw_arc(gc, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, r, r, 0, 360);
			break;
		case PCB_PSSH_HSHADOW:
			break;
	}
}

rnd_r_dir_t pcb_pstk_draw_callback(const rnd_box_t *b, void *cl)
{
	pcb_draw_info_t *info = cl;
	pcb_pstk_t *ps = (pcb_pstk_t *)b;
	pcb_pstk_shape_t *shape;
	pcb_layergrp_t *grp = NULL;

	pcb_obj_noexport(info, ps, return RND_R_DIR_NOT_FOUND);

	if (pcb_hidden_floater((pcb_any_obj_t*)b, info) || pcb_partial_export((pcb_any_obj_t*)b, info))
		return RND_R_DIR_FOUND_CONTINUE;

	if (!PCB->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent))
		return RND_R_DIR_NOT_FOUND;

	if (info->objcb.pstk.gid < 0) {
		if (info->objcb.pstk.shape_mask != 0)
			shape = pcb_pstk_shape(ps, info->objcb.pstk.shape_mask, info->objcb.pstk.comb);
		else
			return RND_R_DIR_NOT_FOUND;
	}
	else {
		int n;
		rnd_layer_id_t *lid;
		shape = pcb_pstk_shape_gid(info->pcb, ps, info->objcb.pstk.gid, info->objcb.pstk.comb, &grp);
		for(n = 0, lid = grp->lid; n < grp->len; n++,lid++) {
			if (*lid < ps->thermals.used) {
				if ((ps->thermals.shape[*lid] & PCB_THERMAL_ON) && ((ps->thermals.shape[*lid] & 3) == PCB_THERMAL_NOSHAPE))
					return RND_R_DIR_NOT_FOUND;
			}
		}
	}

	if (shape != NULL) {
		if (grp == NULL)
			set_ps_color(ps, info->objcb.pstk.is_current, info->objcb.pstk.shape_mask, info->objcb.pstk.layer1, info->xform);
		else
			set_ps_color(ps, info->objcb.pstk.is_current, grp->ltype, info->objcb.pstk.layer1, info->xform);
		if (info->xform->thin_draw || info->xform->wireframe) {
			rnd_hid_set_line_width(pcb_draw_out.fgGC, 0);
			pcb_pstk_draw_shape_thin(info, pcb_draw_out.fgGC, ps, shape);
		}
		else
			pcb_pstk_draw_shape_solid(info, pcb_draw_out.fgGC, ps, shape);
		ps->draw_stamp = info->draw_stamp;

		if (ps->ind_editpoint)
			pcb_draw_delay_label_add((pcb_any_obj_t *)ps);
	}

	return RND_R_DIR_FOUND_CONTINUE;
}

rnd_r_dir_t pcb_pstk_draw_mark_callback(const rnd_box_t *b, void *cl)
{
	pcb_pstk_t *ps = (pcb_pstk_t *)b;
	pcb_pstk_proto_t *proto;
	rnd_coord_t mark, mark2;

	/* mark is a cross in the middle, right on the hole;
	   cross size should extend beyond the hole */
	rnd_hid_set_line_cap(pcb_draw_out.fgGC, rnd_cap_round);
	mark = conf_core.appearance.padstack.cross_size;
	proto = pcb_pstk_get_proto(ps);
	if (proto != NULL)
		mark += proto->hdia/2;

	mark2 = mark*2;
	if (mark2 < rnd_render->coord_per_pix)
		return RND_R_DIR_FOUND_CONTINUE;

	/* draw the cross using xor */
	set_ps_annot_color(pcb_draw_out.fgGC, ps);
	rnd_hid_set_line_width(pcb_draw_out.fgGC, 0);
	if (mark2 > rnd_render->coord_per_pix*3) {
		rnd_render->draw_line(pcb_draw_out.fgGC, ps->x-mark, ps->y, ps->x+mark, ps->y);
		rnd_render->draw_line(pcb_draw_out.fgGC, ps->x, ps->y-mark, ps->x, ps->y+mark);
	}
	else
		rnd_render->draw_line(pcb_draw_out.fgGC, ps->x-rnd_render->coord_per_pix, ps->y, ps->x+rnd_render->coord_per_pix, ps->y);

	return RND_R_DIR_FOUND_CONTINUE;
}

rnd_r_dir_t pcb_pstk_draw_label_callback(const rnd_box_t *b, void *cl)
{
	pcb_draw_info_t *info = cl;
	pcb_pstk_t *ps = (pcb_pstk_t *)b;
	int is_drawn = (ps->draw_stamp == info->draw_stamp);

	/* do not draw any label if the padstack is not visible, to avoid overlapping
	   term labels of sides: two SMD pads on the two sides, turn off bottom to
	   see top only */
	if (!is_drawn)
		return RND_R_DIR_FOUND_CONTINUE;

	/* draw the label if enabled, after everything else is drawn */
	if (ps->term != NULL) {
		if ((pcb_draw_force_termlab) || PCB_FLAG_TEST(PCB_FLAG_TERMNAME, ps))
			pcb_pstk_draw_label(info, ps, 1);
	}

	if (ps->noexport)
		pcb_obj_noexport_mark(ps, ps->x, ps->y);

	return RND_R_DIR_FOUND_CONTINUE;
}

rnd_r_dir_t pcb_pstk_draw_hole_callback(const rnd_box_t *b, void *cl)
{
	pcb_draw_info_t *info = cl;
	pcb_pstk_t *ps = (pcb_pstk_t *)b;
	pcb_pstk_proto_t *proto;

	pcb_obj_noexport(info, ps, return RND_R_DIR_NOT_FOUND);

	if (pcb_hidden_floater(ps, info) || pcb_partial_export(ps, info))
		return RND_R_DIR_FOUND_CONTINUE;

	/* hide subc parts if requested */
	if (!info->pcb->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent))
		return RND_R_DIR_NOT_FOUND;

	/* no hole in this layer group */
	if (info->objcb.pstk.gid >= 0) {
		if (!pcb_pstk_bb_drills(info->pcb, ps, info->objcb.pstk.gid, &proto))
			return RND_R_DIR_FOUND_CONTINUE;
	}
	else
		proto = pcb_pstk_get_proto(ps);

	/* No hole at all */
	if ((proto == NULL) || (proto->hdia <= 0))
		return RND_R_DIR_NOT_FOUND;

	/* hole is plated, but the caller doesn't want plated holes */
	if (proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_PLATED)))
		return RND_R_DIR_NOT_FOUND;

	/* hole is unplated, but the caller doesn't want unplated holes */
	if (!proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_UNPLATED)))
		return RND_R_DIR_NOT_FOUND;

	/* BBvia, but the caller doesn't want BBvias */
	if (((proto->htop != 0) || (proto->hbottom != 0)) && (!(info->objcb.pstk.holetype & PCB_PHOLE_BB)))
		return RND_R_DIR_NOT_FOUND;

	/* actual hole */
	rnd_hid_set_line_width(pcb_draw_out.drillGC, 0);
	rnd_hid_set_line_cap(pcb_draw_out.drillGC, rnd_cap_round);
	rnd_render->fill_circle(pcb_draw_out.drillGC, ps->x, ps->y, proto->hdia / 2);

	/* indicate unplated holes with an arc; unplated holes are more rare
	   than plated holes, thus unplated holes are indicated */
	if (!proto->hplated && !pcb_xform_omit_overlay(info)) {
		rnd_coord_t r = proto->hdia / 2;
		r -= r/8; /* +12.5% */
		rnd_render->set_color(pcb_draw_out.fgGC, PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps) ? &conf_core.appearance.color.selected : &conf_core.appearance.color.subc);
		rnd_hid_set_line_width(pcb_draw_out.fgGC, 0);
		rnd_render->draw_arc(pcb_draw_out.fgGC, ps->x, ps->y, r, r, 20, 290);
	}

	return RND_R_DIR_FOUND_CONTINUE;
}

rnd_r_dir_t pcb_pstk_draw_slot_callback(const rnd_box_t *b, void *cl)
{
	pcb_draw_info_t *info = cl;
	pcb_pstk_t *ps = (pcb_pstk_t *)b;
	pcb_pstk_proto_t *proto;
	pcb_pstk_shape_t *shape;

	pcb_obj_noexport(info, ps, return RND_R_DIR_NOT_FOUND);

	if (pcb_hidden_floater(ps, info) || pcb_partial_export(ps, info))
		return RND_R_DIR_FOUND_CONTINUE;

	/* hide subc parts if requested */
	if (!info->pcb->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent))
		return RND_R_DIR_NOT_FOUND;

	/* no slot in this layer group */
	if (info->objcb.pstk.gid >= 0) {
		if (!pcb_pstk_bb_drills(info->pcb, ps, info->objcb.pstk.gid, &proto))
			return RND_R_DIR_FOUND_CONTINUE;
	}
	else
		proto = pcb_pstk_get_proto(ps);

	/* No slot at all */
	if (proto->mech_idx < 0)
		return RND_R_DIR_NOT_FOUND;

	/* hole is plated, but the caller doesn't want plated holes */
	if (proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_PLATED)))
		return RND_R_DIR_NOT_FOUND;

	/* hole is unplated, but the caller doesn't want unplated holes */
	if (!proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_UNPLATED)))
		return RND_R_DIR_NOT_FOUND;

	/* BBslot, but the caller doesn't want BBslot */
	if (((proto->htop != 0) || (proto->hbottom != 0)) && (!(info->objcb.pstk.holetype & PCB_PHOLE_BB)))
		return RND_R_DIR_NOT_FOUND;

	/* actual slot */
	shape = pcb_pstk_shape(ps, PCB_LYT_MECH, PCB_LYC_AUTO);
	if (shape != NULL) {
		if (info->xform->thin_draw || info->xform->wireframe) {
			rnd_hid_set_line_width(pcb_draw_out.drillGC, 0);
			pcb_pstk_draw_shape_thin(info, pcb_draw_out.drillGC, ps, shape);
		}
		else
			pcb_pstk_draw_shape_solid(info, pcb_draw_out.drillGC, ps, shape);
	}

	return RND_R_DIR_FOUND_CONTINUE;
}

void pcb_pstk_thindraw(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps)
{
	pcb_pstk_shape_t *shape = NULL;
	pcb_board_t *pcb;
	rnd_layergrp_id_t gid = PCB_CURRLAYER(PCB)->meta.real.grp;

	pcb = pcb_data_get_top(ps->parent.data);
	if (pcb != NULL) {
		shape = pcb_pstk_shape_gid(pcb, ps, gid, 0, NULL);
		if (shape == NULL)
			shape = pcb_pstk_shape_gid(pcb, ps, gid, PCB_LYC_SUB, NULL);
	}
	else { /* no pcb means buffer - take the first shape, whichever layer it is for */
		pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape(ps);
		if ((ts != NULL)  && (ts->len > 0))
			shape = ts->shape;
	}

	if (shape != NULL)
		pcb_pstk_draw_shape_thin(info, gc, ps, shape);
}

void pcb_pstk_draw_label(pcb_draw_info_t *info, pcb_pstk_t *ps, rnd_bool vis_side)
{
	rnd_bool vert;
	rnd_coord_t dx, dy;

	if (ps->term == NULL)
		return;

	dx = ps->BoundingBox.X2 - ps->BoundingBox.X1;
	dy = ps->BoundingBox.Y2 - ps->BoundingBox.Y1;
	if ((dx == 0) && (dy == 0)) {
		pcb_pstk_bbox(ps);
		dx = ps->BoundingBox.X2 - ps->BoundingBox.X1;
		dy = ps->BoundingBox.Y2 - ps->BoundingBox.Y1;
	}

	vert = dx < dy;
#ifdef PCB_PSTK_LABEL_OFFCENTER
	rnd_coord_t offs = 0;
	pcb_pstk_proto_t *proto;
	proto = pcb_pstk_get_proto(ps);
	if ((proto != NULL) && (proto->hdia > 0))
		offs = proto->hdia/2;
	pcb_term_label_draw(info, ps->x + offs, ps->y, conf_core.appearance.term_label_size, vert, rnd_false, (pcb_any_obj_t *)ps);
#endif
	pcb_term_label_draw(info, ps->x, ps->y, conf_core.appearance.term_label_size, vert, rnd_true, (pcb_any_obj_t *)ps);
}

static rnd_xform_t dummy_xform;
void pcb_pstk_draw_preview(pcb_board_t *pcb, const pcb_pstk_t *ps, char *layers, rnd_bool mark, rnd_bool label, const rnd_box_t *drawn_area)
{
	pcb_draw_info_t info;
	int n, draw_hole = 0;
	rnd_layer_id_t lid;

	info.pcb = pcb;
	info.drawn_area = drawn_area;
	info.xform_caller = info.xform_exporter = info.xform = &dummy_xform;
	info.layer = NULL;
	info.objcb.pstk.gid = -1;
	info.objcb.pstk.holetype = PCB_PHOLE_UNPLATED | PCB_PHOLE_PLATED;

	/* draw non-currents */
	info.objcb.pstk.is_current = 0;
	for(n = pcb_proto_num_layers-1; n >= 0; n--) {
		if ((layers == NULL) || (layers[n] == 1)) {
			info.objcb.pstk.shape_mask = pcb_proto_layers[n].mask;
			info.objcb.pstk.comb = pcb_proto_layers[n].comb;
			info.objcb.pstk.layer1 = info.layer = NULL;
			if (!(info.objcb.pstk.shape_mask & PCB_LYT_COPPER)) {
				if (pcb_layer_list(pcb, info.objcb.pstk.shape_mask, &lid, 1) == 1)
					info.objcb.pstk.layer1 = info.layer = pcb_get_layer(pcb->Data, lid);
			}
			if (info.objcb.pstk.shape_mask == PCB_LYT_MECH)
				draw_hole = 1;
			else
				pcb_pstk_draw_callback((rnd_box_t *)ps, &info);
		}
	}

	/* draw current with strong */
	info.objcb.pstk.is_current = 1;
	for(n = pcb_proto_num_layers-1; n >= 0; n--) {
		if ((layers == NULL) || (layers[n] > 1)) {
			info.objcb.pstk.shape_mask = pcb_proto_layers[n].mask;
			info.objcb.pstk.comb = pcb_proto_layers[n].comb;
			info.objcb.pstk.layer1 = info.layer = NULL;
			if (!(info.objcb.pstk.shape_mask & PCB_LYT_COPPER)) {
				if (pcb_layer_list(pcb, info.objcb.pstk.shape_mask, &lid, 1) == 1)
					info.objcb.pstk.layer1 = info.layer = pcb_get_layer(pcb->Data, lid);
			}
			if (info.objcb.pstk.shape_mask == PCB_LYT_MECH)
				draw_hole = 2;
			else
				pcb_pstk_draw_callback((rnd_box_t *)ps, &info);
		}
	}

	if (draw_hole) {
		info.objcb.pstk.shape_mask = PCB_LYT_MECH;
		info.objcb.pstk.is_current = (draw_hole > 1);
		pcb_pstk_draw_hole_callback((rnd_box_t *)ps, &info);
	}

	if (mark)
		pcb_pstk_draw_mark_callback((rnd_box_t *)ps, &info);

	if (label)
		pcb_pstk_draw_label_callback((rnd_box_t *)ps, &info);
}



void pcb_pstk_invalidate_erase(pcb_pstk_t *ps)
{
	pcb_draw_invalidate(ps);
}

void pcb_pstk_invalidate_draw(pcb_pstk_t *ps)
{
	pcb_draw_invalidate(ps);
}


static int pcb_pstk_near_box_(pcb_pstk_t *ps, rnd_box_t *box, pcb_pstk_shape_t *shape)
{
	pcb_any_line_t pad;
	rnd_vector_t v;
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);
	int isneg = PCB_IS_BOX_NEGATIVE(box), is_in, n;

	/* cheap check: hole */
	if (proto->hdia > 0) {
		/* in negative case, touching the hole means hit */
		if (isneg)
			if (PCB_CIRCLE_TOUCHES_BOX(ps->x, ps->y, proto->hdia/2, box))
				return 1;
		
		/* in positive case, not including the hole means bad */
		if (!isneg)
			if (!PCB_POINT_IN_BOX(ps->x-proto->hdia/2,ps->y-proto->hdia/2, box) ||
				!PCB_POINT_IN_BOX(ps->x+proto->hdia/2,ps->y+proto->hdia/2, box))
					return 0;
	}

	switch(shape->shape) {
		case PCB_PSSH_CIRC:
		if (isneg)
			return PCB_CIRCLE_TOUCHES_BOX(ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, shape->data.circ.dia/2, box);
		return
			PCB_POINT_IN_BOX(ps->x+shape->data.circ.x-proto->hdia/2,ps->y+shape->data.circ.y-shape->data.circ.dia/2, box) &&
			PCB_POINT_IN_BOX(ps->x+shape->data.circ.x+proto->hdia/2,ps->y+shape->data.circ.y+shape->data.circ.dia/2, box);
		case PCB_PSSH_LINE:
			pad.Point1.X = shape->data.line.x1 + ps->x;
			pad.Point1.Y = shape->data.line.y1 + ps->y;
			pad.Point2.X = shape->data.line.x2 + ps->x;
			pad.Point2.Y = shape->data.line.y2 + ps->y;
			pad.Thickness = shape->data.line.thickness;
			pad.Clearance = 0;
			pad.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0);
			return isneg ? PCB_PAD_TOUCHES_BOX(&pad, box) : PCB_PAD_IN_BOX(&pad, box);

		case PCB_PSSH_HSHADOW:
			if (proto->hdia > 0) {
				/* If there is a hole, we have already checked that the hole is near.
				   Since hshadow is the hole. The negative case was already handled
				   above at the hole, need to handle the positive for hit. Note: for
				   slots there is a separate shape that will match */

				if (!isneg && PCB_POINT_IN_BOX(ps->x-proto->hdia/2,ps->y-proto->hdia/2, box) &&
					PCB_POINT_IN_BOX(ps->x+proto->hdia/2,ps->y+proto->hdia/2, box))
					return 1;
			}
			return 0;

		case PCB_PSSH_POLY:
			if (shape->data.poly.pa == NULL)
				pcb_pstk_shape_update_pa(&shape->data.poly);

			if (isneg) {
				/* shortcut: if any point is in our box in negative, we are done */
				for(n = 0; n < shape->data.poly.len; n++) {
					int pib = PCB_POINT_IN_BOX(ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n], box);
					if (pib)
						return 1;
				}
			}

			/* fully within */
			is_in = 1;
			for(n = 0; n < shape->data.poly.len; n++) {
				int pib = PCB_POINT_IN_BOX(ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n], box);
				if (!pib) {
					is_in = 0;
					break;
				}
			}

			if (!isneg) /* normal box - accept only if we are in */
				return is_in;

			/* negative box, not fully within, no poly point in the box, check whether
			   the box is fully within the poly - if any of it's corners is in */
			v[0] = box->X1 - ps->x;
			v[1] = box->Y1 - ps->y;
			if (rnd_polyarea_contour_inside(shape->data.poly.pa, v))
				return 1;


			/* negative box, not fully within, no poly point in the box, check whether
			   box lines intersect poly lines
			   We expect shapes to be simple so linear search is faster than
			   being clever */
			for(n = 1; n < shape->data.poly.len; n++) {
				if (PCB_XYLINE_ISECTS_BOX(ps->x+shape->data.poly.x[n-1],ps->y+shape->data.poly.y[n-1],ps->x+shape->data.poly.x[n],ps->y+shape->data.poly.y[n],  box))
					return 1;
			}

			n = shape->data.poly.len-1;
			if (PCB_XYLINE_ISECTS_BOX(ps->x+shape->data.poly.x[n-1],ps->y+shape->data.poly.y[n-1],ps->x+shape->data.poly.x[0],ps->y+shape->data.poly.y[0],  box))
				return 1;
	}

	return 0;
}

#define	HOLE_IN_BOX(ps, dia, b) \
	( \
	PCB_POINT_IN_BOX((ps)->x - dia/2, (ps)->y - dia/2, (b)) && \
	PCB_POINT_IN_BOX((ps)->x + dia/2, (ps)->y + dia/2, (b)) \
	)

#define	HOLE_TOUCHES_BOX(ps, dia, b) \
	PCB_CIRCLE_TOUCHES_BOX((ps)->x, (ps)->y, dia/2, (b))

int pcb_pstk_near_box(pcb_pstk_t *ps, rnd_box_t *box, pcb_layer_t *layer)
{
	pcb_pstk_shape_t *shp;
	pcb_pstk_tshape_t *tshp = pcb_pstk_get_tshape(ps);

	/* special case: no-shape padstack is used for hole */
	if ((tshp == NULL) || (tshp->len == 0)) {
		pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);
		if (proto->hdia <= 0)
			return 0;
		return (PCB_IS_BOX_NEGATIVE(box) ? HOLE_TOUCHES_BOX(ps, proto->hdia, box) : HOLE_IN_BOX(ps, proto->hdia, box));
	}

	/* no layer means: "is any shape near?" */
	if (layer == NULL) {
		int n;

		if (tshp == NULL)
			return 0;

		for(n = 0; n < tshp->len; n++)
			if (pcb_pstk_near_box_(ps, box, &tshp->shape[n]) != 0)
				return 1;
		return 0;
	}

	/* else check only on the specific layer */
	shp = pcb_pstk_shape(ps, pcb_layer_flags_(layer), layer->comb);
	if (shp == NULL)
		return 0;
	return pcb_pstk_near_box_(ps, box, shp);
}

static int pcb_is_point_in_pstk_(pcb_pstk_t *ps, rnd_coord_t x, rnd_coord_t y, rnd_coord_t radius, pcb_pstk_shape_t *shape)
{
	pcb_any_line_t pad;
	rnd_vector_t v;

	switch(shape->shape) {
		case PCB_PSSH_HSHADOW:
			return 0;
		case PCB_PSSH_CIRC:
			return PCB_POINT_IN_CIRCLE(x, y, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, shape->data.circ.dia/2 + radius);
		case PCB_PSSH_LINE:
			pad.Point1.X = shape->data.line.x1 + ps->x;
			pad.Point1.Y = shape->data.line.y1 + ps->y;
			pad.Point2.X = shape->data.line.x2 + ps->x;
			pad.Point2.Y = shape->data.line.y2 + ps->y;
			pad.Thickness = shape->data.line.thickness;
			pad.Clearance = 0;
			pad.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0);
			return pcb_is_point_in_line(x, y, radius, &pad);
		case PCB_PSSH_POLY:
			if (shape->data.poly.pa == NULL)
				pcb_pstk_shape_update_pa(&shape->data.poly);
			v[0] = x - ps->x;
			v[1] = y - ps->y;
			return rnd_polyarea_contour_inside(shape->data.poly.pa, v);
	}

	return 0;
}

int pcb_is_point_in_pstk(rnd_coord_t x, rnd_coord_t y, rnd_coord_t radius, pcb_pstk_t *ps, pcb_layer_t *layer)
{
	pcb_pstk_shape_t *shp;
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);

	/* cheap check: hole clicked (also necessary for a mounting hole too) */
	if (proto->hdia > 0) {
		double dist2 = SQR((double)x - (double)ps->x) + SQR((double)y - (double)ps->y);
		if (dist2 <= SQR((double)radius + (double)proto->hdia/2.0))
			return 1;
	}

	/* no layer means: "is point in any shape?" */
	if (layer == NULL) {
		int n;
		pcb_pstk_tshape_t *tshp = pcb_pstk_get_tshape(ps);

		if (tshp == NULL)
			return 0;

		for(n = 0; n < tshp->len; n++)
			if (pcb_is_point_in_pstk_(ps, x, y, radius, &tshp->shape[n]) != 0)
				return 1;
		return 0;
	}

	/* else check only on the specific layer */
	shp = pcb_pstk_shape(ps, pcb_layer_flags_(layer), layer->comb);
	if (shp == NULL)
		return 0;
	return pcb_is_point_in_pstk_(ps, x, y, radius, shp);
}

/* Check the minimum distance between a hole's edge and a shape's edge and
   indicate error if it's smaller than min */
static rnd_bool pcb_pstk_shape_hole_break(pcb_pstk_shape_t *shp, rnd_coord_t hdia, rnd_coord_t min)
{
	double dist, neck, mindist2, dist2;
	pcb_line_t line;
	int n;

	switch(shp->shape) {
		case PCB_PSSH_HSHADOW:
			break;
		case PCB_PSSH_CIRC:
			dist = sqrt((double)shp->data.circ.x*(double)shp->data.circ.x + (double)shp->data.circ.y*(double)shp->data.circ.y);
			neck = (double)(shp->data.circ.dia - hdia) / 2.0 - dist;
			break;
		case PCB_PSSH_LINE:
			line.Point1.X = shp->data.line.x1;
			line.Point1.Y = shp->data.line.y1;
			line.Point2.X = shp->data.line.x2;
			line.Point2.Y = shp->data.line.y2;
			line.Thickness = shp->data.line.thickness;
			dist = sqrt(pcb_point_line_dist2(0, 0, &line));
			neck = (double)(shp->data.line.thickness - hdia) / 2.0 - dist;
			break;
		case PCB_PSSH_POLY:
			/* square of the minimal distance required for a neck */
			mindist2 = hdia/2 + min;
			mindist2 *= mindist2;

			/* cheapest test: if any corner is closer to the hole than min, we are doomed */
			for(n = 0; n < shp->data.poly.len; n++) {
				dist2 = (double)shp->data.poly.x[n] * (double)shp->data.poly.x[n] + (double)shp->data.poly.y[n] * (double)shp->data.poly.y[n];
				if (dist2 < mindist2)
					return 1;
			}
			
			/* more expensive: check each edge */
			line.Point1.X = shp->data.poly.x[shp->data.poly.len - 1];
			line.Point1.Y = shp->data.poly.y[shp->data.poly.len - 1];
			line.Thickness = 1;

			for(n = 0; n < shp->data.poly.len; n++) {
				line.Point2.X = shp->data.poly.x[n];
				line.Point2.Y = shp->data.poly.y[n];

				dist2 = pcb_point_line_dist2(0, 0, &line);
				if (dist2 < mindist2)
					return 1;

				/* shift coords for the next iteration */
				line.Point1.X = line.Point2.X;
				line.Point1.Y = line.Point2.Y;
			}
			return 0; /* survived all tests: we are fine! */
	}

	return neck < min;
}

void pcb_pstk_drc_check_and_warn(pcb_pstk_t *ps, rnd_coord_t *err_minring, rnd_coord_t *err_minhole, rnd_coord_t minring, rnd_coord_t mindrill)
{
	pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps);

	if (proto->hdia > 0) {
		int n;
		pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape_(ps->parent.data, ps->proto, 0);

		for(n = 0; n < ts->len; n++) {
			if (!(ts->shape[n].layer_mask & PCB_LYT_COPPER))
				continue; /* only copper shapes can break */
			if (pcb_pstk_shape_hole_break(&ts->shape[n], proto->hdia, minring)) {
				(*err_minring)++;
				break;
			}
		}
	}

TODO("slot: check if slot breaks other shapes")

	if ((mindrill > 0) && (proto->hdia > 0) && (proto->hdia < mindrill))
		*err_minhole = proto->hdia;
}

/*** undoable mirror ***/

typedef struct {
	pcb_pstk_t *pstk; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	rnd_coord_t y_offs;
	int swap_side;
	int disable_xmirror;
} undo_pstk_mirror_t;

static int undo_pstk_mirror_swap(void *udata)
{
	undo_pstk_mirror_t *g = udata;
	pcb_pstk_t *ps = g->pstk;
	int xmirror = !ps->xmirror, smirror = (g->swap_side ? (!ps->smirror) : ps->smirror);

	/* change the mirror flag - this will automatically cause mirroring in
	   every aspect */
	if (g->disable_xmirror)
		pcb_pstk_change_instance(ps, NULL, NULL, NULL, NULL, &smirror);
	else
		pcb_pstk_change_instance(ps, NULL, NULL, NULL, &xmirror, &smirror);

	/* if mirror center is not 0, also move, to emulate that the mirror took
	   place around that point */
	if (((g->y_offs != 0) || (ps->y != 0)) && (g->y_offs != PCB_PSTK_DONT_MIRROR_COORDS)) {
		pcb_poly_restore_to_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps);
		pcb_pstk_invalidate_erase(ps);
		if (ps->parent.data->padstack_tree != NULL)
			rnd_r_delete_entry(ps->parent.data->padstack_tree, (rnd_box_t *)ps);

		ps->y = PCB_SWAP_Y(ps->y) + g->y_offs;
		pcb_pstk_bbox(ps);

		if (ps->parent.data->padstack_tree != NULL)
			rnd_r_insert_entry(ps->parent.data->padstack_tree, (rnd_box_t *)ps);
		pcb_poly_clear_from_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps);
		pcb_pstk_invalidate_draw(ps);
	}


	return 0;
}

static void undo_pstk_mirror_print(void *udata, char *dst, size_t dst_len)
{
	undo_pstk_mirror_t *g = udata;
	rnd_snprintf(dst, dst_len, "pstk mirror y_offs=%$mm swap_side=%d disable_xmirror=%d", g->y_offs, g->swap_side, g->disable_xmirror);
}

static const uundo_oper_t undo_pstk_mirror = {
	core_pstk_cookie,
	NULL,
	undo_pstk_mirror_swap,
	undo_pstk_mirror_swap,
	undo_pstk_mirror_print
};



void pcb_pstk_mirror(pcb_pstk_t *ps, rnd_coord_t y_offs, int swap_side, int disable_xmirror, rnd_bool undoable)
{
	undo_pstk_mirror_t gtmp, *g = &gtmp;

	if (undoable) g = pcb_undo_alloc(pcb_data_get_top(ps->parent.data), &undo_pstk_mirror, sizeof(undo_pstk_mirror_t));

	g->pstk = ps;
	g->y_offs = y_offs;
	g->swap_side = swap_side;
	g->disable_xmirror = disable_xmirror;

	undo_pstk_mirror_swap(g);
	if (undoable) pcb_undo_inc_serial();
}

void pcb_pstk_scale(pcb_pstk_t *ps, double sx, double sy, int undoable)
{
	pcb_pstk_proto_t *prt;
	int n;

	if ((sx == 1.0) && (sy == 1.0))
		return;

	pcb_pstk_pre(ps);

	prt = malloc(sizeof(pcb_pstk_proto_t));
	pcb_pstk_proto_copy(prt, pcb_pstk_get_proto(ps));

	/* after the copy we have the canonical transformed shape only; scale each shape in it */
	for(n = 0; n < prt->tr.array[0].len; n++)
		pcb_pstk_shape_scale(prt, 0, n, sx, sy, undoable);

	if (prt->hdia > 0.0)
		prt->hdia = rnd_round((double)prt->hdia * ((sx+sy)/2.0));

	pcb_pstk_proto_update(prt);
	ps->proto = pcb_pstk_proto_insert_or_free(ps->parent.data, prt, 1, undoable);

	if (sx != 1.0)
		ps->x = rnd_round((double)ps->x * sx);

	if (sy != 1.0)
		ps->y = rnd_round((double)ps->y * sy);

	pcb_pstk_post(ps);
}

unsigned char *pcb_pstk_get_thermal(pcb_pstk_t *ps, unsigned long lid, rnd_bool_t alloc)
{
	if (ps->thermals.used <= lid) {
		unsigned long oldu = ps->thermals.used;
		if (!alloc)
			return NULL;
		ps->thermals.used = lid+1;
		ps->thermals.shape = realloc(ps->thermals.shape, ps->thermals.used);
		memset(ps->thermals.shape + oldu, 0, ps->thermals.used - oldu);
	}
	return ps->thermals.shape + lid;
}

/*** undoable thermal change ***/
typedef struct {
	pcb_pstk_t *ps;
	rnd_layer_id_t lid;
	unsigned char shape;
} undo_pstk_thermal_t;

static int undo_pstk_thermal_swap(void *udata)
{
	undo_pstk_thermal_t *t = udata;
	unsigned char old, *th = pcb_pstk_get_thermal(t->ps, t->lid, 1);

	if (th != NULL) {
		pcb_board_t *pcb = pcb_data_get_top(t->ps->parent.data);
		pcb_layer_t *layer = pcb_get_layer(pcb->Data, t->lid);

		pcb_poly_restore_to_poly(pcb->Data, PCB_OBJ_PSTK, layer, t->ps);

		old = *th;
		*th = t->shape;
		t->shape = old;

		pcb_poly_clear_from_poly(pcb->Data, PCB_OBJ_PSTK, layer, t->ps);
		pcb_pstk_invalidate_draw(t->ps);
	}
	return 0;
}

static void undo_pstk_thermal_print(void *udata, char *dst, size_t dst_len)
{
	undo_pstk_thermal_t *t = udata;
	rnd_snprintf(dst, dst_len, "pstk_thermal: #%ld %ld %d", t->ps->ID, t->lid, t->shape);
}

static const uundo_oper_t undo_pstk_thermal = {
	core_pstk_cookie,
	NULL, /* free */
	undo_pstk_thermal_swap,
	undo_pstk_thermal_swap,
	undo_pstk_thermal_print
};


void pcb_pstk_set_thermal(pcb_pstk_t *ps, unsigned long lid, unsigned char shape, int undoable)
{
	undo_pstk_thermal_t *t;
	pcb_board_t *pcb = NULL;

	if (undoable) {
		assert(ps->parent_type == PCB_PARENT_DATA);
		pcb = pcb_data_get_top(ps->parent.data);
	}


	if (!undoable || (pcb == NULL)) {
		unsigned char *th = pcb_pstk_get_thermal(ps, lid, 1);
		if (th != NULL)
			*th = shape;
		return;
	}

	t = pcb_undo_alloc(pcb, &undo_pstk_thermal, sizeof(undo_pstk_thermal_t));
	t->ps = ps;
	t->lid = lid;
	t->shape = shape;
	undo_pstk_thermal_swap(t);
}

/*** Undoable instance parameter change ***/

typedef struct {
	long int parent_ID; /* -1 for pcb, positive for a subc */
	long int ID;        /* ID of the padstack */

	rnd_cardinal_t proto;
	rnd_coord_t clearance;
	double rot;
	int xmirror, smirror;
} padstack_change_instance_t;

#define swap(a,b,type) \
	do { \
		type tmp = a; \
		a = b; \
		b = tmp; \
	} while(0)

pcb_data_t *pcb_pstk_data_hack = NULL;

static int undo_change_instance_swap(void *udata)
{
	padstack_change_instance_t *u = udata;
	pcb_data_t *data;
	pcb_pstk_t *ps;

	/* data is either parent subc's data or board's data */
	if (u->parent_ID != -1) {
		pcb_subc_t *subc;
		int n;

		/* Look up the parent subc by ID; on the board, in the hack-data and in all buffers */
		subc = pcb_subc_by_id(PCB->Data, u->parent_ID);
		if ((subc == NULL) && (pcb_pstk_data_hack != NULL))
			subc = pcb_subc_by_id(pcb_pstk_data_hack, u->parent_ID);
		for(n = 0; (subc == NULL) && (n < PCB_MAX_BUFFER); n++)
			subc = pcb_subc_by_id(pcb_buffers[n].Data, u->parent_ID);
		if (subc == NULL) {
			rnd_message(RND_MSG_ERROR, "Can't undo padstack change: parent subc #%ld is not found\n", u->parent_ID);
			return -1;
		}
		data = subc->data;
	}
	else
		data = PCB->Data;

	ps = pcb_pstk_by_id(data, u->ID);
	if (ps == NULL) {
		rnd_message(RND_MSG_ERROR, "Can't undo padstack change: padstack ID #%ld is not available\n", u->ID);
		return -1;
	}

	pcb_poly_restore_to_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps);
	pcb_pstk_invalidate_erase(ps);
	if (ps->parent.data->padstack_tree != NULL)
		rnd_r_delete_entry(ps->parent.data->padstack_tree, (rnd_box_t *)ps);

	swap(ps->proto,      u->proto,     rnd_cardinal_t);
	swap(ps->Clearance,  u->clearance, rnd_coord_t);
	swap(ps->rot,        u->rot,       double);
	swap(ps->xmirror,    u->xmirror,   int);
	swap(ps->smirror,    u->smirror,   int);

	/* force re-render the prototype */
	ps->protoi = -1;
	pcb_pstk_get_tshape(ps);

	pcb_pstk_bbox(ps);
	if (ps->parent.data->padstack_tree != NULL)
		rnd_r_insert_entry(ps->parent.data->padstack_tree, (rnd_box_t *)ps);
	pcb_poly_clear_from_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps);
	pcb_pstk_invalidate_draw(ps);

	return 0;
}

static void undo_change_instance_print(void *udata, char *dst, size_t dst_len)
{
	padstack_change_instance_t *u = udata;
	rnd_snprintf(dst, dst_len, "padstack change: clearance=%$mm rot=%.2f xmirror=%d smirror=%d\n", u->clearance, u->rot, u->xmirror, u->smirror);
}

static const uundo_oper_t undo_pstk_change_instance = {
	core_pstk_cookie,
	NULL, /* free */
	undo_change_instance_swap,
	undo_change_instance_swap,
	undo_change_instance_print
};

int pcb_pstk_change_instance(pcb_pstk_t *ps, rnd_cardinal_t *proto, const rnd_coord_t *clearance, double *rot, int *xmirror, int *smirror)
{
	padstack_change_instance_t *u;
	long int parent_ID;
	pcb_opctx_t ctx;

	if ((proto != NULL) && ((*proto < 0) || (*proto >= ps->parent.data->ps_protos.used)))
		return -1;


	if (ps->ID <= 0) { /* off-board padstack: undo and clipping disabled */
		if (proto != NULL) ps->proto = *proto;
		if (clearance != NULL) ps->Clearance = *clearance;
		if (rot != NULL) ps->rot = *rot;
		if (xmirror != NULL) ps->xmirror = *xmirror;
		if (smirror != NULL) ps->xmirror = *smirror;
		pcb_pstk_bbox(ps);
		return 0;
	}

	switch(ps->parent.data->parent_type) {
		case PCB_PARENT_INVALID:
			/* happens if parent is a buffer: do not undo operations done on the buffer */
			ps->proto = proto ? *proto : ps->proto;
			ps->Clearance = clearance ? *clearance : ps->Clearance;
			ps->rot = rot ? *rot : ps->rot;
			ps->xmirror = xmirror ? *xmirror : ps->xmirror;
			ps->smirror = smirror ? *smirror : ps->smirror;

			/* re-render the prototype so the new shape is generated and cached */
			ps->protoi = -1;
			pcb_pstk_get_tshape(ps);
			return 0;
		case PCB_PARENT_BOARD: parent_ID = -1; break;
		case PCB_PARENT_SUBC: parent_ID = ps->parent.data->parent.subc->ID; break;
		default: return -1;
	}

	ctx.clip.clear = 0;
	ctx.clip.restore = 1;
	pcb_pstkop_clip(&ctx, ps);

	u = pcb_undo_alloc(PCB, &undo_pstk_change_instance, sizeof(padstack_change_instance_t));
	u->parent_ID = parent_ID;
	u->ID = ps->ID;
	u->proto = proto ? *proto : ps->proto;
	u->clearance = clearance ? *clearance : ps->Clearance;
	u->rot = rot ? *rot : ps->rot;
	u->xmirror = xmirror ? *xmirror : ps->xmirror;
	u->smirror = smirror ? *smirror : ps->smirror;

	pcb_pstk_bbox(ps);
	ctx.clip.clear = 1;
	ctx.clip.restore = 0;
	pcb_pstkop_clip(&ctx, ps);


	undo_change_instance_swap(u);

	pcb_undo_inc_serial();
	return 0;
}

rnd_bool pcb_pstk_is_group_empty(pcb_board_t *pcb, rnd_layergrp_id_t gid)
{
	pcb_layergrp_t *g = &pcb->LayerGroups.grp[gid];

	PCB_PADSTACK_LOOP(pcb->Data); {
		rnd_cardinal_t n;
		for(n = 0; n < g->len; n++)
			if (pcb_pstk_shape_at(pcb, padstack, &pcb->Data->Layer[g->lid[n]]))
				return rnd_false;
	}
	PCB_END_LOOP;
	return rnd_true;
}


#include "obj_pstk_op.c"

void pcb_pstk_rotate90(pcb_pstk_t *pstk, rnd_coord_t cx, rnd_coord_t cy, int steps)
{
	pcb_opctx_t ctx;

	ctx.rotate.center_x = cx;
	ctx.rotate.center_y = cy;
	ctx.rotate.number = steps;
	pcb_pstkop_rotate90(&ctx, pstk);
}

void pcb_pstk_rotate(pcb_pstk_t *pstk, rnd_coord_t cx, rnd_coord_t cy, double cosa, double sina, double angle)
{
	pcb_opctx_t ctx;

	ctx.rotate.center_x = cx;
	ctx.rotate.center_y = cy;
	ctx.rotate.angle = angle;
	ctx.rotate.cosa = cosa;
	ctx.rotate.sina = sina;
	pcb_pstkop_rotate(&ctx, pstk);
}

rnd_coord_t obj_pstk_get_clearance(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer)
{
	pcb_pstk_shape_t *shp;

	if (!PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, ps))
		return 0;

	if (ps->Clearance > 0) /* global clarance overrides */
		return ps->Clearance;

	shp = pcb_pstk_shape_at(pcb, ps, layer);
	return shp->clearance;
}

void pcb_pstk_pre(pcb_pstk_t *pstk)
{
	pcb_data_t *data = pstk->parent.data;
	if (data->padstack_tree != NULL)
		rnd_r_delete_entry(data->padstack_tree, (rnd_box_t *)pstk);
	pcb_poly_restore_to_poly(data, PCB_OBJ_PSTK, NULL, pstk);
}

void pcb_pstk_post(pcb_pstk_t *pstk)
{
	pcb_data_t *data = pstk->parent.data;
	if (data->padstack_tree != NULL)
		rnd_r_insert_entry(data->padstack_tree, (rnd_box_t *)pstk);
	pcb_poly_clear_from_poly(data, PCB_OBJ_PSTK, NULL, pstk);
}

rnd_layer_id_t pcb_proto_board_layer_for(const pcb_data_t *data, pcb_layer_type_t mask, pcb_layer_combining_t comb)
{
	rnd_layer_id_t lid;
	const pcb_layer_t *ly;
	for(lid = 0, ly = data->Layer; lid < data->LayerN; lid++,ly++) {
		pcb_layer_type_t typ = pcb_layer_flags_(ly);
		if ((typ == mask) && (ly->comb == comb))
			return lid;
	}
	return -1;
}
