/*
 * inst.c - Instance structures
 *
 * Written 2009-2012 by Werner Almesberger
 * Copyright 2009-2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "util.h"
#include "error.h"
#include "coord.h"
#include "expr.h"
#include "layer.h"
#include "obj.h"
#include "delete.h"
#include "gui_util.h"
#include "gui_status.h"
#include "gui_canvas.h"
#include "gui_tool.h"
#include "gui_meas.h"
#include "gui_inst.h"
#include "gui_frame.h"
#include "gui.h"
#include "inst.h"


struct inst *selected_inst = NULL;
struct bbox active_frame_bbox;
struct pkg *pkgs, *active_pkg, *curr_pkg;
struct pkg *reachable_pkg = NULL;
struct inst *frame_instantiating = NULL;

static struct pkg *prev_pkgs, *prev_reachable_pkg;

static unsigned long active_set = 0;

static struct inst_ops vec_ops;
static struct inst_ops frame_ops;
static struct inst_ops meas_ops;


#define	IS_ACTIVE	((active_set & 1))


/* ----- selective visibility ---------------------------------------------- */


static int show(enum inst_prio prio)
{
	switch (prio) {
	case ip_vec:
	case ip_frame:
		return show_stuff;
	case ip_meas:
		return show_meas;
	default:
		return 1;
	}
}


int bright(const struct inst *inst)
{
	if (!show_bright)
		return 0;
	return inst->ops != &vec_ops && inst->ops != &frame_ops &&
	    inst->ops != &meas_ops;
}


static int show_this(const struct inst *inst)
{
	if (show_all)
		return 1;
	if (inst->ops == &frame_ops && inst->u.frame.ref == active_frame)
		return 1;
	if (!inst->outer)
		return active_frame == frames;
	return inst->outer->u.frame.ref == active_frame;
}


/* ----- selection of items not on the canvas ------------------------------ */


static void *selected_outside = NULL;
static void (*outside_deselect)(void *item);


static void deselect_outside(void)
{
	if (selected_outside && outside_deselect)
		outside_deselect(selected_outside);
	selected_outside = NULL;
}


void inst_select_outside(void *item, void (*deselect)(void *item))
{
	if (item == selected_outside)
		return;
	deselect_outside();
	inst_deselect();
	selected_outside = item;
	outside_deselect = deselect;
}


/* ----- check connectedness ----------------------------------------------- */


/*
 * After an instantiation failure, the instances can get out of sync with the
 * object tree, and attempts to select an item on the canvas can cause accesses
 * to objects that aren't there anymore. So we need to check if we can still
 * reach the corresponding object.
 *
 * Note: even this isn't bullet-proof. Theoretically, we may get a new object
 * in the old place. However, this probably doesn't do any serious damage.
 */


static int inst_connected(const struct inst *inst)
{
	const struct frame *frame;
	const struct vec *vec;
	const struct obj *obj;

	for (frame = frames; frame; frame = frame->next) {
		if (inst->ops == &vec_ops) {
			for (vec = frame->vecs; vec; vec = vec->next)
				if (vec == inst->vec)
					return 1;
		} else {
			for (obj = frame->objs; obj; obj = obj->next)
				if (obj == inst->obj)
					return 1;
		}
	}
	return 0;
}


/* ----- selection --------------------------------------------------------- */


static void inst_select_inst(struct inst *inst)
{
	selected_inst = inst;
	tool_selected_inst(inst);
	gui_frame_select_inst(inst);
	if (inst->ops->select)
		selected_inst->ops->select(inst);
	status_set_icon(get_icon_by_inst(inst));
}


/*
 * @@@ This logic is overly complicated and should be simplified. The general
 * idea was to avoid making unnecessary changes to the user's selections, but
 * that risk doesn't exist. Furthermore, the way activate_item is used, its
 * preconditions aren't met. It works anyway but it could be simpler as a
 * consequence.
 *
 * activate_item tries to activate the path through the frame references,
 * leading to a specific instance. It returns whether this is failed or whether
 * it may have been successful.
 *
 * The initial condition is that we want to activate an item on a frame
 * instance that's not active. Since the frame has been instantiated, there
 * must be a way to activate it. We just have to find out how.
 *
 * The first test eliminates the root frame. If we're at the root frame and
 * still haven't figured out what to do, something is wrong and we give up.
 *
 * The next test skips references that are already right. Since we know that
 * there must be at least one reference that leads elsewhere, and we haven't
 * found it yet, the recursion will tell us whether it can find it at all.
 *
 * Finally, if we've found a mismatch, we correct it. We then try to fix any
 * further mismatches. Since we've made progress, we return 1, even if the
 * other fixes should fail (or reach the root frame).
 *
 */

