/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  (this file is based on PCB, interactive printed circuit board design)
 *  Copyright (C) 1994,1995,1996 Thomas Nau
 *  Copyright (C) 2020 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., 31 Milk Street, # 960789 Boston, MA 02196 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")
 *
 */

/* Drawing primitive: line segment */

#include "config.h"
#include <setjmp.h>

#include "undo.h"
#include "board.h"
#include "data.h"
#include "move.h"
#include "search.h"
#include "polygon.h"
#include "conf_core.h"
#include "thermal.h"
#include <librnd/core/compat_misc.h>
#include "rotate.h"
#include <librnd/hid/hid_inlines.h>
#include <librnd/core/misc_util.h>

#include "obj_line.h"
#include "obj_line_op.h"

#include "obj_subc_parent.h"
#include "obj_hash.h"

#include "draw_wireframe.h"
#include "obj_line_draw.h"
#include "obj_rat_draw.h"
#include "obj_pstk_draw.h"

TODO("ui_layer parent fix: remove this")
#include "layer_ui.h"

/**** allocation ****/

void pcb_line_reg(pcb_layer_t *layer, pcb_line_t *line)
{
	linelist_append(&layer->Line, line);
	PCB_SET_PARENT(line, layer, layer);

	if (pcb_is_uilayer(layer)) {
		pcb_obj_id_reg(layer->parent.data, line);
/* can not register points because they do not have the common object header
		pcb_obj_id_reg(layer->parent.data, &line->Point1);
		pcb_obj_id_reg(layer->parent.data, &line->Point2);*/
	}
	else if (layer->parent_type == PCB_PARENT_DATA) {
		pcb_obj_id_reg(layer->parent.data, line);
/* can not register points because they do not have the common object header
		pcb_obj_id_reg(layer->parent.data, &line->Point1);
		pcb_obj_id_reg(layer->parent.data, &line->Point2);*/
	}
}

void pcb_line_unreg(pcb_line_t *line)
{
	pcb_layer_t *layer = line->parent.layer;
	assert(line->parent_type == PCB_PARENT_LAYER);
	linelist_remove(line);
	if (pcb_is_uilayer(layer)) {
		pcb_obj_id_del(layer->parent.data, line);
/*		pcb_obj_id_del(layer->parent.data, &line->Point1);
		pcb_obj_id_del(layer->parent.data, &line->Point2);*/
	}
	else {
		assert(layer->parent_type == PCB_PARENT_DATA);
		pcb_obj_id_del(layer->parent.data, line);
/*		pcb_obj_id_del(layer->parent.data, &line->Point1);
		pcb_obj_id_del(layer->parent.data, &line->Point2);*/
	}
	PCB_CLEAR_PARENT(line);
}

pcb_line_t *pcb_line_alloc_id(pcb_layer_t *layer, long int id)
{
	pcb_line_t *new_obj;

	new_obj = calloc(sizeof(pcb_line_t), 1);
	new_obj->ID = id;
	new_obj->type = PCB_OBJ_LINE;
	new_obj->Attributes.post_change = pcb_obj_attrib_post_change;

	pcb_line_reg(layer, new_obj);

	return new_obj;
}

pcb_line_t *pcb_line_alloc(pcb_layer_t *layer)
{
	return pcb_line_alloc_id(layer, pcb_create_ID_get());
}

void pcb_line_free(pcb_line_t *line)
{
	if ((line->parent.layer != NULL) && (line->parent.layer->line_tree != NULL))
		rnd_rtree_delete(line->parent.layer->line_tree, line, (rnd_rtree_box_t *)line);
	pcb_attribute_free(&line->Attributes);
	pcb_line_unreg(line);
	pcb_obj_common_free((pcb_any_obj_t *)line);
	free(line);
}


/**** utility ****/

static const char core_line_cookie[] = "core-line";

typedef struct {
	pcb_line_t *line; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	rnd_coord_t Thickness, Clearance;
	rnd_point_t Point1, Point2;
} undo_line_geo_t;

static int undo_line_geo_swap(void *udata)
{
	undo_line_geo_t *g = udata;
	pcb_layer_t *layer = g->line->parent.layer;

	if (layer->line_tree != NULL)
		rnd_rtree_delete(layer->line_tree, g->line, (rnd_rtree_box_t *)g->line);
	pcb_poly_restore_to_poly(layer->parent.data, PCB_OBJ_LINE, layer, g->line);

	rnd_swap(rnd_point_t, g->Point1, g->line->Point1);
	rnd_swap(rnd_point_t, g->Point2, g->line->Point2);
	rnd_swap(rnd_coord_t, g->Thickness, g->line->Thickness);
	rnd_swap(rnd_coord_t, g->Clearance, g->line->Clearance);

	pcb_line_bbox(g->line);
	if (layer->line_tree != NULL)
		rnd_rtree_insert(layer->line_tree, g->line, (rnd_rtree_box_t *)g->line);
	pcb_poly_clear_from_poly(layer->parent.data, PCB_OBJ_LINE, layer, g->line);

	return 0;
}

static void undo_line_geo_print(void *udata, char *dst, size_t dst_len)
{
	rnd_snprintf(dst, dst_len, "line geo");
}

static const uundo_oper_t undo_line_geo = {
	core_line_cookie,
	NULL,
	undo_line_geo_swap,
	undo_line_geo_swap,
	undo_line_geo_print
};



struct line_info {
	pcb_line_t lin;
	pcb_line_t modpts, *remove_line; /* if remove line is not NULL, remove that line and modify new line coords to modpts */
	unsigned skip_new:1;
	jmp_buf env;
};

