/*
 *                            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) 2019 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")
 *
 */

#include "config.h"

#include "operation.h"
#include "board.h"
#include "data.h"
#include "conf_core.h"
#include "undo.h"
#include "extobj.h"

/* performs several operations on the passed object */
void *pcb_object_operation(pcb_opfunc_t *F, pcb_opctx_t *ctx, int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	pcb_any_obj_t *res = NULL, *exto = NULL;

	if (F->common_pre != NULL) {
		if (F->common_pre(ctx, Ptr2, Ptr3) == 1)
			return NULL;
	}

	if ((!F->extobj_inhibit_regen) && (Ptr2 != NULL))
		exto = pcb_extobj_float_pre(Ptr2);

	switch (Type) {
		case PCB_OBJ_LINE:
			if (F->Line)
				res = F->Line(ctx, (pcb_layer_t *)Ptr1, (pcb_line_t *)Ptr2);
			break;

		case PCB_OBJ_ARC:
			if (F->Arc)
				res = F->Arc(ctx, (pcb_layer_t *)Ptr1, (pcb_arc_t *)Ptr2);
			break;

		case PCB_OBJ_GFX:
			if (F->Gfx)
				res = F->Gfx(ctx, (pcb_layer_t *)Ptr1, (pcb_gfx_t *)Ptr2);
			break;

		case PCB_OBJ_LINE_POINT:
			if (F->LinePoint)
				res = F->LinePoint(ctx, (pcb_layer_t *)Ptr1, (pcb_line_t *)Ptr2, (rnd_point_t *)Ptr3);
			break;

		case PCB_OBJ_ARC_POINT:
			if (F->ArcPoint)
				res = F->ArcPoint(ctx, (pcb_layer_t *)Ptr1, (pcb_arc_t *)Ptr2, (int *)Ptr3);
			break;

		case PCB_OBJ_GFX_POINT:
			/* at the moment there are no operations on GFX point so it's missing from the op struct */
			break;

		case PCB_OBJ_TEXT:
			if (F->Text)
				res = F->Text(ctx, (pcb_layer_t *)Ptr1, (pcb_text_t *)Ptr2);
			break;

		case PCB_OBJ_POLY:
			if (F->Polygon)
				res = F->Polygon(ctx, (pcb_layer_t *)Ptr1, (pcb_poly_t *)Ptr2);
			break;

		case PCB_OBJ_POLY_POINT:
			if (F->Point)
				res = F->Point(ctx, (pcb_layer_t *)Ptr1, (pcb_poly_t *)Ptr2, (rnd_point_t *)Ptr3);
			break;

		case PCB_OBJ_SUBC:
			if (F->subc) {
				pcb_subc_t *subc = (pcb_subc_t *)Ptr2;
				pcb_extobj_t *exto = pcb_extobj_get(subc);
				int save;

				if (exto != NULL) {
					/* F->subc() will do transofrmation on subc part objects in random
					   order; if it happens to transform floaters first, the other
					   objects may be recalculated and then transformed by F->subc().
					   To avoid this inhibit extobj operations and recalc the whole
					   subc once at the end */
					save = subc->extobj_inhibit;
					subc->extobj_inhibit = 1;
				}

				res = F->subc(ctx, subc);

				if (exto != NULL) {
					subc->extobj_inhibit = save;
					if (!subc->extobj_inhibit)
						pcb_extobj_recalc(subc);
				}
			}
			break;

		case PCB_OBJ_PSTK:
			if (F->padstack)
				res = F->padstack(ctx, (pcb_pstk_t *)Ptr2);
			break;

		case PCB_OBJ_RAT:
			if (F->Rat)
				res = F->Rat(ctx, (pcb_rat_t *)Ptr2);
			break;
	}

	if (exto != NULL)
		pcb_extobj_float_geo(exto);

	if (F->common_post != NULL)
		F->common_post(ctx, Ptr2, Ptr3);

	return res;
}

/* performs several operations on selected objects which are also visible
   The lowlevel procedures are passed together with additional information
   resets the selected flag if requested
   returns rnd_true if anything has changed */