static int activate_item(struct inst *inst)
{
	if (!inst->outer)
		return 0;
	if (inst->outer->u.frame.ref->active_ref == inst->outer->obj)
		return activate_item(inst->outer);
	inst->outer->u.frame.ref->active_ref = inst->outer->obj;
	activate_item(inst->outer);
	return 1;
}


static int __inst_select(struct coord pos, int tries)
{
	enum inst_prio prio;
	const struct inst *prev;
	struct inst *inst;
	struct inst *first = NULL;	/* first active item */
	struct inst *next = NULL;	/* active item after currently sel. */
	struct inst *any_first = NULL;	/* first item, active or inactive */
	struct inst *any_same_frame = NULL; /* first item on active frame */
	struct frame *frame;
	int best_dist = 0; /* keep gcc happy */
	int select_next;
	int dist, i;

	if (!tries) {
		fprintf(stderr, "__inst_select: tries exhausted\n");
		return 0;
	}
	prev = selected_inst;
	deselect_outside();
	edit_nothing();
	if (selected_inst) {
		gui_frame_deselect_inst(selected_inst);
		tool_selected_inst(NULL);
	}
	inst_deselect();
	select_next = 0;
	FOR_INST_PRIOS_DOWN(prio) {
		if (!show(prio))
			continue;
		FOR_ALL_INSTS(i, prio, inst) {
			if (!show_this(inst))
				continue;
			if (!inst->ops->distance)
				continue;
			if (!inst_connected(inst))
				continue;
			dist = inst->ops->distance(inst, pos, draw_ctx.scale);
			if (dist >= 0) {
				if (!any_first)
					any_first = inst;
				if (!any_same_frame && inst->outer &&
				    inst->outer->u.frame.ref == active_frame)
					any_same_frame = inst;
				if (!inst->active)
					continue;
				if (!first)
					first = inst;
				if (!next && select_next)
					next = inst;
				if (inst == prev)
					select_next = 1;
				if (!selected_inst || best_dist > dist) {
					selected_inst = inst;
					best_dist = dist;
				}
			}
		}
	}
	if (select_next) {
		selected_inst = next ? next : first;
		goto selected;
	}
	if (selected_inst)
		goto selected;

	/* give vectors a second chance */

	if (show_stuff) {
		FOR_ALL_INSTS(i, ip_vec, inst) {
			if (!inst->active)
				continue;
			if (!inst_connected(inst))
				continue;
			dist = gui_dist_vec_fallback(inst, pos, draw_ctx.scale);
			if (dist >= 0 && (!selected_inst || best_dist > dist)) {
				selected_inst = inst;
				best_dist = dist;
			}
		}

		if (selected_inst)
			goto selected;
	}

	if (!show_all)
		return 0;

	if (any_same_frame) {
		activate_item(any_same_frame);
		search_inst(any_same_frame);
		instantiate();
		change_world();
		return __inst_select(pos, tries-1);
	}
	if (any_first) {
		frame = any_first->outer ? any_first->outer->u.frame.ref : NULL;
		if (frame != active_frame) {
			select_frame(frame);
			return __inst_select(pos, tries-1);
		}
	}

	return 0;

selected:
	inst_select_inst(selected_inst);
	return 1;
}


int inst_select(struct coord pos)
{
	/*
	 * We shouldn't need more than 2 tries to select any item, so 5 is more
	 * than enough. This can still fail, but then it would for any number
	 * of tries.
	 */
	return __inst_select(pos, 5);
}


struct inst *inst_find_point(struct coord pos)
{
	struct inst *inst, *found;
	int best_dist = 0; /* keep gcc happy */
	int dist, i;

	found = NULL;
	FOR_ALL_INSTS(i, ip_frame, inst) {
		if (!inst->u.frame.active)
			continue;
		dist = gui_dist_frame_eye(inst, pos, draw_ctx.scale);
		if (dist >= 0 && (!found || best_dist > dist)) {
			found = inst;
			best_dist = dist;
		}
	}
	if (found)
		return found;

	FOR_ALL_INSTS(i, ip_vec, inst) {
		if (!inst->active || !inst->ops->distance)
			continue;
		dist = inst->ops->distance(inst, pos, draw_ctx.scale);
		if (dist >= 0 && (!found || best_dist > dist)) {
			found = inst;
			best_dist = dist;
		}
	}
	return found;
}