RND_INLINE pcb_line_merge_t can_merge_lines(const pcb_line_t *old_line, const pcb_line_t *new_line, pcb_line_t *out)
{
	int ov_n1o, ov_n2o, ov_o1n, ov_o2n;
	pcb_line_t tmp;

	if (!conf_core.editor.trace_auto_merge)
		return PCB_LINMER_NONE;

	/* do not merge to subc parts or terminals */
	if ((pcb_obj_parent_subc((pcb_any_obj_t *)old_line) != NULL) || (old_line->term != NULL))
		return PCB_LINMER_NONE;

	/* 100% match on endpoints */
	if (new_line->Point1.X == old_line->Point1.X && new_line->Point2.X == old_line->Point2.X && new_line->Point1.Y == old_line->Point1.Y && new_line->Point2.Y == old_line->Point2.Y)
		return PCB_LINMER_SKIP;

	/* 100% match on endpoints - check the other point order */
	if (new_line->Point2.X == old_line->Point1.X && new_line->Point1.X == old_line->Point2.X && new_line->Point2.Y == old_line->Point1.Y && new_line->Point1.Y == old_line->Point2.Y)
		return PCB_LINMER_SKIP;

	/* don't merge lines if the clear flags or clearance or thickness differ */
	if (old_line->Thickness != new_line->Thickness)
		return PCB_LINMER_NONE;

	if (old_line->Clearance != new_line->Clearance)
		return PCB_LINMER_NONE;

	if (PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, old_line) != PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, new_line))
		return PCB_LINMER_NONE;

	/*** matching endpoint ***/
	out->Thickness = 4; /* theoretical line for the on-point check should have small thicnkess - but leave some room for rouding errors */

	/* remove unnecessary line points */
	if (old_line->Point1.X == new_line->Point1.X && old_line->Point1.Y == new_line->Point1.Y) {
		out->Point1.X = old_line->Point2.X;
		out->Point1.Y = old_line->Point2.Y;
		out->Point2.X = new_line->Point2.X;
		out->Point2.Y = new_line->Point2.Y;
		if (pcb_is_point_on_line(new_line->Point1.X, new_line->Point1.Y, 1, out))
			return PCB_LINMER_REMPT;
		return PCB_LINMER_NONE;
	}
	else if (old_line->Point2.X == new_line->Point1.X && old_line->Point2.Y == new_line->Point1.Y) {
		out->Point1.X = old_line->Point1.X;
		out->Point1.Y = old_line->Point1.Y;
		out->Point2.X = new_line->Point2.X;
		out->Point2.Y = new_line->Point2.Y;
		if (pcb_is_point_on_line(new_line->Point1.X, new_line->Point1.Y, 1, out))
			return PCB_LINMER_REMPT;
		return PCB_LINMER_NONE;
	}
	else if (old_line->Point1.X == new_line->Point2.X && old_line->Point1.Y == new_line->Point2.Y) {
		out->Point1.X = old_line->Point2.X;
		out->Point1.Y = old_line->Point2.Y;
		out->Point2.X = new_line->Point1.X;
		out->Point2.Y = new_line->Point1.Y;
		if (pcb_is_point_on_line(new_line->Point2.X, new_line->Point2.Y, 1, out))
			return PCB_LINMER_REMPT;
		return PCB_LINMER_NONE;
	}
	else if (old_line->Point2.X == new_line->Point2.X && old_line->Point2.Y == new_line->Point2.Y) {
		out->Point1.X = old_line->Point1.X;
		out->Point1.Y = old_line->Point1.Y;
		out->Point2.X = new_line->Point1.X;
		out->Point2.Y = new_line->Point1.Y;
		if (pcb_is_point_on_line(new_line->Point2.X, new_line->Point2.Y, 1, out))
			return PCB_LINMER_REMPT;
		return PCB_LINMER_NONE;
	}

	/*** Partial overlap ***/

	tmp = *old_line;
	tmp.Thickness = 4;
	ov_n1o = pcb_is_point_on_line(new_line->Point1.X, new_line->Point1.Y, 1, &tmp);
	ov_n2o = pcb_is_point_on_line(new_line->Point2.X, new_line->Point2.Y, 1, &tmp);

	/* new line fully within old line */
	if (ov_n1o && ov_n2o)
		return PCB_LINMER_SKIP;

	tmp = *new_line;
	tmp.Thickness = 4;
	ov_o1n = pcb_is_point_on_line(old_line->Point1.X, old_line->Point1.Y, 1, &tmp);
	ov_o2n = pcb_is_point_on_line(old_line->Point2.X, old_line->Point2.Y, 1, &tmp);

	/* old line fully covered by the new line */
	if (ov_o1n && ov_o2n) {
		*out = *new_line;
		return PCB_LINMER_REMPT;
	}

	/* common overlapping segment */
	if (ov_o1n && ov_n2o) {
		if (rnd_distance2(old_line->Point1.X, old_line->Point1.Y, new_line->Point2.X, new_line->Point2.Y) <= 4*4)
			return PCB_LINMER_NONE; /* don't merge if points are within rounding error range: they are the same point in a \_ configuration but with a few nm error that looks like an overlap */

		out->Point1.X = old_line->Point2.X;
		out->Point1.Y = old_line->Point2.Y;
		out->Point2.X = new_line->Point1.X;
		out->Point2.Y = new_line->Point1.Y;
		return PCB_LINMER_REMPT;
	}

	if (ov_o1n && ov_n1o) {
		if (rnd_distance2(old_line->Point1.X, old_line->Point1.Y, new_line->Point1.X, new_line->Point1.Y) <= 4*4)
			return PCB_LINMER_NONE; /* don't merge if points are within rounding error range: they are the same point in a \_ configuration but with a few nm error that looks like an overlap */

		out->Point1.X = old_line->Point2.X;
		out->Point1.Y = old_line->Point2.Y;
		out->Point2.X = new_line->Point2.X;
		out->Point2.Y = new_line->Point2.Y;
		return PCB_LINMER_REMPT;
	}

	if (ov_o2n && ov_n2o) {
		if (rnd_distance2(old_line->Point2.X, old_line->Point2.Y, new_line->Point2.X, new_line->Point2.Y) <= 4*4)
			return PCB_LINMER_NONE; /* don't merge if points are within rounding error range: they are the same point in a \_ configuration but with a few nm error that looks like an overlap */

		out->Point1.X = old_line->Point1.X;
		out->Point1.Y = old_line->Point1.Y;
		out->Point2.X = new_line->Point1.X;
		out->Point2.Y = new_line->Point1.Y;
		return PCB_LINMER_REMPT;
	}

	if (ov_o2n && ov_n1o) {
		if (rnd_distance2(old_line->Point2.X, old_line->Point2.Y, new_line->Point1.X, new_line->Point1.Y) <= 4*4)
			return PCB_LINMER_NONE; /* don't merge if points are within rounding error range: they are the same point in a \_ configuration but with a few nm error that looks like an overlap */

		out->Point1.X = old_line->Point1.X;
		out->Point1.Y = old_line->Point1.Y;
		out->Point2.X = new_line->Point2.X;
		out->Point2.Y = new_line->Point2.Y;
		return PCB_LINMER_REMPT;
	}


	return PCB_LINMER_NONE;
}

pcb_line_merge_t pcb_line_can_merge_lines(const pcb_line_t *old_line, const pcb_line_t *new_line, pcb_line_t *out)
{
	return can_merge_lines(old_line, new_line, out);
}

static void maybe_move_line_pt(pcb_layer_t *layer, pcb_line_t *line, rnd_point_t *pt, rnd_point_t *new_loc)
{
	rnd_coord_t dx = new_loc->X - pt->X, dy = new_loc->Y - pt->Y;
	if ((dx == 0) && (dy == 0))
		return;
	pcb_move_obj(PCB_OBJ_LINE_POINT, layer, line, pt, dx, dy);
}


void pcb_line_mod_merge_inhibit_inc(pcb_board_t *pcb)
{
	if (pcb->line_mod_merge_inhibit > 8192) {
		rnd_message(RND_MSG_ERROR, "internal error: pcb_line_mod_merge_inhibit_dec overflow\n");
		return;
	}
	pcb->line_mod_merge_inhibit++;
}

void pcb_line_mod_merge_inhibit_dec(pcb_board_t *pcb)
{
	if (pcb->line_mod_merge_inhibit == 0) {
		rnd_message(RND_MSG_ERROR, "internal error: pcb_line_mod_merge_inhibit_dec underflow\n");
		return;
	}
	pcb->line_mod_merge_inhibit--;
	if (pcb->line_mod_merge_inhibit == 0) {
		long n;
		if (pcb->line_mod_merge == NULL)
			return;
		for(n = 0; n < pcb->line_mod_merge->used; n++)
			if (pcb->line_mod_merge->array[n] != NULL)
				pcb_line_mod_merge(pcb->line_mod_merge->array[n], 1);
		pcb->line_mod_merge->used = 0;
	}
}

static void line_mod_merge_removed(pcb_board_t *pcb, pcb_line_t *line)
{
	long n;
	if (pcb->line_mod_merge == NULL)
		return;
	for(n = 0; n < pcb->line_mod_merge->used; n++) {
		if (pcb->line_mod_merge->array[n] == line) {
			pcb->line_mod_merge->array[n] = NULL;
			return;
		}
	}
}