static rnd_bool pcb_selected_operation_(pcb_board_t *pcb, pcb_data_t *data, pcb_opfunc_t *F, pcb_opctx_t *ctx, rnd_bool Reset, int type, pcb_op_mode_t mode, rnd_bool floater_only)
{
	rnd_bool changed = rnd_false;
	pcb_any_obj_t *exto;
	int on_locked_too = (mode & PCB_OP_ON_LOCKED);
	int no_subc_part = (mode & PCB_OP_NO_SUBC_PART);

	if (data != NULL)
		pcb_data_clip_inhibit_inc(data);


	/* check lines */
	if (type & PCB_OBJ_LINE && F->Line) {
		PCB_LINE_VISIBLE_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, line))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, line))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, line))
				continue;
			if (no_subc_part && (pcb_lobj_parent_subc(line->parent_type, &line->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(line);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, line);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)line, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)line);
			F->Line(ctx, layer, line);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)line, NULL);
			changed = rnd_true;
		}
		PCB_ENDALL_LOOP;
	}

	/* check arcs */
	if (type & PCB_OBJ_ARC && F->Arc) {
		PCB_ARC_VISIBLE_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, arc))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, arc))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, arc))
				continue;
			if (no_subc_part && (pcb_lobj_parent_subc(arc->parent_type, &arc->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(arc);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, arc);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)arc, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)arc);
			F->Arc(ctx, layer, arc);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)arc, NULL);
			changed = rnd_true;
		}
		PCB_ENDALL_LOOP;
	}

	/* check text */
	if (type & PCB_OBJ_TEXT && F->Text) {
		PCB_TEXT_ALL_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, text) || !pcb_text_is_visible(PCB, layer, text))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, text))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, text))
				continue;
			if (no_subc_part && (pcb_lobj_parent_subc(text->parent_type, &text->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(text);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, text);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)text, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)text);
			F->Text(ctx, layer, text);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)text, NULL);
			changed = rnd_true;
		}
		PCB_ENDALL_LOOP;
	}

	/* check polygons */
	if (type & PCB_OBJ_POLY && F->Polygon) {
		PCB_POLY_VISIBLE_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, polygon))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, polygon))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, polygon))
				continue;
			if (no_subc_part && (pcb_lobj_parent_subc(polygon->parent_type, &polygon->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(polygon);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, polygon);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)polygon, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)polygon);
			F->Polygon(ctx, layer, polygon);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)polygon, NULL);
			changed = rnd_true;
		}
		PCB_ENDALL_LOOP;
	}

	/* check gfx */
	if (type & PCB_OBJ_GFX && F->Gfx) {
		PCB_GFX_VISIBLE_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, gfx))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, gfx))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, gfx))
				continue;
			if (no_subc_part && (pcb_lobj_parent_subc(gfx->parent_type, &gfx->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(gfx);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, gfx);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)gfx, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)gfx);
			F->Gfx(ctx, layer, gfx);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)gfx, NULL);
			changed = rnd_true;
		}
		PCB_ENDALL_LOOP;
	}

	if ((type & (PCB_OBJ_SUBC | PCB_OBJ_SUBC_PART)) && F->subc) {
		PCB_SUBC_LOOP(data);
		{
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, subc) && (subc->extobj == NULL)) /* extobj: even locked means floaters can be operated on */
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, subc))
				continue;
			if (no_subc_part && (pcb_gobj_parent_subc(subc->parent_type, &subc->parent) != NULL))
				continue;
			if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, subc)) {
				if (Reset) {
					pcb_undo_add_obj_to_flag(subc);
					PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, subc);
				}
				if (F->common_pre != NULL)
					if (F->common_pre(ctx, (pcb_any_obj_t *)subc, NULL) == 1) continue;
				if (F->extobj_inhibit_regen)
					exto = NULL;
				else
					exto = pcb_extobj_float_pre((pcb_any_obj_t *)subc);
				F->subc(ctx, subc);
				if (exto != NULL) pcb_extobj_float_geo(exto);
				if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)subc, NULL);
				changed = rnd_true;

				if (pcb_selected_operation_(pcb, subc->data, F, ctx, Reset, type, mode, 1))
					changed = rnd_true;
			}
			else if ((pcb->loose_subc) || (type & PCB_OBJ_SUBC_PART) || (subc->extobj != NULL)) {
				if (pcb_selected_operation_(pcb, subc->data, F, ctx, Reset, type, mode, 0))
					changed = rnd_true;
			}
			else
				if (pcb_selected_operation_(pcb, subc->data, F, ctx, Reset, type, mode, 1))
					changed = rnd_true;
		}
		PCB_END_LOOP;
	}
	else { /* there can be selected objects within subcircuits - but deal only with floaters */
		PCB_SUBC_LOOP(data);
		{
			if (pcb_selected_operation_(pcb, subc->data, F, ctx, Reset, type, mode, 1))
				changed = rnd_true;
		}
		PCB_END_LOOP;
	}

	/* process padstacks */
	if (type & PCB_OBJ_PSTK && pcb->pstk_on && F->padstack) {
		PCB_PADSTACK_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, padstack))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, padstack))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, padstack))
				continue;
			if (no_subc_part && (pcb_gobj_parent_subc(padstack->parent_type, &padstack->parent) != NULL))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(padstack);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, padstack);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)padstack, NULL) == 1) continue;
			if (F->extobj_inhibit_regen)
				exto = NULL;
			else
				exto = pcb_extobj_float_pre((pcb_any_obj_t *)padstack);
			F->padstack(ctx, padstack);
			if (exto != NULL) pcb_extobj_float_geo(exto);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)padstack, NULL);
			changed = rnd_true;
		}
		PCB_END_LOOP;
	}

	/* and rat-lines */
	if (type & PCB_OBJ_RAT && pcb->RatOn && F->Rat) {
		PCB_RAT_LOOP(data);
		{
			if (!PCB_FLAG_TEST(PCB_FLAG_SELECTED, line))
				continue;
			if (!on_locked_too && PCB_FLAG_TEST(PCB_FLAG_LOCK, line))
				continue;
			if (floater_only && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, line))
				continue;
			if (Reset) {
				pcb_undo_add_obj_to_flag(line);
				PCB_FLAG_CLEAR(PCB_FLAG_SELECTED, line);
			}
			if (F->common_pre != NULL)
				if (F->common_pre(ctx, (pcb_any_obj_t *)line, NULL) == 1) continue;
			F->Rat(ctx, line);
			if (F->common_post != NULL) F->common_post(ctx, (pcb_any_obj_t *)line, NULL);
			changed = rnd_true;
		}
		PCB_END_LOOP;
	}

	if (Reset && changed)
		pcb_undo_inc_serial();

	if (data != NULL)
		pcb_data_clip_inhibit_dec(data, 0);

	return changed;
}

rnd_bool pcb_selected_operation(pcb_board_t *pcb, pcb_data_t *data, pcb_opfunc_t *F, pcb_opctx_t *ctx, rnd_bool Reset, int type, pcb_op_mode_t mode)
{
	return pcb_selected_operation_(pcb, data, F, ctx, Reset, type, mode, 0);
}