int inst_find_point_selected(struct coord pos, struct inst **res)
{
	struct vec **anchors[3];
	int n, best_i, i;
	struct inst *best = NULL;
	struct inst *inst;
	int d_min, d, j;

	assert(selected_inst);
	n = inst_anchors(selected_inst, anchors);
	for (i = 0; i != n; i++) {
		if (*anchors[i]) {
			FOR_ALL_INSTS(j, ip_vec, inst) {
				if (inst->vec != *anchors[i])
					continue;
				d = gui_dist_vec(inst, pos, draw_ctx.scale);
				if (d != -1 && (!best || d < d_min)) {
					best = inst;
					best_i = i;
					d_min = d;
				}
			}
		} else {
			FOR_ALL_INSTS(j, ip_frame, inst) {
				if (inst != selected_inst->outer)
					continue;
				d = gui_dist_frame(inst, pos, draw_ctx.scale);
				if (d != -1 && (!best || d < d_min)) {
					best = inst;
					best_i = i;
					d_min = d;
				}
			}
		}
	}
	if (!best)
		return -1;
	if (res)
		*res = best;
	return best_i;
}


struct coord inst_get_point(const struct inst *inst)
{
	if (inst->ops == &vec_ops)
		return inst->u.vec.end;
	if (inst->ops == &frame_ops)
		return inst->base;
	abort();
}


struct vec *inst_get_vec(const struct inst *inst)
{
	if (inst->ops == &vec_ops)
		return inst->vec;
	if (inst->ops == &frame_ops)
		return NULL;
	abort();
}


int inst_anchors(struct inst *inst, struct vec ***anchors)
{
	if (inst->vec) {
		anchors[0] = &inst->vec->base;
		return 1;
	}
	return obj_anchors(inst->obj, anchors);
}


void inst_deselect(void)
{
	if (selected_inst) {
		tool_selected_inst(NULL);
		gui_frame_deselect_inst(selected_inst);
	}
	deselect_outside();
	status_set_type_x(NULL, "");
	status_set_type_y(NULL, "");
	status_set_type_entry(NULL, "");
	status_set_name(NULL, "");
	status_set_x(NULL, "");
	status_set_y(NULL, "");
	status_set_r(NULL, "");
	status_set_angle(NULL, "");
	selected_inst = NULL;
	edit_nothing();
	refresh_pos();
	status_set_icon(NULL);
}


/* ----- select instance by vector/object ---------------------------------- */


static void vec_edit(struct vec *vec);
static void obj_edit(struct obj *obj);


void inst_select_vec(struct vec *vec)
{
	struct inst *inst;
	int i;

	if (vec->frame != active_frame)
		select_frame(vec->frame);
	FOR_ALL_INSTS(i, ip_vec, inst)
		if (inst->vec == vec && inst->active) {
			inst_deselect();
			inst_select_inst(inst);
			return;
		}
	vec_edit(vec);
}


void inst_select_obj(struct obj *obj)
{
	enum inst_prio prio;
	struct inst *inst;
	int i;

	if (obj->frame != active_frame)
		select_frame(obj->frame);
	FOR_INST_PRIOS_DOWN(prio)
		FOR_ALL_INSTS(i, prio, inst)
			if (inst->obj && inst->obj == obj && inst->active)
				goto found;
	obj_edit(obj);
	return;

found:
	inst_deselect();
	inst_select_inst(inst);
}


/* ----- common status reporting ------------------------------------------- */


static void rect_status(struct coord a, struct coord b, unit_type width,
    int rounded)
{
	const char *tip;
	struct coord d = sub_vec(b, a);
	double r;
	unit_type diag;

	status_set_xy(d);
	tip = "Angle of diagonal";
	if (!d.x && !d.y) {
		status_set_angle(tip, "a = 0 deg");
	} else {
		status_set_angle(tip, "a = %3.1f deg", theta(a, b));
	}
	if (d.x < 0)
		d.x = -d.x;
	if (d.y < 0)
		d.y = -d.y;
	diag = hypot(d.x, d.y);
	if (rounded) {
		/*
		 * Only consider the part of the diagonal that is on the pad
		 * surface.
		 *
		 * The circle: (x-r)^2+(y-r)^2 = r^2
		 * The diagonal: x = t*cos(theta), y = t*sin(theta)
		 *
		 * t is the distance from the corner of the surrounding
		 * rectangle to the half-circle:
		 *
		 * t = 2*r*(s+c-sqrt(2*s*c))
		 *
		 * With s = sin(theta) and c = cos(theta).
		 *
		 * Since d.x = diag*cos(theta), we don't need to calculate the
		 * sinus and cosinus but can use d.x and d.y directly.
		 */
		r = (d.x > d.y ? d.y : d.x)/2;
		diag -= 2*r*(d.x+d.y-sqrt(2*d.x*d.y))/diag;
	}
	set_with_units(status_set_r, "d = ", diag, "Length of diagonal");
	if (width != -1) {
		status_set_type_entry(NULL, "width =");
		set_with_units(status_set_name, "", width, "Line width");
	}
}


