/*
 *                            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
 *
 *  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 "conf_core.h"

#include <stdlib.h>

#include "board.h"
#include "data.h"
#include "draw.h"
#include <librnd/core/error.h>
#include "move.h"
#include "select.h"
#include "undo.h"
#include "event.h"
#include <librnd/core/actions.h>
#include <librnd/core/compat_misc.h>
#include "extobj.h"
#include "obj_arc_op.h"
#include "obj_line_op.h"
#include "obj_text_op.h"
#include "obj_subc_op.h"
#include "obj_poly_op.h"
#include "obj_pstk_op.h"
#include "obj_rat_op.h"
#include "obj_gfx_op.h"

pcb_opfunc_t MoveFunctions = {
	NULL, /* common_pre */
	NULL, /* common_post */
	pcb_lineop_move,
	pcb_textop_move,
	pcb_polyop_move,
	pcb_lineop_move_point,
	pcb_polyop_move_point,
	pcb_arcop_move,
	pcb_gfxop_move,
	NULL,
	NULL,
	pcb_subcop_move,
	pcb_pstkop_move,
	0 /* extobj_inhibit_regen */
};

pcb_opfunc_t MoveFunctions_noclip = {
	NULL, /* common_pre */
	NULL, /* common_post */
	pcb_lineop_move_noclip,
	pcb_textop_move_noclip,
	pcb_polyop_move_noclip,
	NULL,
	NULL,
	pcb_arcop_move_noclip,
	pcb_gfxop_move_noclip,
	NULL,
	NULL,
	NULL, /* subc */
	pcb_pstkop_move_noclip,
	0 /* extobj_inhibit_regen */
};

pcb_opfunc_t ClipFunctions = {
	NULL, /* common_pre */
	NULL, /* common_post */
	pcb_lineop_clip,
	pcb_textop_clip,
	pcb_polyop_clip,
	NULL,
	NULL,
	pcb_arcop_clip,
	NULL, /* gfx */
	NULL,
	NULL,
	NULL, /* subc */
	pcb_pstkop_clip,
	1 /* extobj_inhibit_regen */
};

static pcb_opfunc_t MoveToLayerFunctions = {
	NULL, /* common_pre */
	NULL, /* common_post */
	pcb_lineop_move_to_layer,
	pcb_textop_move_to_layer,
	pcb_polyop_move_to_layer,
	NULL,
	NULL,
	pcb_arcop_move_to_layer,
	pcb_gfxop_move_to_layer,
	pcb_ratop_move_to_layer,
	NULL,
	NULL, /* subc */
	NULL, /* padstack */
	0 /* extobj_inhibit_regen */
};

static pcb_opfunc_t CopyFunctions = {
	NULL, /* common_pre */
	NULL, /* common_post */
	pcb_lineop_copy,
	pcb_textop_copy,
	pcb_polyop_copy,
	NULL,
	NULL,
	pcb_arcop_copy,
	pcb_gfxop_copy,
	NULL,
	NULL,
	pcb_subcop_copy,
	pcb_pstkop_copy,
	0 /* extobj_inhibit_regen */
};


