/*
	Copyright (C) 2017 - 2024
	Part of the Battle for Wesnoth Project https://www.wesnoth.org/

	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.

	See the COPYING file for more details.
*/

#include "actions/undo_move_action.hpp"
#include "actions/move.hpp"

#include "resources.hpp"
#include "replay.hpp"
#include "units/map.hpp"
#include "units/animation_component.hpp"
#include "log.hpp"
#include "game_display.hpp"
#include "units/udisplay.hpp"
#include "units/unit.hpp"
#include "game_board.hpp"
#include "map/map.hpp"

static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)

namespace actions
{
namespace undo
{

move_action::move_action(const unit_const_ptr moved,
			const std::vector<map_location>::const_iterator & begin,
			const std::vector<map_location>::const_iterator & end,
			int sm, int timebonus, int orig, const map_location::DIRECTION dir)
	: undo_action()
	, shroud_clearing_action(moved, begin, end, orig, timebonus != 0)
	, starting_moves(sm)
	, starting_dir(dir == map_location::NDIRECTIONS ? moved->facing() : dir)
	, goto_hex(moved->get_goto())
{
}

/**
 * Writes this into the provided config.
 */
void move_action::write(config & cfg) const
{
	undo_action::write(cfg);
	shroud_clearing_action::write(cfg);
	cfg["starting_direction"] = map_location::write_direction(starting_dir);
	cfg["starting_moves"] = starting_moves;
	config & child = cfg.mandatory_child("unit");
	child["goto_x"] = goto_hex.wml_x();
	child["goto_y"] = goto_hex.wml_y();
}

/**
 * Reset halo of adjacent units when undo move.
 */
static void reset_adjacent(bool& halo_adjacent, unit_map& units, const map_location& loc)
{
	if(halo_adjacent){
		unit_map::iterator u = units.find(loc);
		const auto adjacent = get_adjacent_tiles(loc);
		for(unsigned i = 0; i < adjacent.size(); ++i) {
			const unit_map::const_iterator it = units.find(adjacent[i]);
			if (it == units.end() || it->incapacitated())
				continue;
			if ( &*it == &*u )
				continue;
			it->anim_comp().set_standing();
		}
	}
}

/**
 * Undoes this action.
 * @return true on success; false on an error.
 */
bool move_action::undo(int)
{
	game_display & gui = *game_display::get_singleton();
	unit_map &   units = resources::gameboard->units();

	// Copy some of our stored data.
	const int saved_moves = starting_moves;
	std::vector<map_location> rev_route = route;
	std::reverse(rev_route.begin(), rev_route.end());

	// Check units.
	unit_map::iterator u = units.find(rev_route.front());
	const unit_map::iterator u_end = units.find(rev_route.back());
	if ( u == units.end()  ||  u_end != units.end() ) {
		//this can actually happen if the scenario designer has abused the [allow_undo] command
		ERR_NG << "Illegal 'undo' found. Possible abuse of [allow_undo]?";
		return false;
	}
	this->return_village();

	// Record the unit's current state so it can be redone.
	starting_moves = u->movement_left();
	goto_hex = u->get_goto();

	// Move the unit.
	unit_display::move_unit(rev_route, u.get_shared_ptr(), true, starting_dir);
	bool halo_adjacent = false;
	for (const config::any_child sp : u->abilities().all_children_range()){
		if(!(sp.cfg)["halo_image"].empty() && (sp.cfg).has_child("affect_adjacent")){
			halo_adjacent = true;
			break;
		}
	}
	reset_adjacent(halo_adjacent, units, rev_route.front());
	units.move(u->get_location(), rev_route.back());
	unit::clear_status_caches();
	reset_adjacent(halo_adjacent, units, rev_route.back());

	// Restore the unit's old state.
	u = units.find(rev_route.back());
	u->set_goto(map_location());
	u->set_movement(saved_moves, true);
	u->anim_comp().set_standing();

	gui.invalidate_unit_after_move(rev_route.front(), rev_route.back());
	execute_undo_umc_wml();
	return true;
}


}
}