static void rect_status_sort(struct coord a, struct coord b, unit_type width,
    int rounded)
{
	sort_coord(&a, &b);
	rect_status(a, b, width, rounded);
}


/* ----- helper functions for instance creation ---------------------------- */


static void update_bbox(struct bbox *bbox, struct coord coord)
{
	if (bbox->min.x > coord.x)
		bbox->min.x = coord.x;
	if (bbox->max.x < coord.x)
		bbox->max.x = coord.x;
	if (bbox->min.y > coord.y)
		bbox->min.y = coord.y;
	if (bbox->max.y < coord.y)
		bbox->max.y = coord.y;
}


static void propagate_bbox(const struct inst *inst)
{
	struct inst *frame = frame_instantiating ?
	    frame_instantiating : curr_pkg->insts[ip_frame];

	update_bbox(&frame->bbox, inst->bbox.min);
	update_bbox(&frame->bbox, inst->bbox.max);

	if (curr_pkg->bbox.min.x || curr_pkg->bbox.min.y ||
	    curr_pkg->bbox.max.x || curr_pkg->bbox.max.y) {
		update_bbox(&curr_pkg->bbox, inst->bbox.min);
		update_bbox(&curr_pkg->bbox, inst->bbox.max);
	} else {
		curr_pkg->bbox = inst->bbox;
	}
}


static void grow_bbox_by_width(struct bbox *bbox, unit_type width)
{
	bbox->min.x -= width/2;
	bbox->min.y -= width/2;
	bbox->max.x += width/2;
	bbox->max.y += width/2;
}


static int zero_sized(struct coord a, struct coord b, const char *fmt,
    const char *arg)
{
	if (a.x == b.x && a.y == b.y) {
		fail(fmt, "zero-sized", arg);
		return 1;
	}
	if (a.x == b.x) {
		fail(fmt, "zero-width", arg);
		return 1;
	}
	if (a.y == b.y) {
		fail(fmt, "zero-height", arg);
		return 1;
	}
	return 0;
}


static struct inst *add_inst(const struct inst_ops *ops, enum inst_prio prio,
    struct coord base)
{
	struct inst *inst;

	inst = alloc_type(struct inst);
	inst->ops = ops;
	inst->prio = prio;
	inst->vec = NULL;
	inst->obj = NULL;
	inst->base = inst->bbox.min = inst->bbox.max = base;
	inst->outer = frame_instantiating;
	inst->active = IS_ACTIVE;
	inst->next = NULL;
	*curr_pkg->next_inst[prio] = inst;
	curr_pkg->next_inst[prio] = &inst->next;
	return inst;
}


/* ----- vec --------------------------------------------------------------- */


static int validate_vec_name(const char *s, void *ctx)
{
	struct vec *vec = ctx;
	const struct vec *walk;

	if (!is_id(s))
		return 0;
	for (walk = vec->frame->vecs; walk; walk = walk->next)
		if (walk->name && !strcmp(walk->name, s))
			return 0;
	return 1;
}


static void vec_edit(struct vec *vec)
{
	edit_x(&vec->x, "X distance");
	edit_y(&vec->y, "Y distance");
	edit_unique_null(&vec->name, validate_vec_name, vec, "Vector name");
}


static void vec_op_select(struct inst *self)
{
	status_set_type_entry(NULL, "ref =");
	status_set_name("Vector reference (name)",
	    "%s", self->vec->name ? self->vec->name : "");
	rect_status(self->base, self->u.vec.end, -1, 0);
	vec_edit(self->vec);
}


/*
 * @@@ The logic of gui_find_point_vec isn't great. Instead of selecting a
 * point and then filtering, we should filter the candidates, so that a point
 * that's close end eligible can win against one that's closer but not
 * eligible.
 */

static struct inst *find_point_vec(struct inst *self, struct coord pos)
{
	struct inst *inst;
	const struct vec *vec;

	inst = inst_find_point(pos);
	if (!inst)
		return NULL;
	if (inst->ops == &frame_ops)
		return inst;
	for (vec = inst->vec; vec; vec = vec->base)
		if (vec == self->vec)
		return NULL;
	return inst;
}


/*
 * When instantiating and when dumping, we assume that bases appear in the
 * frame->vecs list before vectors using them. A move may change this order.
 * We therefore have to sort the list after the move.
 *
 * Since the list is already ordered, cleaning it up is just O(n).
 */