void pcb_line_mod_merge(pcb_line_t *line, rnd_bool undoable)
{
	pcb_layer_t *layer = line->parent.layer;
	rnd_rtree_it_t it;
	pcb_line_t *l2, *old_line, *new_line, out;
	rnd_box_t search;
	int retries = 16;
	pcb_board_t *pcb;

	if (!conf_core.editor.trace_auto_merge)
		return;

	pcb = pcb_data_get_top(line->parent.layer->parent.data);
	if (pcb == NULL)
		return;

	if (pcb->line_mod_merge_inhibit) { /* delay removal */
		long n;
		if (pcb->line_mod_merge == NULL)
			pcb->line_mod_merge = calloc(sizeof(vtp0_t), 1);
		for(n = 0; n < pcb->line_mod_merge->used; n++)
			if (pcb->line_mod_merge->array[n] == line)
				return;
		vtp0_append(pcb->line_mod_merge, line);
		return;
	}

	assert(undoable); /* non-undoable is not yet supported */

	retry:;
	if (retries-- == 0)
		return; /* fallback: avoid infinite loop in any case */

	search.X1 = MIN(line->Point1.X, line->Point2.X);
	search.X2 = MAX(line->Point1.X, line->Point2.X);
	search.Y1 = MIN(line->Point1.Y, line->Point2.Y);
	search.Y2 = MAX(line->Point1.Y, line->Point2.Y);
	if (search.Y2 == search.Y1)
		search.Y2++;
	if (search.X2 == search.X1)
		search.X2++;

	for(l2 = rnd_rtree_first(&it, layer->line_tree, (rnd_rtree_box_t *)&search); l2 != NULL; l2 = rnd_rtree_next(&it)) {
		pcb_line_merge_t res;

		if (line == l2)
			continue;

		if (line->ID < l2->ID) {
			old_line = line;
			new_line = l2;
		}
		else {
			old_line = l2;
			new_line = line;
		}

		res = can_merge_lines(old_line, new_line, &out);
		switch(res) {
			case PCB_LINMER_NONE: break;
			case PCB_LINMER_REMPT:
				pcb_undo_move_obj_to_remove(PCB_OBJ_LINE, layer, new_line, new_line);
				line_mod_merge_removed(pcb, new_line);
				maybe_move_line_pt(layer, old_line, &old_line->Point1, &out.Point1);
				maybe_move_line_pt(layer, old_line, &old_line->Point2, &out.Point2);
				line = old_line;
				goto retry;

			case PCB_LINMER_SKIP:
				pcb_undo_move_obj_to_remove(PCB_OBJ_LINE, layer, new_line, new_line);
				line_mod_merge_removed(pcb, new_line);
				break;
		}
	}
}


static rnd_rtree_dir_t line_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	int res;
	pcb_line_t *line = (pcb_line_t *)obj;
	struct line_info *i = (struct line_info *) cl;

	res = can_merge_lines(line, &i->lin, &i->modpts);
	switch(res) {
		case PCB_LINMER_NONE:  return rnd_RTREE_DIR_NOT_FOUND_CONT;
		case PCB_LINMER_REMPT: i->remove_line = line; longjmp(i->env, 1); break;
		case PCB_LINMER_SKIP:  i->skip_new = 1; longjmp(i->env, 1); break;
	}

	return rnd_RTREE_DIR_NOT_FOUND_CONT; /* should't ever get here */
}


/* creates a new line on a layer and checks for overlap and extension */
pcb_line_t *pcb_line_new_merge(pcb_layer_t *Layer, rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, rnd_coord_t Thickness, rnd_coord_t Clearance, pcb_flag_t Flags)
{
	struct line_info info = {0};
	rnd_box_t search;

	if (!conf_core.editor.trace_auto_merge)
		return pcb_line_new(Layer, X1, Y1, X2, Y2, Thickness, Clearance, Flags);

	search.X1 = MIN(X1, X2);
	search.X2 = MAX(X1, X2);
	search.Y1 = MIN(Y1, Y2);
	search.Y2 = MAX(Y1, Y2);
	if (search.Y2 == search.Y1)
		search.Y2++;
	if (search.X2 == search.X1)
		search.X2++;

	info.lin.Point1.X = X1;
	info.lin.Point2.X = X2;
	info.lin.Point1.Y = Y1;
	info.lin.Point2.Y = Y2;
	info.lin.Thickness = Thickness;
	info.lin.Clearance = Clearance;
	info.lin.Flags = Flags;

	/* prevent stacking of duplicate lines
	 * and remove needless intermediate points
	 * verify that the layer is on the board first!
	 */
	if (setjmp(info.env) == 0) {
		rnd_rtree_search_any(Layer->line_tree, (rnd_rtree_box_t *)&search, NULL, line_callback, &info, NULL);
		return pcb_line_new(Layer, X1, Y1, X2, Y2, Thickness, Clearance, Flags);
	}

	if (info.skip_new)
		return NULL; /* skip creating the new line (e.g. stacked line) */

	/* remove unnecessary points */
	if (info.remove_line) {
		/* must do this BEFORE getting new line memory */
		pcb_undo_move_obj_to_remove(PCB_OBJ_LINE, Layer, info.remove_line, info.remove_line);
		X1 = info.modpts.Point1.X;
		X2 = info.modpts.Point2.X;
		Y1 = info.modpts.Point1.Y;
		Y2 = info.modpts.Point2.Y;
	}
	return pcb_line_new(Layer, X1, Y1, X2, Y2, Thickness, Clearance, Flags);
}

pcb_line_t *pcb_line_new(pcb_layer_t *Layer, rnd_coord_t X1, rnd_coord_t Y1, rnd_coord_t X2, rnd_coord_t Y2, rnd_coord_t Thickness, rnd_coord_t Clearance, pcb_flag_t Flags)
{
	pcb_line_t *Line;

	Line = pcb_line_alloc(Layer);
	if (!Line)
		return Line;
	Line->Flags = Flags;
	PCB_FLAG_CLEAR(PCB_FLAG_RAT, Line);
	Line->Thickness = Thickness;
	Line->Clearance = Clearance;
	Line->Point1.X = X1;
	Line->Point1.Y = Y1;
	Line->Point1.ID = pcb_create_ID_get();
	Line->Point2.X = X2;
	Line->Point2.Y = Y2;
	Line->Point2.ID = pcb_create_ID_get();
	pcb_add_line_on_layer(Layer, Line);
	return Line;
}

static pcb_line_t *pcb_line_copy_meta(pcb_line_t *dst, pcb_line_t *src)
{
	if (dst == NULL)
		return NULL;
	pcb_attribute_copy_all(&dst->Attributes, &src->Attributes);
	return dst;
}

pcb_line_t *pcb_line_dup(pcb_layer_t *dst, pcb_line_t *src)
{
	pcb_line_t *l = pcb_line_new(dst, src->Point1.X, src->Point1.Y, src->Point2.X, src->Point2.Y, src->Thickness, src->Clearance, src->Flags);
	return pcb_line_copy_meta(l, src);
}

pcb_line_t *pcb_line_dup_at(pcb_layer_t *dst, pcb_line_t *src, rnd_coord_t dx, rnd_coord_t dy)
{
	pcb_line_t *l = pcb_line_new(dst, src->Point1.X + dx, src->Point1.Y + dy, src->Point2.X + dx, src->Point2.Y + dy, src->Thickness, src->Clearance, src->Flags);
	return pcb_line_copy_meta(l, src);
}

void pcb_add_line_on_layer(pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_bbox(Line);
	if (!Layer->line_tree)
		rnd_rtree_init(Layer->line_tree = malloc(sizeof(rnd_rtree_t)));
	rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	Line->parent.layer = Layer;
	Line->parent_type = PCB_PARENT_LAYER;
}

static void pcb_line_bbox_(const pcb_line_t *Line, rnd_box_t *dst, int mini)
{
	rnd_coord_t width = mini ? (Line->Thickness + 1) / 2 : (Line->Thickness + Line->Clearance + 1) / 2;

	/* Adjust for our discrete polygon approximation */
	width = (double) width *RND_POLY_CIRC_RADIUS_ADJ + 0.5;

	dst->X1 = MIN(Line->Point1.X, Line->Point2.X) - width;
	dst->X2 = MAX(Line->Point1.X, Line->Point2.X) + width;
	dst->Y1 = MIN(Line->Point1.Y, Line->Point2.Y) - width;
	dst->Y2 = MAX(Line->Point1.Y, Line->Point2.Y) + width;
	rnd_close_box(dst);
}

/* sets the bounding box of a line */
void pcb_line_bbox(pcb_line_t *Line)
{
	pcb_line_bbox_(Line, &Line->BoundingBox, 0);
	pcb_line_bbox_(Line, &Line->bbox_naked, 1);
	pcb_set_point_bounding_box(&Line->Point1);
	pcb_set_point_bounding_box(&Line->Point2);
}

