/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2022,2025 Tibor 'Igor2' Palinkas
 *  Copyright (C) 1994,1995,1996 Thomas Nau (from pcb-rnd)
 *
 *  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., 31 Milk Street, # 960789 Boston, MA 02196 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
 */

#include "config.h"

#include <librnd/core/error.h>
#include <librnd/hid/hid.h>
#include <librnd/core/actions.h>
#include <librnd/hid/tool.h>
#include <librnd/core/compat_misc.h>
#include "event.h"
#include "cnc_obj.h"

#include "undo.h"

int csch_undo(csch_sheet_t *sheet)
{
	int res;

	if (sheet->undo.num_undo == 0) {
		rnd_message(RND_MSG_INFO, "Nothing to undo - buffer is empty\n");
		return -1;
	}

	if (sheet->undo.serial == 0) {
		rnd_message(RND_MSG_ERROR, "ERROR: Attempt to csch_undo() with Serial == 0\n       Please save your work and report this bug.\n");
		return -1;
	}

	if ((sheet->undo.tail != NULL) && (sheet->undo.tail->serial > sheet->undo.serial)) {
		rnd_message(RND_MSG_ERROR, "ERROR: Bad undo serial number %d in undo stack - expecting %d or lower\n"
			"       Please save your work and report this bug.\n", sheet->undo.tail->serial, sheet->undo.serial);

	/* It is likely that the serial number got corrupted through some bad
	   use of the save_serial() / restore_serial() APIs.
	   Reset the serial number to be consistent with that of the last
	   operation on the undo stack in the hope that this might clear
	   the problem and  allow the user to hit Undo again. */
		sheet->undo.serial = sheet->undo.tail->serial + 1;
		return -1;
	}

	csch_cobj_redraw_freeze(sheet);
	res = uundo_undo(&sheet->undo);
	csch_cobj_redraw_unfreeze(sheet);

	if (res != 0)
		rnd_message(RND_MSG_ERROR, "ERROR: Failed to undo some operations\n");

	rnd_event(&sheet->hidlib, CSCH_EVENT_UNDO_POST, "i", CSCH_UNDO_EV_UNDO);

	return res;
}


int csch_redo(csch_sheet_t *sheet)
{
	int res;

	if (sheet->undo.num_redo == 0) {
		rnd_message(RND_MSG_INFO, "Nothing to redo. Perhaps changes have been made since last undo\n");
		return 0;
	}

	if ((sheet->undo.tail != NULL) && (sheet->undo.tail->next != NULL) && (sheet->undo.tail->next->serial > sheet->undo.serial)) {

		rnd_message(RND_MSG_ERROR, "ERROR: Bad undo serial number %d in redo stack - expecting %d or higher\n"
							"       Please save your work and report this bug.\n", sheet->undo.tail->next->serial, sheet->undo.serial);

		/* It is likely that the serial number got corrupted through some bad
		 * use of the save_serial() / restore_serial() APIs.
		 *
		 * Reset the serial number to be consistent with that of the first
		 * operation on the redo stack in the hope that this might clear
		 * the problem and  allow the user to hit Redo again.
		 */

		sheet->undo.serial = sheet->undo.tail->next->serial;
		return 0;
	}

	csch_cobj_redraw_freeze(sheet);
	res = uundo_redo(&sheet->undo);
	csch_cobj_redraw_unfreeze(sheet);

	if (res != 0)
		rnd_message(RND_MSG_ERROR, "ERROR: Failed to redo some operations\n");

	rnd_event(&sheet->hidlib, CSCH_EVENT_UNDO_POST, "i", CSCH_UNDO_EV_REDO);

	return res;
}


void csch_undo_clear_list(csch_sheet_t *sheet, rnd_bool Force)
{
	if (sheet->undo.num_undo && (Force || rnd_hid_message_box(&sheet->hidlib, "warning", "clear undo buffer", "Do you reall want to clear 'undo' buffer?", "yes", 1, "no", 0, NULL) == 1)) {
		uundo_list_clear(&sheet->undo);
		rnd_event(&sheet->hidlib, CSCH_EVENT_UNDO_POST, "i", CSCH_UNDO_EV_CLEAR_LIST);
	}
}