static void do_move_to_vec(struct inst *inst, struct inst *to, int i)
{
	struct vec *to_vec = inst_get_vec(to);
	struct vec *vec = inst->vec;
	struct frame *frame = vec->frame;
	struct vec *v, **anchor, **walk;

	assert(!i);
	vec->base = to_vec;

	/*
	 * Mark the vector that's being rebased and all vectors that
	 * (recursively) depend on it.
	 *
	 * We're mainly interested in the range between the vector being moved
	 * and the new base. If the vector follows the base, the list is
	 * already in the correct order and nothing needs moving.
	 */
	for (v = frame->vecs; v != vec; v = v->next)
		v->mark = 0;
	vec->mark = 1;
	for (v = vec->next; v && v != to_vec; v = v->next)
		v->mark = v->base ? v->base->mark : 0;
	if (!v)
		return;

	/*
	 * All the marked vectors appearing on the list before the new base
	 * are moved after the new base, preserving their order.
	 *
	 * Start at frame->vecs, not "vec", so that we move the the vector
	 * being rebased as well.
	 */
	anchor = &to_vec->next;
	walk = &frame->vecs;
	while (*walk != to_vec) {
		v = *walk;
		if (!v->mark) {
			walk = &v->next;
		} else {
			*walk = v->next;
			v->next = *anchor;
			*anchor = v;
			anchor = &v->next;
		}
	}
}


static struct inst_ops vec_ops = {
	.draw		= gui_draw_vec,
	.hover		= gui_hover_vec,
	.distance	= gui_dist_vec,
	.select		= vec_op_select,
	.find_point	= find_point_vec,
	.draw_move	= draw_move_vec,
	.do_move_to	= do_move_to_vec,
};


int inst_vec(struct vec *vec, struct coord base)
{
	struct inst *inst;

	inst = add_inst(&vec_ops, ip_vec, base);
	inst->vec = vec;
	inst->u.vec.end = vec->pos;
	find_inst(inst);
	update_bbox(&inst->bbox, vec->pos);
	propagate_bbox(inst);
	return 1;
}


/* ----- line -------------------------------------------------------------- */


static void obj_line_edit(struct obj *obj)
{
	edit_dist_expr(&obj->u.line.width, "Line width");
}


static void line_op_select(struct inst *self)
{
	rect_status_sort(self->base, self->u.rect.end, self->u.rect.width, 0);
	obj_line_edit(self->obj);
}


static struct inst_ops line_ops = {
	.draw		= gui_draw_line,
	.distance	= gui_dist_line,
	.select		= line_op_select,
	.draw_move	= draw_move_line,
};


int inst_line(struct obj *obj, struct coord a, struct coord b, unit_type width)
{
	struct inst *inst;

	inst = add_inst(&line_ops, ip_line, a);
	inst->obj = obj;
	inst->u.rect.end = b;
	inst->u.rect.width = width;
	find_inst(inst);
	update_bbox(&inst->bbox, b);
	grow_bbox_by_width(&inst->bbox, width);
	propagate_bbox(inst);
	return 1;
}


/* ----- rect -------------------------------------------------------------- */


static void obj_rect_edit(struct obj *obj)
{
	edit_dist_expr(&obj->u.rect.width, "Line width");
}


static void rect_op_select(struct inst *self)
{
	rect_status_sort(self->base, self->u.rect.end, self->u.rect.width, 0);
	obj_rect_edit(self->obj);
}


static struct inst_ops rect_ops = {
	.draw		= gui_draw_rect,
	.distance	= gui_dist_rect,
	.select		= rect_op_select,
	.draw_move	= draw_move_rect,
};


int inst_rect(struct obj *obj, struct coord a, struct coord b, unit_type width)
{
	struct inst *inst;

	inst = add_inst(&rect_ops, ip_rect, a);
	inst->obj = obj;
	inst->u.rect.end = b;
	inst->u.rect.width = width;
	find_inst(inst);
	update_bbox(&inst->bbox, b);
	grow_bbox_by_width(&inst->bbox, width);
	propagate_bbox(inst);
	return 1;
}


/* ----- pad / rpad -------------------------------------------------------- */


static int validate_pad_name(const char *s, void *ctx)
{
	char *tmp;

	status_begin_reporting();
	tmp = expand(s, NULL);
	if (!tmp)
		return 0;
	free(tmp);
	return 1;
}


static void obj_pad_edit(struct obj *obj)
{
	edit_pad_type(&obj->u.pad.type);
	edit_name(&obj->u.pad.name, validate_pad_name, NULL,
	    "Pad name (template)");
}


static void pad_op_select(struct inst *self)
{
	status_set_type_entry(NULL, "label =");
	status_set_name("Pad name (actual)", "%s", self->u.pad.name);
	rect_status_sort(self->base, self->u.pad.other, -1, 0);
	obj_pad_edit(self->obj);
}


static struct inst_ops pad_ops = {
	.draw		= gui_draw_pad,
	.distance	= gui_dist_pad,
	.select		= pad_op_select,
	.draw_move	= draw_move_pad,
};