void *pcb_move_obj(int Type, void *Ptr1, void *Ptr2, void *Ptr3, rnd_coord_t DX, rnd_coord_t DY)
{
	void *result;
	pcb_opctx_t ctx;

	ctx.move.pcb = PCB;
	ctx.move.dx = DX;
	ctx.move.dy = DY;
	pcb_undo_add_obj_to_move(Type, Ptr1, Ptr2, Ptr3, DX, DY);
	result = pcb_object_operation(&MoveFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
	return result;
}

void *pcb_move_obj_and_rubberband(int Type, void *Ptr1, void *Ptr2, void *Ptr3, rnd_coord_t DX, rnd_coord_t DY)
{
	pcb_opctx_t ctx;
	void *ptr2;

	if ((DX == 0) && (DY == 0) && (conf_core.editor.move_linepoint_uses_route == 0))
		return NULL;

	ctx.move.pcb = PCB;
	ctx.move.dx = DX;
	ctx.move.dy = DY;

	pcb_data_clip_inhibit_inc(PCB->Data);
	pcb_draw_inhibit_inc();

	switch(Type) {
		case PCB_OBJ_ARC_POINT:
			{
				/* Get the initial arc point positions */
				pcb_arc_t * p_arc = ((pcb_arc_t *)pcb_crosshair.AttachedObject.Ptr2);
				rnd_coord_t ox1,ox2,oy1,oy2;
				rnd_coord_t nx1,nx2,ny1,ny2;

				pcb_arc_get_end(p_arc,0, &ox1, &oy1);
				pcb_arc_get_end(p_arc,1, &ox2, &oy2);

				/* moving the endpoint of an arc is not really a move, but a change of arc properties */
				if (pcb_crosshair.AttachedObject.radius == 0)
					pcb_arc_set_angles((pcb_layer_t *)Ptr1, (pcb_arc_t *)Ptr2,
								 pcb_crosshair.AttachedObject.start_angle,
								 pcb_crosshair.AttachedObject.delta_angle);
				else
					pcb_arc_set_radii((pcb_layer_t *)Ptr1, (pcb_arc_t *)Ptr2,
								pcb_crosshair.AttachedObject.radius,
								pcb_crosshair.AttachedObject.radius);
				pcb_crosshair.AttachedObject.radius = 0;

				/* Get the new arc point positions so that we can calculate the position deltas */
				pcb_arc_get_end(p_arc,0, &nx1, &ny1);
				pcb_arc_get_end(p_arc,1, &nx2, &ny2);
				rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_MOVE, "icccc", 0, nx1-ox1, ny1-oy1, nx2-ox2, ny2-oy2);
				ptr2 = p_arc;
			}
			break;

		case PCB_OBJ_ARC:
			rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_MOVE, "icccc", 0, DX, DY, DX, DY);
			pcb_undo_add_obj_to_move(Type, Ptr1, Ptr2, Ptr3, DX, DY);
			ptr2 = pcb_object_operation(&MoveFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
			break;

		case PCB_OBJ_LINE_POINT :
			rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_MOVE, "icc", 0, DX, DY);
			ptr2 = pcb_lineop_move_point_with_route(&ctx, Ptr1, Ptr2, Ptr3);
			break;

		case PCB_OBJ_LINE:
			{
				rnd_coord_t dx1 = DX;
				rnd_coord_t dy1 = DY;
				rnd_coord_t dx2 = DX;
				rnd_coord_t dy2 = DY;
				pcb_line_t *line = (pcb_line_t*) Ptr2;
				int constrained = 0;

				if(conf_core.editor.rubber_band_keep_midlinedir)
					rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_CONSTRAIN_MAIN_LINE, "pppppp", line, &constrained, &dx1, &dy1, &dx2, &dy2);
				rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_MOVE, "icccc", constrained, dx1, dy1, dx2, dy2);

				ctx.move.dx = dx1;
				ctx.move.dy = dy1;

				/* If the line ends have moved indpendently then move the individual points */
				if((dx1 != dx2) || (dy1 != dy2)) {
					/* Move point1 form line */
					pcb_undo_add_obj_to_move(PCB_OBJ_LINE_POINT,
							 Ptr1, line, &line->Point1,
							 dx1, dy1);
					pcb_lineop_move_point(&ctx, Ptr1, line, &line->Point1);

					/* Move point2 form line */
					pcb_undo_add_obj_to_move(PCB_OBJ_LINE_POINT,
								 Ptr1, line, &line->Point2,
								 dx2, dy2);
					ctx.move.dx = dx2;
					ctx.move.dy = dy2;
					ptr2 = pcb_lineop_move_point(&ctx, Ptr1, line, &line->Point2);
					pcb_line_mod_merge(line, 1);
				}
				/* Otherwise make a normal move */
				else {
					pcb_undo_add_obj_to_move(Type, Ptr1, Ptr2, Ptr3, dx1, dy1);
					ptr2 = pcb_object_operation(&MoveFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
					pcb_line_mod_merge(ptr2, 1);
				}
			}
			break;

		default:
			rnd_event(&PCB->hidlib, PCB_EVENT_RUBBER_MOVE, "icc", 0, DX, DY);
			pcb_undo_add_obj_to_move(Type, Ptr1, Ptr2, Ptr3, DX, DY);
			ptr2 = pcb_object_operation(&MoveFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
			if (((pcb_any_obj_t *)ptr2)->type == PCB_OBJ_LINE)
				pcb_line_mod_merge(ptr2, 1);
			break;
	}

	pcb_undo_inc_serial();

	pcb_draw_inhibit_dec();
	pcb_data_clip_inhibit_dec(PCB->Data, 0);
	pcb_draw();

	return ptr2;
}

void *pcb_move_obj_to_layer(int Type, void *Ptr1, void *Ptr2, void *Ptr3, pcb_layer_t *Target, rnd_bool enmasse)
{
	void *result;
	pcb_opctx_t ctx;

	if (pcb_extobj_get_floater_subc(Ptr2) != NULL) {
		rnd_message(RND_MSG_ERROR, "Can not move extended object floater to a different layer\nRather edit the layer bindings of the extended object to redirect the parent layer of the object.\n");
		return NULL;
	}

	ctx.move.pcb = PCB;
	ctx.move.dst_layer = Target;
	ctx.move.more_to_come = enmasse;

	result = pcb_object_operation(&MoveToLayerFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
	pcb_undo_inc_serial();
	return result;
}

void *pcb_copy_obj(int Type, void *Ptr1, void *Ptr2, void *Ptr3, rnd_coord_t DX, rnd_coord_t DY)
{
	void *ptr;
	pcb_opctx_t ctx;

	ctx.copy.pcb = PCB;
	ctx.copy.DeltaX = DX;
	ctx.copy.DeltaY = DY;
	ctx.copy.from_outside = 0;
	ctx.copy.keep_id = 0;

	/* the subroutines add the objects to the undo-list */
	ptr = pcb_object_operation(&CopyFunctions, &ctx, Type, Ptr1, Ptr2, Ptr3);
	pcb_undo_inc_serial();
	return ptr;
}