int pcb_line_eq(const pcb_host_trans_t *tr1, const pcb_line_t *l1, const pcb_host_trans_t *tr2, const pcb_line_t *l2)
{
	if (pcb_field_neq(l1, l2, Thickness) || pcb_field_neq(l1, l2, Clearance)) return 0;
	if (pcb_neqs(l1->term, l2->term)) return 0;
	if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, l1) && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, l2)) {
		if (pcb_neq_tr_coords(tr1, l1->Point1.X, l1->Point1.Y, tr2, l2->Point1.X, l2->Point1.Y)) goto swap;
		if (pcb_neq_tr_coords(tr1, l1->Point2.X, l1->Point2.Y, tr2, l2->Point2.X, l2->Point2.Y)) return 0;
	}
	return 1;

	swap:; /* check the line with swapped endpoints */
	if (pcb_neq_tr_coords(tr1, l1->Point2.X, l1->Point2.Y, tr2, l2->Point1.X, l2->Point1.Y)) return 0;
	if (pcb_neq_tr_coords(tr1, l1->Point1.X, l1->Point1.Y, tr2, l2->Point2.X, l2->Point2.Y)) return 0;
	return 1;
}


unsigned int pcb_line_hash(const pcb_host_trans_t *tr, const pcb_line_t *l)
{
	unsigned int crd = 0;

	if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, l)) {
		rnd_coord_t x1, y1, x2, y2;
		pcb_hash_tr_coords(tr, &x1, &y1, l->Point1.X, l->Point1.Y);
		pcb_hash_tr_coords(tr, &x2, &y2, l->Point2.X, l->Point2.Y);
		crd = pcb_hash_coord(x1) ^ pcb_hash_coord(y1) ^ pcb_hash_coord(x2) ^ pcb_hash_coord(y2);
	}

	return pcb_hash_coord(l->Thickness) ^ pcb_hash_coord(l->Clearance) ^
		pcb_hash_str(l->term) ^ crd;
}


rnd_coord_t pcb_line_length(const pcb_line_t *line)
{
	return rnd_round(rnd_distance((double)line->Point1.X, (double)line->Point1.Y, (double)line->Point2.X, (double)line->Point2.Y));
}

double pcb_line_area(const pcb_line_t *line)
{
	return
		pcb_line_length(line) * (double)line->Thickness /* body */
		+ (double)line->Thickness * (double)line->Thickness * M_PI; /* cap circles */
}

void pcb_sqline_to_rect(const pcb_line_t *line, rnd_coord_t *x, rnd_coord_t *y)
{
	double l, vx, vy, nx, ny, width, x1, y1, x2, y2, dx, dy;

	x1 = line->Point1.X;
	y1 = line->Point1.Y;
	x2 = line->Point2.X;
	y2 = line->Point2.Y;

	width = (double)((line->Thickness + 1) / 2);
	dx = x2-x1;
	dy = y2-y1;

	if ((dx == 0) && (dy == 0))
		dx = 1;

	l = sqrt((double)dx*(double)dx + (double)dy*(double)dy);

	vx = dx / l;
	vy = dy / l;
	nx = -vy;
	ny = vx;

	x[0] = (rnd_coord_t)rnd_round(x1 - vx * width + nx * width);
	y[0] = (rnd_coord_t)rnd_round(y1 - vy * width + ny * width);
	x[1] = (rnd_coord_t)rnd_round(x1 - vx * width - nx * width);
	y[1] = (rnd_coord_t)rnd_round(y1 - vy * width - ny * width);
	x[2] = (rnd_coord_t)rnd_round(x2 + vx * width - nx * width);
	y[2] = (rnd_coord_t)rnd_round(y2 + vy * width - ny * width);
	x[3] = (rnd_coord_t)rnd_round(x2 + vx * width + nx * width);
	y[3] = (rnd_coord_t)rnd_round(y2 + vy * width + ny * width);
}

void pcb_line_pre(pcb_line_t *line)
{
	pcb_layer_t *ly = pcb_layer_get_real(line->parent.layer);
	if (ly == NULL)
		return;
	if (ly->line_tree != NULL)
		rnd_rtree_delete(ly->line_tree, line, (rnd_rtree_box_t *)line);
	pcb_poly_restore_to_poly(ly->parent.data, PCB_OBJ_LINE, ly, line);
}

void pcb_line_post(pcb_line_t *line)
{
	pcb_layer_t *ly = pcb_layer_get_real(line->parent.layer);
	if (ly == NULL)
		return;
	if (ly->line_tree != NULL)
		rnd_rtree_insert(ly->line_tree, line, (rnd_rtree_box_t *)line);
	pcb_poly_clear_from_poly(ly->parent.data, PCB_OBJ_LINE, ly, line);
}

/*** ops ***/
/* copies a line to buffer */
void *pcb_lineop_add_to_buffer(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_t *line;
	rnd_layer_id_t lid = pcb_layer_id(ctx->buffer.src, Layer);
	pcb_layer_t *layer;

	/* the buffer may not have the specified layer, e.g. on loose subc subc-aux layer */
	if (lid == -1)
		return NULL;

	layer = &ctx->buffer.dst->Layer[lid];
	line = pcb_line_new(layer, Line->Point1.X, Line->Point1.Y,
															Line->Point2.X, Line->Point2.Y,
															Line->Thickness, Line->Clearance, pcb_flag_mask(Line->Flags, PCB_FLAG_FOUND | ctx->buffer.extraflg));
	pcb_line_copy_meta(line, Line);
	if (ctx->buffer.keep_id) pcb_obj_change_id((pcb_any_obj_t *)line, Line->ID);
	return line;
}

/* moves a line between board and buffer */
void *pcb_lineop_move_buffer(pcb_opctx_t *ctx, pcb_layer_t *dstly, pcb_line_t *line)
{
	pcb_layer_t *srcly = line->parent.layer;

	assert(line->parent_type == PCB_PARENT_LAYER);
	if ((dstly == NULL) || (dstly == srcly)) { /* auto layer in dst */
		rnd_layer_id_t lid = pcb_layer_id(ctx->buffer.src, srcly);
		if (lid < 0) {
			rnd_message(RND_MSG_ERROR, "Internal error: can't resolve source layer ID in pcb_lineop_move_buffer\n");
			return NULL;
		}
		dstly = &ctx->buffer.dst->Layer[lid];
	}

	pcb_poly_restore_to_poly(ctx->buffer.src, PCB_OBJ_LINE, srcly, line);
	rnd_rtree_delete(srcly->line_tree, line, (rnd_rtree_box_t *)line);

	pcb_line_unreg(line);
	pcb_line_reg(dstly, line);

	PCB_FLAG_CLEAR(PCB_FLAG_FOUND, line);

	if (!dstly->line_tree)
		rnd_rtree_init(dstly->line_tree = malloc(sizeof(rnd_rtree_t)));
	rnd_rtree_insert(dstly->line_tree, line, (rnd_rtree_box_t *)line);
	pcb_poly_clear_from_poly(ctx->buffer.dst, PCB_OBJ_LINE, dstly, line);

	return line;
}

/* changes the size of a line */
void *pcb_lineop_change_size(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	rnd_coord_t value = (ctx->chgsize.is_absolute) ? ctx->chgsize.value : Line->Thickness + ctx->chgsize.value;

	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line))
		return NULL;
	if (value <= PCB_MAX_THICKNESS && value >= PCB_MIN_THICKNESS && value != Line->Thickness) {
		pcb_undo_add_obj_to_size(PCB_OBJ_LINE, Layer, Line, Line);
		pcb_line_invalidate_erase(Line);
		rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		pcb_poly_restore_to_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		Line->Thickness = value;
		pcb_line_bbox(Line);
		rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		pcb_poly_clear_from_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		pcb_line_invalidate_draw(Layer, Line);
		return Line;
	}
	return NULL;
}