static void rpad_op_select(struct inst *self)
{
	status_set_type_entry(NULL, "label =");
	status_set_name("Pad name (actual)", "%s", self->u.pad.name);
	rect_status_sort(self->base, self->u.pad.other, -1, 1);
	obj_pad_edit(self->obj);
}


static struct inst_ops rpad_ops = {
	.draw		= gui_draw_rpad,
	.distance	= gui_dist_pad, /* @@@ */
	.select		= rpad_op_select,
	.draw_move	= draw_move_rpad,
};


int inst_pad(struct obj *obj, const char *name, struct coord a, struct coord b)
{
	struct inst *inst;

	if (zero_sized(a, b, "%s pad \"%s\"", name))
		return 0;
	inst = add_inst(obj->u.pad.rounded ? &rpad_ops : &pad_ops,
	    obj->u.pad.type == pt_normal || obj->u.pad.type == pt_bare ||
	    obj->u.pad.type == pt_trace ?
	    ip_pad_copper : ip_pad_special, a);
	inst->obj = obj;
	inst->u.pad.name = stralloc(name);
	inst->u.pad.other = b;
	inst->u.pad.layers = pad_type_to_layers(obj->u.pad.type);
	find_inst(inst);
	update_bbox(&inst->bbox, b);
	propagate_bbox(inst);
	return 1;
}


/* ----- hole -------------------------------------------------------------- */


static void hole_op_select(struct inst *self)
{
	rect_status_sort(self->base, self->u.hole.other, -1, 1);
}


static struct inst_ops hole_ops = {
	.draw		= gui_draw_hole,
	.distance	= gui_dist_hole,
	.select		= hole_op_select,
	.draw_move	= draw_move_hole,
};


int inst_hole(struct obj *obj, struct coord a, struct coord b)
{
	struct inst *inst;

	if (zero_sized(a, b, "%s hole", NULL))
		return 0;
	inst = add_inst(&hole_ops, ip_hole, a);
	inst->obj = obj;
	inst->u.hole.other = b;
	inst->u.hole.layers = mech_hole_layers();
	find_inst(inst);
	update_bbox(&inst->bbox, b);
	propagate_bbox(inst);
	return 1;
}


/* ----- arc --------------------------------------------------------------- */


static void obj_arc_edit(struct obj *obj)
{
	edit_dist_expr(&obj->u.arc.width, "Line width");
}


static void arc_op_select(struct inst *self)
{
	status_set_xy(self->base);
	status_set_angle("Angle", "a = %3.1f deg",
	    self->u.arc.a1 == self->u.arc.a2 ? 360 :
	    self->u.arc.a2-self->u.arc.a1);
	set_with_units(status_set_r, "r = ", self->u.arc.r, "Radius");
	status_set_type_entry(NULL, "width =");
	set_with_units(status_set_name, "", self->u.arc.width, "Line width");
	obj_arc_edit(self->obj);
}


static struct inst_ops arc_ops = {
	.draw		= gui_draw_arc,
	.distance	= gui_dist_arc,
	.select		= arc_op_select,
	.draw_move	= draw_move_arc,
	.do_move_to	= do_move_to_arc,
};


int inst_arc(struct obj *obj, struct coord center, struct coord start,
    struct coord end, unit_type width)
{
	struct inst *inst;
	double r, a1, a2;

	a1 = theta(center, start);
	a2 = theta(center, end);
	inst = add_inst(&arc_ops,
	    fmod(a1, 360) == fmod(a2, 360) ? ip_circ : ip_arc, center);
	inst->obj = obj;
	r = hypot(start.x-center.x, start.y-center.y);
	inst->u.arc.r = r;
	inst->u.arc.a1 = a1;
	inst->u.arc.a2 = a2;
	inst->u.arc.width = width;
	inst->bbox.min.x = center.x-r;
	inst->bbox.max.x = center.x+r;
	inst->bbox.min.y = center.y-r;
	inst->bbox.max.y = center.y+r;
	find_inst(inst);
	grow_bbox_by_width(&inst->bbox, width);
	propagate_bbox(inst);
	return 1;
}


/* ----- measurement ------------------------------------------------------- */


static void obj_meas_edit(struct obj *obj)
{
	edit_dist_expr(&obj->u.meas.offset, "Measurement line offset");
}


static void meas_op_select(struct inst *self)
{
	rect_status_sort(self->base, self->u.meas.end, -1, 0);
	status_set_type_entry(NULL, "offset =");
	set_with_units(status_set_name, "", self->u.meas.offset,
	    "Measurement line offset");
	obj_meas_edit(self->obj);
}


