// McpMarshal.cpp: implementation of the MCP UI-thread marshalling glue.
//

#include "pch.h"
#include "McpMarshal.h"

namespace mcp
{
	namespace
	{
		HWND  g_hTargetWnd{ nullptr };
		DWORD g_dwUiThreadId{ 0 };
	}

	void SetTargetWindow(HWND hWnd)
	{
		g_hTargetWnd = hWnd;
		g_dwUiThreadId = hWnd ? ::GetCurrentThreadId() : 0;
	}

	bool IsReady()
	{
		return g_hTargetWnd != nullptr;
	}

	void Dispatch(McpCommand *pCmd)
	{
		if (!pCmd)
		{
			return;
		}

		try
		{
			if (pCmd->fn)
			{
				pCmd->fn();
			}
		}
		catch (...)
		{
			pCmd->err = std::current_exception();
		}

		::SetEvent(pCmd->hDone);
	}

	bool Run(std::function<void()> fn)
	{
		const HWND hWnd = g_hTargetWnd;

		if (!hWnd)
		{
			return false;
		}

		// Already on the UI thread: running inline avoids posting a message to
		// ourselves and then blocking forever waiting for it to be handled.
		if (::GetCurrentThreadId() == g_dwUiThreadId)
		{
			if (fn)
			{
				fn();   // exceptions propagate to the caller directly
			}

			return true;
		}

		McpCommand cmd;
		cmd.fn = std::move(fn);
		cmd.hDone = ::CreateEvent(nullptr, FALSE /*auto-reset*/, FALSE, nullptr);

		if (!cmd.hDone)
		{
			return false;
		}

		// PostMessage (not SendMessage): SendMessage from a worker thread blocks
		// inside USER32 in a way that can interfere with the UI message pump; an
		// explicit post + event wait keeps the handshake under our control.
		if (!::PostMessage(hWnd, WM_MCP_EXEC, 0, reinterpret_cast<LPARAM>(&cmd)))
		{
			::CloseHandle(cmd.hDone);
			return false;
		}

		// Block until the UI thread has run the command. The wait is unbounded
		// on purpose: `cmd` lives on this stack and `fn`'s by-reference captures
		// must stay valid until it has executed, so we cannot return early.
		// (A cancellable, bounded wait can be layered on later if a wedged UI
		// ever proves to be a problem - see mcp/README.md.)
		::WaitForSingleObject(cmd.hDone, INFINITE);
		::CloseHandle(cmd.hDone);

		if (cmd.err)
		{
			std::rethrow_exception(cmd.err);
		}

		return true;
	}
}