/*changes the clearance size of a line */
void *pcb_lineop_change_clear_size(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	rnd_coord_t value = (ctx->chgsize.is_absolute) ? ctx->chgsize.value : Line->Clearance + ctx->chgsize.value;

	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line) || !PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, Line))
		return NULL;
	if (value < 0)
		value = 0;
	value = MIN(PCB_MAX_THICKNESS, value);
	if (!ctx->chgsize.is_absolute && ctx->chgsize.value < 0)
		value = 0;
	if (value != Line->Clearance) {
		pcb_undo_add_obj_to_clear_size(PCB_OBJ_LINE, Layer, Line, Line);
		pcb_poly_restore_to_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		pcb_line_invalidate_erase(Line);
		rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		Line->Clearance = value;
		pcb_line_bbox(Line);
		rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		pcb_poly_clear_from_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		pcb_line_invalidate_draw(Layer, Line);
		return Line;
	}
	return NULL;
}

/* changes the clearance flag of a line */
void *pcb_lineop_change_join(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line))
		return NULL;
	pcb_line_invalidate_erase(Line);
	if (PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, Line)) {
		pcb_undo_add_obj_to_clear_poly(PCB_OBJ_LINE, Layer, Line, Line, rnd_false);
		pcb_poly_restore_to_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	}
	pcb_undo_add_obj_to_flag(Line);
	PCB_FLAG_TOGGLE(PCB_FLAG_CLEARLINE, Line);
	if (PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, Line)) {
		pcb_undo_add_obj_to_clear_poly(PCB_OBJ_LINE, Layer, Line, Line, rnd_true);
		pcb_poly_clear_from_poly(ctx->chgsize.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	}
	pcb_line_invalidate_draw(Layer, Line);
	return Line;
}

/* sets the clearance flag of a line */
void *pcb_lineop_set_join(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line) || PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, Line))
		return NULL;
	return pcb_lineop_change_join(ctx, Layer, Line);
}

/* clears the clearance flag of a line */
void *pcb_lineop_clear_join(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line) || !PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, Line))
		return NULL;
	return pcb_lineop_change_join(ctx, Layer, Line);
}

/* copies a line */
void *pcb_lineop_copy(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_t *line;

	line = pcb_line_new_merge(Layer, Line->Point1.X + ctx->copy.DeltaX,
																Line->Point1.Y + ctx->copy.DeltaY,
																Line->Point2.X + ctx->copy.DeltaX,
																Line->Point2.Y + ctx->copy.DeltaY, Line->Thickness, Line->Clearance, pcb_flag_mask(Line->Flags, PCB_FLAG_FOUND));
	if (!line)
		return line;
	if (ctx->copy.keep_id)
		line->ID = Line->ID;
	pcb_line_copy_meta(line, Line);
	pcb_line_invalidate_draw(Layer, line);
	pcb_undo_add_obj_to_create(PCB_OBJ_LINE, Layer, line, line);
	return line;
}

/* moves a line */
void *pcb_lineop_move_noclip(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (Layer->meta.real.vis)
		pcb_line_invalidate_erase(Line);
	pcb_line_move(Line, ctx->move.dx, ctx->move.dy);
	if (Layer->meta.real.vis)
		pcb_line_invalidate_draw(Layer, Line);
	return Line;
}