static struct inst_ops meas_ops = {
	.draw		= gui_draw_meas,
	.distance	= gui_dist_meas,
	.select		= meas_op_select,
	.begin_drag_move= begin_drag_move_meas,
	.find_point	= find_point_meas_move,
	.draw_move	= draw_move_meas,
	.end_drag_move	= end_drag_move_meas,
	.do_move_to	= do_move_to_meas,
};


struct inst *find_meas_hint(const struct obj *obj)
{
	struct inst *inst;

	for (inst = curr_pkg->insts[ip_meas]; inst; inst = inst->next)
		if (inst->obj == obj)
			break;
	return inst;
}


int inst_meas(struct obj *obj, struct coord from, struct coord to)
{
	struct inst *inst;
	struct coord a1, b1;

	inst = find_meas_hint(obj);
	assert(inst);
	inst->base = from;
	inst->u.meas.end = to;
	inst->u.meas.valid = 1;
	/* @@@ we still need to consider the text size as well */
	update_bbox(&inst->bbox, from);
	update_bbox(&inst->bbox, to);
	project_meas(inst, &a1, &b1);
	update_bbox(&inst->bbox, a1);
	update_bbox(&inst->bbox, b1);
	propagate_bbox(inst);
	return 1;
}


void inst_meas_hint(struct obj *obj, unit_type offset)
{
	static const struct coord zero = { 0, 0 };
	struct inst *inst;

	inst = find_meas_hint(obj);
	if (inst)
		return;
	inst = add_inst(&meas_ops, ip_meas, zero);
	inst->obj = obj;
	inst->u.meas.offset = offset;
	inst->u.meas.valid = 0;
	inst->active = 1; /* measurements are always active */
}


/* ----- direct editing of objects ----------------------------------------- */


static void obj_edit(struct obj *obj)
{
	switch (obj->type) {
	case ot_frame:
		break;
	case ot_line:
		obj_line_edit(obj);
		break;
	case ot_rect:
		obj_rect_edit(obj);
		break;
	case ot_arc:
		obj_arc_edit(obj);
		break;
	case ot_pad:
		obj_pad_edit(obj);
		break;
	case ot_meas:
		obj_meas_edit(obj);
		break;
	default:
		abort();
	}
}


/* ----- active instance --------------------------------------------------- */


void inst_begin_active(int active)
{
	active_set = (active_set << 1) | active;
}


void inst_end_active(void)
{
	active_set >>= 1;
}


/* ----- frame ------------------------------------------------------------- */


static void frame_op_select(struct inst *self)
{
	rect_status(self->bbox.min, self->bbox.max, -1, 0);
	status_set_type_entry(NULL, "name =");
	status_set_name("Frame name", "%s", self->u.frame.ref->name);
}


static struct inst_ops frame_ops = {
	.draw		= gui_draw_frame,
	.hover		= gui_hover_frame,
	.distance	= gui_dist_frame,
	.select		= frame_op_select,
	.draw_move	= draw_move_frame,
};


void inst_begin_frame(struct obj *obj, struct frame *frame,
    struct coord base, int active, int is_active_frame)
{
	struct inst *inst;

	inst = add_inst(&frame_ops, ip_frame, base);
	inst->obj = obj;
	inst->u.frame.ref = frame;
	inst->u.frame.active = is_active_frame;
	inst->active = active;
	find_inst(inst);
	frame_instantiating = inst;
}


void inst_end_frame(const struct frame *frame)
{
	struct inst *inst = frame_instantiating;

	frame_instantiating = frame_instantiating->outer;
	if (frame_instantiating)
		propagate_bbox(inst);
	if (inst->u.frame.active && frame == active_frame)
		active_frame_bbox = inst->bbox;
}


/* ----- package ----------------------------------------------------------- */


void inst_select_pkg(const char *name, int active)
{
	struct pkg **pkg;
	enum inst_prio prio;

	name = name ? unique(name) : NULL;
	for (pkg = &pkgs; *pkg; pkg = &(*pkg)->next)
		if ((*pkg)->name == name)
			break;
	if (!*pkg) {
		*pkg = zalloc_type(struct pkg);
		(*pkg)->name = name;
		FOR_INST_PRIOS_UP(prio)
			(*pkg)->next_inst[prio] = &(*pkg)->insts[prio];
		(*pkg)->samples =
		    zalloc_size(sizeof(struct sample *)*n_samples);
		(*pkg)->n_samples = n_samples;
	}
	curr_pkg = *pkg;
	if (active && name)
		reachable_pkg = curr_pkg;
}


/* ----- misc. ------------------------------------------------------------- */


struct bbox inst_get_bbox(const struct pkg *pkg)
{
	if (pkg)
		return pkg->bbox;
	else
		return pkgs->insts[ip_frame]->bbox;
}


