/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

/* Low level operation implementations that are common to multiple objects */

#include "config.h"

#include <librnd/core/rnd_printf.h>

#include "event.h"
#include "cnc_obj.h"
#include "operation.h"
#include "cnc_conn.h"
#include "undo.h"
#include "util_wirenet.h"

#include "op_common.h"

static const char csch_operation_cookie[] = "libcschem/op_common.c";

/*** Remove ***/

static int undo_remove_undo(void *udata)
{
	csch_undo_remove_t *u = udata;
	csch_chdr_t *obj = u->obj;
	const csch_ops_t *ops = csch_op_get(obj->type);
	assert(ops->remove_undo != NULL);
	ops->remove_undo(u);
	csch_sheet_set_changed(obj->sheet, 1);
	return 0;
}

static int undo_remove_redo(void *udata)
{
	csch_undo_remove_t *u = udata;
	csch_chdr_t *obj = u->obj;
	const csch_ops_t *ops = csch_op_get(obj->type);
	assert(ops->remove_redo != NULL);

	ops->remove_redo(u);
	csch_sheet_set_changed(obj->sheet, 1);
	return 0;
}

static void undo_crem_print(void *udata, char *dst, size_t dst_len, const char *op)
{
	csch_undo_remove_t *u = udata;
	csch_oidpath_t oidp = {0};
	char *path;
	const char *st = "<unknown>";
	int len;

	csch_oidpath_from_obj(&oidp, u->obj);
	path = csch_oidpath_to_str(&oidp);

	if (u->obj->link.parent == &u->sheet->active) st = "active";
	else if (u->obj->link.parent == &u->sheet->deleted) st = "deleted";

	len = rnd_snprintf(dst, dst_len, "%s: %s %s (%s)", op, csch_ctype_name(u->obj->type), path, st);
	if (csch_obj_is_grp(u->obj)) {
		csch_cgrp_t *grp = (csch_cgrp_t *)u->obj;
		const char *role = csch_attrib_get_str(&grp->attr, "role");
		const char *name = csch_attrib_get_str(&grp->attr, "name");
		
		if (role != NULL) {
			dst_len -= len;
			dst += len;
			rnd_snprintf(dst, dst_len, " role=%s", role);
		}
		if (name != NULL) {
			dst_len -= len;
			dst += len;
			rnd_snprintf(dst, dst_len, " name=%s", name);
		}
	}

	csch_oidpath_free(&oidp);
	free(path);
}


static void undo_remove_print(void *udata, char *dst, size_t dst_len)
{
	undo_crem_print(udata, dst, dst_len, "remove");
}

static const uundo_oper_t undo_remove = {
	csch_operation_cookie,
	NULL, /* free */
	undo_remove_undo,
	undo_remove_redo,
	undo_remove_print
};

csch_undo_remove_t *csch_op_remove_alloc_undo(csch_sheet_t *sheet)
{
	return uundo_append(&sheet->undo, &undo_remove, sizeof(csch_undo_remove_t));
}


csch_chdr_t *csch_cnc_common_remove_redo(csch_undo_remove_t *u, csch_cnc_common_remove_t how)
{
	csch_chdr_t *obj = u->obj;

	if (how & CSCH_REM_FROM_RTREE)
		csch_cobj_rtree_del(obj->sheet, obj);

	if ((how & CSCH_REM_FROM_PARENT) && (obj != &obj->sheet->direct.hdr) && (obj != &obj->sheet->indirect.hdr)) {
		assert(obj->parent != NULL);
		u->parent = obj->parent;
		htip_pop(&obj->parent->id2obj, obj->oid);
		gdl_remove(&u->sheet->active, obj, link);
		gdl_append(&u->sheet->deleted, obj, link);
		obj->parent = NULL;
	}

	csch_cobj_redraw(obj);

	return obj;
}

csch_chdr_t *csch_cnc_common_remove_undo(csch_undo_remove_t *u, csch_cnc_common_remove_t how)
{
	csch_chdr_t *obj = u->obj;

	if (how & CSCH_REM_FROM_PARENT) {
		obj->parent = u->parent;
		assert(obj->parent != NULL);
		htip_insert(&obj->parent->id2obj, obj->oid, obj);
		gdl_remove(&u->sheet->deleted, obj, link);
		gdl_append(&u->sheet->active, obj, link);
	}

	if (how & CSCH_REM_FROM_RTREE)
		csch_cobj_rtree_add(obj->sheet, obj);

	csch_cobj_redraw(obj);

	return obj;
}


/*** Create ***/

static void undo_create_print(void *udata, char *dst, size_t dst_len)
{
	undo_crem_print(udata, dst, dst_len, "create");
}


static const uundo_oper_t undo_create = {
	csch_operation_cookie,
	NULL, /* free */
	undo_remove_redo, /* swapped: create is reverse order remove */
	undo_remove_undo, /* swapped: create is reverse order remove */
	undo_create_print
};


/*** MISC ***/

csch_chdr_t *csch_op_inserted(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_chdr_t *obj)
{
	csch_undo_create_t *u = uundo_append(&sheet->undo, &undo_create, sizeof(csch_undo_create_t));
	u->sheet = sheet;
	u->obj = obj;
	u->parent = obj->parent;
	u->side_effects = 0;

	if (csch_obj_is_grp(obj)) /* e.g. terminal or symbol moved/added in user op */
		csch_conn_auto_recalc(sheet, obj);

	if (obj->parent->role == CSCH_ROLE_WIRE_NET) /* new wire added in a wirenet */
		csch_wirenet_recalc_obj_conn(sheet, obj);

	csch_undo_inc_serial(sheet);
	csch_cobj_redraw(obj);
	return obj;
}