void *pcb_lineop_move(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (Layer->line_tree != NULL)
		rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	if (ctx->move.pcb != NULL)
		pcb_poly_restore_to_poly(ctx->move.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	pcb_lineop_move_noclip(ctx, Layer, Line);
	if (Layer->line_tree != NULL)
		rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	if (ctx->move.pcb != NULL)
		pcb_poly_clear_from_poly(ctx->move.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	return Line;
}

void *pcb_lineop_clip(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	if (ctx->clip.restore) {
		rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		if (ctx->clip.pcb != NULL)
			pcb_poly_restore_to_poly(ctx->clip.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	}
	if (ctx->clip.clear) {
		rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		if (ctx->clip.pcb != NULL)
			pcb_poly_clear_from_poly(ctx->clip.pcb->Data, PCB_OBJ_LINE, Layer, Line);
	}
	return Line;
}

/* moves one end of a line */
void *pcb_lineop_move_point(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line, rnd_point_t *Point)
{
	if (Layer) {
		if (Layer->meta.real.vis)
			pcb_line_invalidate_erase(Line);
		pcb_poly_restore_to_poly(ctx->move.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		RND_MOVE_POINT(Point->X, Point->Y, ctx->move.dx, ctx->move.dy);
		pcb_line_bbox(Line);
		rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		pcb_poly_clear_from_poly(ctx->move.pcb->Data, PCB_OBJ_LINE, Layer, Line);
		if (Layer->meta.real.vis)
			pcb_line_invalidate_draw(Layer, Line);
		return Line;
	}
	else {												/* must be a rat */

		if (ctx->move.pcb->RatOn)
			pcb_rat_invalidate_erase((pcb_rat_t *) Line);
		rnd_rtree_delete(ctx->move.pcb->Data->rat_tree, Line, (rnd_rtree_box_t *)Line);
		RND_MOVE_POINT(Point->X, Point->Y, ctx->move.dx, ctx->move.dy);
		pcb_line_bbox(Line);
		rnd_rtree_insert(ctx->move.pcb->Data->rat_tree, Line, (rnd_rtree_box_t *)Line);
		if (ctx->move.pcb->RatOn)
			pcb_rat_invalidate_draw((pcb_rat_t *) Line);
		return Line;
	}
}

/* moves one end of a line */
void *pcb_lineop_move_point_with_route(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line, rnd_point_t *Point)
{
	if ((conf_core.editor.move_linepoint_uses_route == 0) || !Layer) {
		pcb_undo_add_obj_to_move(PCB_OBJ_LINE_POINT, Layer, Line, Point, ctx->move.dx, ctx->move.dy);
		return pcb_lineop_move_point(ctx, Layer, Line, Point);
	}
	else {
		/* Move with Route Code */
		pcb_route_t route;
		int mod1, is_first = (&Line->Point1 == Point);
		rnd_point_t point1 = Line->Point1;
		rnd_point_t point2 = Line->Point2;

		if (is_first) {
			point1.X += ctx->move.dx;
			point1.Y += ctx->move.dy;
		}
		else {
			point2.X += ctx->move.dx;
			point2.Y += ctx->move.dy;
		}

		mod1 = rnd_gui->shift_is_pressed(rnd_gui);
		if (is_first)
			mod1 = !mod1;

		/* Calculate the new line route and add apply it */
		pcb_route_init(&route);
		pcb_route_calculate(PCB,
												&route,
												&point1,
												&point2,
												pcb_layer_id(ctx->move.pcb->Data,Layer),
												Line->Thickness,
												Line->Clearance,
												Line->Flags,
												mod1,
												rnd_gui->control_is_pressed(rnd_gui) );
		pcb_route_apply_to_line(&route,Layer,Line);
		pcb_route_destroy(&route);
		return Line; /* first line is preserved */
	}
}

/* moves a line between layers; lowlevel routines */
void *pcb_lineop_move_to_layer_low(pcb_opctx_t *ctx, pcb_layer_t * Source, pcb_line_t * line, pcb_layer_t * Destination)
{
	rnd_rtree_delete(Source->line_tree, line, (rnd_rtree_box_t *)line);

	pcb_line_unreg(line);
	pcb_line_reg(Destination, line);

	if (!Destination->line_tree)
		rnd_rtree_init(Destination->line_tree = malloc(sizeof(rnd_rtree_t)));
	rnd_rtree_insert(Destination->line_tree, line, (rnd_rtree_box_t *)line);

	return line;
}

/* ---------------------------------------------------------------------------
 * moves a line between layers
 */
struct via_info {
	rnd_coord_t X, Y;
	jmp_buf env;
};

static rnd_rtree_dir_t moveline_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	struct via_info *i = (struct via_info *)cl;
	pcb_pstk_t *ps = pcb_pstk_new(PCB->Data, -1, conf_core.design.via_proto,
		i->X, i->Y, conf_core.design.clearance, pcb_flag_make(PCB_FLAG_CLEARLINE));

	if (ps != NULL) {
		pcb_undo_add_obj_to_create(PCB_OBJ_PSTK, ps, ps, ps);
		pcb_pstk_invalidate_draw(ps);
	}
	longjmp(i->env, 1);
}

void *pcb_lineop_move_to_layer(pcb_opctx_t *ctx, pcb_layer_t * Layer, pcb_line_t * Line)
{
	struct via_info info;
	rnd_box_t sb;
	pcb_line_t *newone;
	void *ptr1, *ptr2, *ptr3;
	rnd_coord_t via_dia2;

	if (PCB_FLAG_TEST(PCB_FLAG_LOCK, Line)) {
		rnd_message(RND_MSG_WARNING, "Sorry, line object is locked\n");
		return NULL;
	}
	if (ctx->move.dst_layer == Layer && Layer->meta.real.vis)
		pcb_line_invalidate_draw(Layer, Line);
	if (ctx->move.dst_layer == Layer)
		return Line;

	pcb_undo_add_obj_to_move_to_layer(PCB_OBJ_LINE, Layer, Line, Line);
	if (Layer->meta.real.vis)
		pcb_line_invalidate_erase(Line);
	pcb_poly_restore_to_poly(PCB->Data, PCB_OBJ_LINE, Layer, Line);
	newone = (pcb_line_t *) pcb_lineop_move_to_layer_low(ctx, Layer, Line, ctx->move.dst_layer);
	Line = NULL;
	pcb_poly_clear_from_poly(PCB->Data, PCB_OBJ_LINE, ctx->move.dst_layer, newone);
	if (ctx->move.dst_layer->meta.real.vis)
		pcb_line_invalidate_draw(ctx->move.dst_layer, newone);
	if (!conf_core.editor.auto_via || !PCB->pstk_on || ctx->move.more_to_come ||
			pcb_layer_get_group_(Layer) ==
			pcb_layer_get_group_(ctx->move.dst_layer) || !(pcb_layer_flags(PCB, pcb_layer_id(PCB->Data, Layer)) & PCB_LYT_COPPER) || !(pcb_layer_flags_(ctx->move.dst_layer) & PCB_LYT_COPPER))
		return newone;

	via_dia2 = pcb_pstk_pen_dia(PCB);
	via_dia2 /= 2;

	/* consider via at Point1 */
	sb.X1 = newone->Point1.X - newone->Thickness / 2;
	sb.X2 = newone->Point1.X + newone->Thickness / 2;
	sb.Y1 = newone->Point1.Y - newone->Thickness / 2;
	sb.Y2 = newone->Point1.Y + newone->Thickness / 2;

	if ((pcb_search_obj_by_location(PCB_OBJ_CLASS_PIN, &ptr1, &ptr2, &ptr3,
															newone->Point1.X, newone->Point1.Y, via_dia2) == PCB_OBJ_VOID)) {
		info.X = newone->Point1.X;
		info.Y = newone->Point1.Y;
		if (setjmp(info.env) == 0)
			rnd_rtree_search_any(Layer->line_tree, (rnd_rtree_box_t *)&sb, NULL, moveline_callback, &info, NULL);
	}
	/* consider via at Point2 */
	sb.X1 = newone->Point2.X - newone->Thickness / 2;
	sb.X2 = newone->Point2.X + newone->Thickness / 2;
	sb.Y1 = newone->Point2.Y - newone->Thickness / 2;
	sb.Y2 = newone->Point2.Y + newone->Thickness / 2;

	if ((pcb_search_obj_by_location(PCB_OBJ_CLASS_PIN, &ptr1, &ptr2, &ptr3,
															newone->Point2.X, newone->Point2.Y, via_dia2) == PCB_OBJ_VOID)) {
		info.X = newone->Point2.X;
		info.Y = newone->Point2.Y;
		if (setjmp(info.env) == 0)
			rnd_rtree_search_any(Layer->line_tree, (rnd_rtree_box_t *)&sb, NULL, moveline_callback, &info, NULL);
	}
	return newone;
}

void *pcb_lineop_change_thermal(pcb_opctx_t *ctx, pcb_layer_t *ly, pcb_line_t *line)
{
	return pcb_anyop_change_thermal(ctx, (pcb_any_obj_t *)line);
}

/* destroys a line from a layer */
void *pcb_lineop_destroy(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);

	pcb_line_free(Line);
	return NULL;
}

/* remove point... */
struct rlp_info {
	jmp_buf env;
	pcb_line_t *line;
	rnd_point_t *point;
};
static rnd_rtree_dir_t remove_point(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_line_t *line = (pcb_line_t *)obj;
	struct rlp_info *info = (struct rlp_info *)cl;
	if (line == info->line)
		return rnd_RTREE_DIR_NOT_FOUND_CONT;
	if ((line->Point1.X == info->point->X)
			&& (line->Point1.Y == info->point->Y)) {
		info->line = line;
		info->point = &line->Point1;
		longjmp(info->env, 1);
	}
	else if ((line->Point2.X == info->point->X)
					 && (line->Point2.Y == info->point->Y)) {
		info->line = line;
		info->point = &line->Point2;
		longjmp(info->env, 1);
	}
	return rnd_RTREE_DIR_NOT_FOUND_CONT;
}

/* removes a line point, or a line if the selected point is the end */
void *pcb_lineop_remove_point(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line, rnd_point_t *Point)
{
	rnd_point_t other;
	struct rlp_info info;
	if (&Line->Point1 == Point)
		other = Line->Point2;
	else
		other = Line->Point1;
	info.line = Line;
	info.point = Point;
	if (setjmp(info.env) == 0) {
		rnd_rtree_search_any(Layer->line_tree, (const rnd_rtree_box_t *)Point, NULL, remove_point, &info, NULL);
		return pcb_lineop_remove(ctx, Layer, Line);
	}
	pcb_move_obj(PCB_OBJ_LINE_POINT, Layer, info.line, info.point, other.X - Point->X, other.Y - Point->Y);
	return (pcb_lineop_remove(ctx, Layer, Line));
}

/* removes a line from a layer */
void *pcb_lineop_remove(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	/* erase from screen */
	if (Layer->meta.real.vis)
		pcb_line_invalidate_erase(Line);
	pcb_undo_move_obj_to_remove(PCB_OBJ_LINE, Layer, Line, Line);
	return NULL;
}

void *pcb_line_destroy(pcb_layer_t *Layer, pcb_line_t *Line)
{
	void *res;
	pcb_opctx_t ctx;

	ctx.remove.pcb = PCB;
	ctx.remove.destroy_target = NULL;

	res = pcb_lineop_remove(&ctx, Layer, Line);
	pcb_draw();
	return res;
}

/* rotates a line in 90 degree steps */
void pcb_line_rotate90(pcb_line_t *Line, rnd_coord_t X, rnd_coord_t Y, unsigned Number)
{
	RND_COORD_ROTATE90(Line->Point1.X, Line->Point1.Y, X, Y, Number);
	RND_COORD_ROTATE90(Line->Point2.X, Line->Point2.Y, X, Y, Number);
	/* keep horizontal, vertical Point2 > Point1 */
	if (Line->Point1.X == Line->Point2.X) {
		if (Line->Point1.Y > Line->Point2.Y) {
			rnd_coord_t t;
			t = Line->Point1.Y;
			Line->Point1.Y = Line->Point2.Y;
			Line->Point2.Y = t;
		}
	}
	else if (Line->Point1.Y == Line->Point2.Y) {
		if (Line->Point1.X > Line->Point2.X) {
			rnd_coord_t t;
			t = Line->Point1.X;
			Line->Point1.X = Line->Point2.X;
			Line->Point2.X = t;
		}
	}
	/* instead of rotating the bounding box, the call updates both end points too */
	pcb_line_bbox(Line);
}

void pcb_line_rotate(pcb_layer_t *layer, pcb_line_t *line, rnd_coord_t X, rnd_coord_t Y, double cosa, double sina)
{
	if (layer->line_tree != NULL)
		rnd_rtree_delete(layer->line_tree, line, (rnd_rtree_box_t *)line);
	rnd_rotate(&line->Point1.X, &line->Point1.Y, X, Y, cosa, sina);
	rnd_rotate(&line->Point2.X, &line->Point2.Y, X, Y, cosa, sina);
	pcb_line_bbox(line);
	if (layer->line_tree != NULL)
		rnd_rtree_insert(layer->line_tree, line, (rnd_rtree_box_t *)line);
}

void pcb_line_mirror(pcb_line_t *line, rnd_coord_t y_offs, rnd_bool undoable)
{
	undo_line_geo_t gtmp, *g = &gtmp;

	if (undoable) g = pcb_undo_alloc(pcb_data_get_top(line->parent.layer->parent.data), &undo_line_geo, sizeof(undo_line_geo_t));

	g->line = line;
	g->Point1.X = PCB_SWAP_X(line->Point1.X);
	g->Point1.Y = PCB_SWAP_Y(line->Point1.Y) + y_offs;
	g->Point2.X = PCB_SWAP_X(line->Point2.X);
	g->Point2.Y = PCB_SWAP_Y(line->Point2.Y) + y_offs;
	g->Thickness = line->Thickness;
	g->Clearance = line->Clearance;

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

void pcb_line_modify(pcb_line_t *line, rnd_coord_t *x1, rnd_coord_t *y1, rnd_coord_t *x2, rnd_coord_t *y2, rnd_coord_t *thick, rnd_coord_t *clr, rnd_bool undoable)
{
	undo_line_geo_t gtmp, *g = &gtmp;

	if (undoable) g = pcb_undo_alloc(pcb_data_get_top(line->parent.layer->parent.data), &undo_line_geo, sizeof(undo_line_geo_t));

	g->line = line;
	g->Point1.X  = (x1 == NULL) ? line->Point1.X : *x1;
	g->Point1.Y  = (y1 == NULL) ? line->Point1.Y : *y1;
	g->Point2.X  = (x2 == NULL) ? line->Point2.X : *x2;
	g->Point2.Y  = (y2 == NULL) ? line->Point2.Y : *y2;
	g->Thickness = (thick == NULL) ? line->Thickness : *thick;
	g->Clearance = (clr == NULL) ? line->Clearance : *clr;

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

void pcb_line_scale(pcb_line_t *line, double sx, double sy, double sth)
{
	int onbrd = (line->parent.layer != NULL) && (!line->parent.layer->is_bound);

	if (onbrd)
		pcb_line_pre(line);

	if (sx != 1.0) {
		line->Point1.X = rnd_round((double)line->Point1.X * sx);
		line->Point2.X = rnd_round((double)line->Point2.X * sx);
	}

	if (sy != 1.0) {
		line->Point1.Y = rnd_round((double)line->Point1.Y * sy);
		line->Point2.Y = rnd_round((double)line->Point2.Y * sy);
	}

	if (sth != 1.0)
		line->Thickness = rnd_round((double)line->Thickness * sth);

	pcb_line_bbox(line);

	if (onbrd)
		pcb_line_post(line);
}

void pcb_line_flip_side(pcb_layer_t *layer, pcb_line_t *line)
{
	rnd_rtree_delete(layer->line_tree, line, (rnd_rtree_box_t *)line);
	line->Point1.X = PCB_SWAP_X(line->Point1.X);
	line->Point1.Y = PCB_SWAP_Y(line->Point1.Y);
	line->Point2.X = PCB_SWAP_X(line->Point2.X);
	line->Point2.Y = PCB_SWAP_Y(line->Point2.Y);
	pcb_line_bbox(line);
	rnd_rtree_insert(layer->line_tree, line, (rnd_rtree_box_t *)line);
}

static void rotate_line1(pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_invalidate_erase(Line);
	if (Layer) {
		if (!Layer->is_bound)
			pcb_poly_restore_to_poly(PCB->Data, PCB_OBJ_LINE, Layer, Line);
		if (Layer->line_tree != NULL)
			rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	}
	else
		rnd_rtree_delete(PCB->Data->rat_tree, Line, (rnd_rtree_box_t *)Line);
}

static void rotate_line2(pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_bbox(Line);
	if (Layer) {
		if (Layer->line_tree != NULL)
			rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
		if (!Layer->is_bound)
			pcb_poly_clear_from_poly(PCB->Data, PCB_OBJ_LINE, Layer, Line);
		pcb_line_invalidate_draw(Layer, Line);
	}
	else {
		rnd_rtree_insert(PCB->Data->rat_tree, Line, (rnd_rtree_box_t *)Line);
		pcb_rat_invalidate_draw((pcb_rat_t *) Line);
	}
}

/* rotates a line's point */
void *pcb_lineop_rotate90_point(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line, rnd_point_t *Point)
{
	rotate_line1(Layer, Line);

	pcb_point_rotate90(Point, ctx->rotate.center_x, ctx->rotate.center_y, ctx->rotate.number);

	rotate_line2(Layer, Line);
	return Line;
}

/* rotates a line */
void *pcb_lineop_rotate90(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	rotate_line1(Layer, Line);
	pcb_point_rotate90(&Line->Point1, ctx->rotate.center_x, ctx->rotate.center_y, ctx->rotate.number);
	pcb_point_rotate90(&Line->Point2, ctx->rotate.center_x, ctx->rotate.center_y, ctx->rotate.number);
	rotate_line2(Layer, Line);
	return Line;
}

void *pcb_lineop_rotate(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_rotate(Layer, Line, ctx->rotate.center_x, ctx->rotate.center_y, ctx->rotate.cosa, ctx->rotate.sina);
	return Line;
}

/* inserts a point into a line */
void *pcb_lineop_insert_point(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_line_t *line;
	rnd_coord_t X, Y;

	if (((Line->Point1.X == ctx->insert.x) && (Line->Point1.Y == ctx->insert.y)) ||
			((Line->Point2.X == ctx->insert.x) && (Line->Point2.Y == ctx->insert.y)))
		return NULL;
	X = Line->Point2.X;
	Y = Line->Point2.Y;
	pcb_undo_add_obj_to_move(PCB_OBJ_LINE_POINT, Layer, Line, &Line->Point2, ctx->insert.x - X, ctx->insert.y - Y);
	pcb_line_invalidate_erase(Line);
	rnd_rtree_delete(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	pcb_poly_restore_to_poly(PCB->Data, PCB_OBJ_LINE, Layer, Line);
	Line->Point2.X = ctx->insert.x;
	Line->Point2.Y = ctx->insert.y;
	pcb_line_bbox(Line);
	rnd_rtree_insert(Layer->line_tree, Line, (rnd_rtree_box_t *)Line);
	pcb_poly_clear_from_poly(PCB->Data, PCB_OBJ_LINE, Layer, Line);
	pcb_line_invalidate_draw(Layer, Line);
	/* we must create after playing with Line since creation may
	 * invalidate the line pointer
	 */

	line = pcb_line_new(Layer, ctx->insert.x, ctx->insert.y, X, Y, Line->Thickness, Line->Clearance, Line->Flags);

	if (line) {
		pcb_line_copy_meta(line, Line);
		pcb_undo_add_obj_to_create(PCB_OBJ_LINE, Layer, line, line);
		pcb_line_invalidate_draw(Layer, line);
		pcb_poly_clear_from_poly(PCB->Data, PCB_OBJ_LINE, Layer, line);
		/* creation call adds it to the rtree */
	}
	return line;
}

void *pcb_lineop_change_flag(pcb_opctx_t *ctx, pcb_layer_t *Layer, pcb_line_t *Line)
{
	static pcb_flag_values_t pcb_line_flags = 0;
	if (pcb_line_flags == 0)
		pcb_line_flags = pcb_obj_valid_flags(PCB_OBJ_LINE);

	if ((ctx->chgflag.flag & pcb_line_flags) != ctx->chgflag.flag)
		return NULL;
	if ((ctx->chgflag.flag & PCB_FLAG_TERMNAME) && (Line->term == NULL))
		return NULL;
	pcb_undo_add_obj_to_flag(Line);

	if (ctx->chgflag.flag & PCB_FLAG_CLEARLINE)
		pcb_poly_restore_to_poly(ctx->chgflag.pcb->Data, PCB_OBJ_LINE, Line->parent.layer, Line);

	PCB_FLAG_CHANGE(ctx->chgflag.how, ctx->chgflag.flag, Line);

	if (ctx->chgflag.flag & PCB_FLAG_CLEARLINE)
		pcb_poly_clear_from_poly(ctx->chgflag.pcb->Data, PCB_OBJ_LINE, Line->parent.layer, Line);

	return Line;
}

void *pcb_lineop_invalidate_label(pcb_opctx_t *ctx, pcb_layer_t *layer, pcb_line_t *line)
{
	pcb_line_name_invalidate_draw(line);
	return line;
}


/*** draw ***/

static rnd_bool is_line_term_vert(const pcb_line_t *line)
{
	rnd_coord_t dx, dy;

	dx = line->Point1.X - line->Point2.X;
	if (dx < 0)
		dx = -dx;

	dy = line->Point1.Y - line->Point2.Y;
	if (dy < 0)
		dy = -dy;

	return dx < dy;
}

void pcb_line_name_invalidate_draw(pcb_line_t *line)
{
	if (line->term != NULL) {
		pcb_term_label_invalidate((line->Point1.X + line->Point2.X)/2, (line->Point1.Y + line->Point2.Y)/2,
			100.0, is_line_term_vert(line), rnd_true, (pcb_any_obj_t *)line);
	}
}

void pcb_line_draw_label(pcb_draw_info_t *info, pcb_line_t *line, rnd_bool vis_side)
{
	if ((line->term != NULL) && vis_side)
		pcb_term_label_draw(info, (line->Point1.X + line->Point2.X)/2, (line->Point1.Y + line->Point2.Y)/2,
			conf_core.appearance.term_label_size, is_line_term_vert(line), rnd_true, (pcb_any_obj_t *)line);
	if (line->noexport && vis_side)
		pcb_obj_noexport_mark(line, (line->Point1.X+line->Point2.X)/2, (line->Point1.Y+line->Point2.Y)/2);

	if (line->ind_editpoint) {
		pcb_obj_editpont_setup();
		pcb_obj_editpont_cross(line->Point1.X, line->Point1.Y);
		if ((line->Point1.X != line->Point2.X) || (line->Point1.Y != line->Point2.Y))
			pcb_obj_editpont_cross(line->Point2.X, line->Point2.Y);
	}
}


void pcb_line_draw_(pcb_draw_info_t *info, pcb_line_t *line, int allow_term_gfx)
{
	rnd_coord_t thickness = line->Thickness;
	int draw_lab;

	if (delayed_terms_enabled && (line->term != NULL)) {
		pcb_draw_delay_obj_add((pcb_any_obj_t *)line);
		return;
	}

	if ((info != NULL) && (info->xform != NULL) && (info->xform->bloat != 0)) {
		thickness += info->xform->bloat;
		if (thickness < 1)
			thickness = 1;
	}

	PCB_DRAW_BBOX(line);
	rnd_hid_set_line_cap(pcb_draw_out.fgGC, rnd_cap_round);
	if (!info->xform->thin_draw && !info->xform->wireframe) {
		if ((allow_term_gfx) && pcb_draw_term_need_gfx(line) && pcb_draw_term_hid_permission()) {
			rnd_hid_set_line_cap(pcb_draw_out.active_padGC, rnd_cap_round);
			rnd_hid_set_line_width(pcb_draw_out.active_padGC, thickness);
			rnd_render->draw_line(pcb_draw_out.active_padGC, line->Point1.X, line->Point1.Y, line->Point2.X, line->Point2.Y);
			rnd_hid_set_line_width(pcb_draw_out.fgGC, PCB_DRAW_TERM_GFX_WIDTH);
		}
		else
			rnd_hid_set_line_width(pcb_draw_out.fgGC, thickness);
		rnd_render->draw_line(pcb_draw_out.fgGC, line->Point1.X, line->Point1.Y, line->Point2.X, line->Point2.Y);
	}
	else {
		if(info->xform->thin_draw) {
			rnd_hid_set_line_width(pcb_draw_out.fgGC, 0);
			rnd_render->draw_line(pcb_draw_out.fgGC, line->Point1.X, line->Point1.Y, line->Point2.X, line->Point2.Y);
		}

		if (info->xform->wireframe) {
			rnd_hid_set_line_width(pcb_draw_out.fgGC, 0);
			pcb_draw_wireframe_line(pcb_draw_out.fgGC, line->Point1.X, line->Point1.Y, line->Point2.X, line->Point2.Y, thickness, 0);
		}
	}

	draw_lab = (line->term != NULL) && ((pcb_draw_force_termlab) || PCB_FLAG_TEST(PCB_FLAG_TERMNAME, line));
	draw_lab |= line->ind_editpoint;

	if (draw_lab)
		pcb_draw_delay_label_add((pcb_any_obj_t *)line);
}

static void pcb_line_draw(pcb_draw_info_t *info, pcb_line_t *line, int allow_term_gfx)
{
	const rnd_color_t *color;
	rnd_color_t buf;
	const pcb_layer_t *layer = info->layer != NULL ? info->layer : pcb_layer_get_real(line->parent.layer);

	pcb_obj_noexport(info, line, return);

	if (layer == NULL) /* if the layer is inbound, e.g. in preview, fall back using the layer recipe */
		layer = line->parent.layer;

	if (info->xform->flag_color && PCB_FLAG_TEST(PCB_FLAG_WARN, line))
		color = &conf_core.appearance.color.warn;
	else if (info->xform->flag_color && PCB_FLAG_TEST(PCB_FLAG_SELECTED | PCB_FLAG_FOUND, line)) {
		if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, line)) {
			if (layer->is_bound)
				PCB_OBJ_COLOR_ON_BOUND_LAYER(color, layer, 1);
			else
				color = &conf_core.appearance.color.selected;
		}
		else
			color = &conf_core.appearance.color.connected;
	}
	else if (PCB_HAS_COLOROVERRIDE(line)) {
		color = line->override_color;
	}
	else if (layer->is_bound)
		PCB_OBJ_COLOR_ON_BOUND_LAYER(color, layer, 0);
	else
		color = &layer->meta.real.color;

	if (info->xform->flag_color && line->ind_onpoint) {
		pcb_lighten_color(color, &buf, 1.75);
		color = &buf;
	}

	rnd_render->set_color(pcb_draw_out.fgGC, color);
	pcb_line_draw_(info, line, allow_term_gfx);
}

rnd_rtree_dir_t pcb_line_draw_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_line_t *line = (pcb_line_t *)obj;
	pcb_draw_info_t *info = cl;

	if (pcb_hidden_floater((pcb_any_obj_t*)obj, info) || pcb_partial_export((pcb_any_obj_t*)obj, info))
		return rnd_RTREE_DIR_FOUND_CONT;

	if (!PCB->SubcPartsOn && pcb_lobj_parent_subc(line->parent_type, &line->parent))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;

	pcb_line_draw(info, line, 0);
	return rnd_RTREE_DIR_FOUND_CONT;
}

rnd_rtree_dir_t pcb_line_draw_term_callback(void *cl, void *obj, const rnd_rtree_box_t *box)
{
	pcb_line_t *line = (pcb_line_t *)obj;
	pcb_draw_info_t *info = cl;

	if (pcb_hidden_floater((pcb_any_obj_t*)obj, info) || pcb_partial_export((pcb_any_obj_t*)obj, info))
		return rnd_RTREE_DIR_FOUND_CONT;

	if (!PCB->SubcPartsOn && pcb_lobj_parent_subc(line->parent_type, &line->parent))
		return rnd_RTREE_DIR_NOT_FOUND_CONT;

	pcb_line_draw(info, line, 1);
	return rnd_RTREE_DIR_FOUND_CONT;
}

/* erases a line on a layer */
void pcb_line_invalidate_erase(pcb_line_t *Line)
{
	pcb_draw_invalidate(Line);
}

void pcb_line_invalidate_draw(pcb_layer_t *Layer, pcb_line_t *Line)
{
	pcb_draw_invalidate(Line);
}