static void cleanup_inst(enum inst_prio prio, const struct inst *inst)
{
	switch (prio) {
	case ip_pad_copper:
	case ip_pad_special:
		free(inst->u.pad.name);
		break;
	default:
		break;
	}
}


static void free_pkgs(struct pkg *pkg)
{
	enum inst_prio prio;
	struct pkg *next_pkg;
	struct inst *inst, *next;

	while (pkg) {
		next_pkg = pkg->next;
		FOR_INST_PRIOS_UP(prio)
			for (inst = pkg->insts[prio]; inst; inst = next) {
				next = inst->next;
				cleanup_inst(prio, inst);
				free(inst);
			}
		reset_samples(pkg->samples, pkg->n_samples);
		free(pkg->samples);
		free(pkg);
		pkg = next_pkg;
	}
}


void inst_start(void)
{
	static struct bbox bbox_zero = { { 0, 0 }, { 0, 0 }};

	active_frame_bbox = bbox_zero;
	prev_pkgs = pkgs;
	prev_reachable_pkg = reachable_pkg;
	pkgs = NULL;
	reachable_pkg = NULL;
	inst_select_pkg(NULL, 0);
	curr_pkg = pkgs;
	frame_instantiating = NULL;
}


void inst_commit(void)
{
	struct pkg *pkg;

	if (active_pkg) {
		for (pkg = pkgs; pkg && pkg->name != active_pkg->name;
		    pkg = pkg->next);
		active_pkg = pkg;
	}
	if (!active_pkg)
		active_pkg = pkgs->next;
	free_pkgs(prev_pkgs);
}


void inst_revert(void)
{
	free_pkgs(pkgs);
	pkgs = prev_pkgs;
	reachable_pkg = prev_reachable_pkg;
}


void inst_draw(void)
{
	enum inst_prio prio;
	struct inst *inst;
	int i;

	FOR_INST_PRIOS_UP(prio)
		FOR_ALL_INSTS(i, prio, inst)
			if (show_this(inst))
				if (show(prio) && !inst->active &&
				    inst->ops->draw)
					inst->ops->draw(inst);
	FOR_INST_PRIOS_UP(prio)
		FOR_ALL_INSTS(i, prio, inst)
			if (show(prio) && prio != ip_frame && inst->active &&
			    inst != selected_inst && inst->ops->draw)
				inst->ops->draw(inst);
	if (show_stuff)
		FOR_ALL_INSTS(i, ip_frame, inst)
			if (inst->active && inst != selected_inst &&
			    inst->ops->draw)
				inst->ops->draw(inst);
	if (selected_inst && selected_inst->ops->draw)
		selected_inst->ops->draw(selected_inst);
}


void inst_highlight_vecs(int (*pick)(struct inst *inst, void *user), void *user)
{
	struct inst *inst;
	int i;

	FOR_ALL_INSTS(i, ip_vec, inst) {
		inst->u.vec.highlighted = pick(inst, user);
		if (inst->u.vec.highlighted)
			gui_highlight_vec(inst);
	}
}


struct inst *inst_find_vec(struct coord pos,
    int (*pick)(struct inst *inst, void *user), void *user)
{
	struct inst *inst, *found;
	int best_dist = 0; /* keep gcc happy */
	int dist, i;

	found = NULL;
	FOR_ALL_INSTS(i, ip_vec, inst) {
		if (!inst->ops->distance)
			continue;
		dist = inst->ops->distance(inst, pos, draw_ctx.scale);
		if (dist < 0 || (found && best_dist <= dist))
			continue;
		if (!pick(inst, user))
			continue;
		found = inst;
		best_dist = dist;
	}
	return found;
}


struct inst *insts_ip_vec(void)
{
	return active_pkg->insts[ip_vec];
}


struct pix_buf *inst_draw_move(struct inst *inst, struct coord pos, int i)
{
	return inst->ops->draw_move(inst, pos, i);
}


int inst_do_move_to(struct inst *inst, struct inst *to, int i)
{
	if (!inst->ops->do_move_to)
		return 0;
	inst->ops->do_move_to(inst, to, i);
	return 1;
}


struct pix_buf *inst_hover(struct inst *inst)
{
	if (!inst->ops->hover)
		return NULL;
	return inst->ops->hover(inst);
}


void inst_begin_drag_move(struct inst *inst, int i)
{
	if (inst->ops->begin_drag_move)
		inst->ops->begin_drag_move(inst, i);
}


void inst_delete(struct inst *inst)
{
	if (inst->ops == &vec_ops)
		delete_vec(inst->vec);
	else
		delete_obj(inst->obj);
}
