/*
 * Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <type_traits>

#include <ox/fs/fs.hpp>

#include <studio/undostack.hpp>

#include <jasper/world/worldobject.hpp>

#include "commands.hpp"

namespace jasper::world {

template<
	WorldObjCommand cmd,
	auto getVar,
	auto hasMerge = false,
	typename ValType = std::remove_reference_t<decltype(getVar(*new WorldObject))>>
class EditObject: public studio::UndoCommand {
	private:
		WorldObjectSet &m_doc;
		size_t const m_objIdx{};
		ValType const m_oldVal{};
		ValType m_newVal{};
	public:
		EditObject(WorldObjectSet &doc, size_t const objIdx, ValType newVal) noexcept:
				m_doc{doc},
				m_objIdx{objIdx},
				m_oldVal{getVar(m_doc.objects[objIdx])},
				m_newVal{std::move(newVal)} {
			setObsolete(m_oldVal == m_newVal);
		}

		ox::Error redo() noexcept override {
			auto &obj = m_doc.objects[m_objIdx];
			if constexpr(!std::is_trivially_copy_assignable_v<ValType>) {
				getVar(obj) = std::move(m_newVal);
			} else {
				getVar(obj) = m_newVal;
			}
			return {};
		}

		ox::Error undo() noexcept override {
			auto &obj = m_doc.objects[m_objIdx];
			if constexpr(!std::is_trivially_copy_assignable_v<ValType>) {
				m_newVal = std::move(getVar(obj));
			}
			getVar(obj) = m_oldVal;
			return {};
		}

		[[nodiscard]]
		int commandId() const noexcept override {
			return static_cast<int>(cmd);
		}

		bool mergeWith(UndoCommand &undoCmd) noexcept override {
			if constexpr(hasMerge) {
				auto const c = dynamic_cast<EditObject const*>(&undoCmd);
				if (c && m_objIdx == c->m_objIdx) {
					m_newVal = std::move(c->m_newVal);
					return true;
				}
			}
			return false;
		}

};


using EditObjectName = EditObject<
	WorldObjCommand::EditObjectName,
	[](WorldObject &obj) -> auto& {
		return obj.name;
	},
	true>;

using EditObjectFrames = EditObject<
	WorldObjCommand::EditObjectFrames,
	[](WorldObject &obj) -> auto& {
		return obj.frames;
	},
	true>;

using EditObjectInterval = EditObject<
	WorldObjCommand::EditObjectInterval,
	[](WorldObject &obj) -> auto& {
		return obj.intervalMs;
	},
	true>;

using EditObjectPalette = EditObject<
	WorldObjCommand::EditObjectPalette,
	[](WorldObject &obj) -> auto& {
		return obj.palBank;
	}>;

using EditObjectCollisionMap = EditObject<
	WorldObjCommand::EditObjectCollisionMap,
	[](WorldObject &obj) -> auto& {
		return obj.collisionMap;
	}>;


class EditObjectSubSheet: public studio::UndoCommand {
	private:
		WorldObjectSet &m_doc;
		size_t const m_objIdx{};
		ngfx::SubSheetId const m_oldSubSheet{};
		ngfx::SubSheetId m_newSubSheet{};
		uint8_t const m_oldFrames{};
		uint8_t m_newFrames{};
	public:
		EditObjectSubSheet(
			WorldObjectSet &doc,
			size_t objIdx,
			ngfx::SubSheetId newSubSheet,
			uint8_t newFrames);

		ox::Error redo() noexcept override;

		ox::Error undo() noexcept override;

		[[nodiscard]]
		int commandId() const noexcept override;
};

}