void csch_undo_inc_serial(csch_sheet_t *sheet)
{
	uundo_inc_serial(&sheet->undo);
	rnd_event(&sheet->hidlib, CSCH_EVENT_UNDO_POST, "i", CSCH_UNDO_EV_REDO);
}


static const char undo_cookie[] = "libcschem/undo.c";

static const char csch_acts_Undo[] = "undo()\n" "undo(ClearList|FreezeSerial|UnfreezeSerial|FreezeAdd|UnfreezeAdd|IncSerial|GetSerial|Above)";
static const char csch_acth_Undo[] = "Undo recent changes.";
/* DOC: undo.html */
fgw_error_t csch_act_Undo(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	const char *function = NULL;
	csch_sheet_t *sheet = CSCH_ACT_SHEET;

	RND_ACT_MAY_CONVARG(1, FGW_STR, Undo, function = argv[1].val.str);
	if (!function || !*function) {
		rnd_hid_notify_crosshair_change(RND_ACT_DESIGN, rnd_false);
		if (rnd_tool_undo_act(RND_ACT_DESIGN))
			if (csch_undo(sheet) == 0) {
TODO("changed:");
/*				csch_board_set_changed_flag(PCB_ACT_BOARD, rnd_true);*/
			}
	}
	else if (function) {
		rnd_hid_notify_crosshair_change(RND_ACT_DESIGN, rnd_false);
		if (rnd_strcasecmp(function, "ClearList") == 0)
			csch_undo_clear_list(sheet, rnd_false);
		else if (rnd_strcasecmp(function, "FreezeSerial") == 0)
			uundo_freeze_serial(&sheet->undo);
		else if (rnd_strcasecmp(function, "UnFreezeSerial") == 0)
			uundo_unfreeze_serial(&sheet->undo);
		else if (rnd_strcasecmp(function, "FreezeAdd") == 0)
			uundo_freeze_add(&sheet->undo);
		else if (rnd_strcasecmp(function, "UnFreezeAdd") == 0)
			uundo_unfreeze_add(&sheet->undo);
		else if (rnd_strcasecmp(function, "IncSerial") == 0)
			csch_undo_inc_serial(sheet);
		else if (rnd_strcasecmp(function, "GetSerial") == 0) {
			res->type = FGW_LONG;
			res->val.nat_long = sheet->undo.serial;
			return 0;
		}
		else if (rnd_strcasecmp(function, "GetNum") == 0) {
			res->type = FGW_LONG;
			res->val.nat_long = sheet->undo.num_undo;
			return 0;
		}
		else if (rnd_strcasecmp(function, "Above") == 0) {
			long ser;
			RND_ACT_CONVARG(2, FGW_LONG, Undo, ser = argv[2].val.nat_long);
			uundo_undo_above(&sheet->undo, ser);
		}

	}
	rnd_hid_notify_crosshair_change(RND_ACT_DESIGN, rnd_true);
	RND_ACT_IRES(0);
	return 0;
}


static const char csch_acts_Redo[] = "redo()";
static const char csch_acth_Redo[] = "Redo recent \"undo\" operations.";
/* DOC: redo.html */
fgw_error_t csch_act_Redo(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_sheet_t *sheet = CSCH_ACT_SHEET;

	rnd_hid_notify_crosshair_change(RND_ACT_DESIGN, rnd_false);
	if (rnd_tool_redo_act(RND_ACT_DESIGN))
		if (csch_redo(sheet)) {
			/* board has changed; but it was handled in the swap code anyway */
		}
	rnd_hid_notify_crosshair_change(RND_ACT_DESIGN, rnd_true);
	RND_ACT_IRES(0);
	return 0;
}

static rnd_action_t csch_undo_act_list[] = {
	{"Undo", csch_act_Undo, csch_acth_Undo, csch_acts_Undo},
	{"Redo", csch_act_Redo, csch_acth_Redo, csch_acts_Redo}
};

void csch_undo_act_init(void)
{
	RND_REGISTER_ACTIONS(csch_undo_act_list, undo_cookie);
}

void csch_undo_act_uninit(void)
{
	rnd_remove_actions_by_cookie(undo_cookie);
}

