#include "stdafx.h"
#include "MessageBox.h"

namespace Forms
{
	DialogResult MessageBox::Win32ToDialogResult(uint32_t Value)
	{
		switch (Value)
		{
		case IDOK:
			return DialogResult::OK;
		case IDCANCEL:
			return DialogResult::Cancel;
		case IDABORT:
			return DialogResult::Abort;
		case IDRETRY:
			return DialogResult::Retry;
		case IDIGNORE:
			return DialogResult::Ignore;
		case IDYES:
			return DialogResult::Yes;
		case IDNO:
			return DialogResult::No;
		default:
			return DialogResult::No;
		}
	}

	static Drawing::Color ForegroundColor = Drawing::Color::Black;
	static Drawing::Color BackgroundColor = Drawing::Color::White;
	static Drawing::Color BottomColor = Drawing::Color(240, 240, 240);

	struct Win32MessageBoxThreadInfo
	{
		HHOOK hHook;				// Hook handle
		HWND  hWnd;					// Message box handle
		WNDPROC lpMsgBoxProc;		// Original window proc
		COLORREF crText;			// Foreground color
		COLORREF crBack;			// Background color
		COLORREF crBottom;			// Background bottom color
		HBRUSH hBrush;				// Background brush
		HBRUSH hBrushBottom;		// Background bottom brush
	} static thread_local Win32PerThreadInfo;

	LRESULT CALLBACK Win32MessageBoxProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		switch (uMsg)
		{
		case WM_INITDIALOG:
		{
			Win32PerThreadInfo.hBrush = ::CreateSolidBrush(Win32PerThreadInfo.crBack);
			Win32PerThreadInfo.hBrushBottom = ::CreateSolidBrush(Win32PerThreadInfo.crBottom);
		}
		break;
		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
		{
			HDC hDC = (HDC)wParam;

			::SetBkMode(hDC, TRANSPARENT);
			::SetTextColor(hDC, Win32PerThreadInfo.crText);

			return (LRESULT)Win32PerThreadInfo.hBrush;
		}
		case WM_CTLCOLORBTN:
		{
			return (LRESULT)Win32PerThreadInfo.hBrushBottom;
		}
		case WM_PAINT:
		{
			auto Result = CallWindowProc(Win32PerThreadInfo.lpMsgBoxProc, hWnd, uMsg, wParam, lParam);

			RECT Rc{};
			GetClientRect(hWnd, &Rc);

			auto DC = GetDC(hWnd);

			FillRect(DC, &Rc, Win32PerThreadInfo.hBrush);

			Rc.top = Rc.bottom - 42;

			FillRect(DC, &Rc, Win32PerThreadInfo.hBrushBottom);

			return Result;
		}
		case WM_COMMAND:
		{
			::DeleteObject(Win32PerThreadInfo.hBrush);
			::DeleteObject(Win32PerThreadInfo.hBrushBottom);
		}
		break;
		}

		return CallWindowProc(Win32PerThreadInfo.lpMsgBoxProc, hWnd, uMsg, wParam, lParam);
	}

	LRESULT CALLBACK Win32CallbackProc(int nCode, WPARAM wParam, LPARAM lParam)
	{
		if (nCode < 0)
			return ::CallNextHookEx(Win32PerThreadInfo.hHook, nCode, wParam, lParam);

		switch (nCode)
		{
		case HCBT_CREATEWND:
		{
			// Ensure that we have the right window to track
			LPCBT_CREATEWND lpCbtCreate = (LPCBT_CREATEWND)lParam;
			if (WC_DIALOG == lpCbtCreate->lpcs->lpszClass)
			{
				Win32PerThreadInfo.hWnd = (HWND)wParam;
			}
			else
			{
				if ((NULL == Win32PerThreadInfo.lpMsgBoxProc) && (NULL != Win32PerThreadInfo.hWnd))
					Win32PerThreadInfo.lpMsgBoxProc = (WNDPROC)::SetWindowLongPtr(Win32PerThreadInfo.hWnd, GWLP_WNDPROC, (LONG_PTR)Win32MessageBoxProc);
			}
		}
		break;
		case HCBT_DESTROYWND:
		{
			// Ensure that our tracked window is being destroyed
			if (Win32PerThreadInfo.hWnd == (HWND)wParam)
				::SetWindowLongPtr(Win32PerThreadInfo.hWnd, GWLP_WNDPROC, (LONG_PTR)Win32PerThreadInfo.lpMsgBoxProc);
		}
		}

		return 0;
	}

	DialogResult MessageBox::ShowCore(const String& Text, const String& Caption, MessageBoxButtons Buttons, MessageBoxIcon Icon, MessageBoxDefaultButton DefaultButton, MessageBoxOptions Options, bool ShowHelp, Control* Owner)
	{
		uint32_t Style = (ShowHelp) ? HELP_BUTTON : 0;
		Style |= (int)Buttons | (int)Icon | (int)DefaultButton | (int)Options;

		HWND Handle = nullptr;

		if (ShowHelp || (((int)Options & ((int)MessageBoxOptions::ServiceNotification | (int)MessageBoxOptions::DefaultDesktopOnly)) == 0))
		{
			if (Owner == nullptr)
				Handle = GetActiveWindow();
			else
				Handle = Owner->GetHandle();
		}

		// Reset hooking data for this thread
		Win32PerThreadInfo.hHook = NULL;
		Win32PerThreadInfo.hWnd = NULL;
		Win32PerThreadInfo.lpMsgBoxProc = NULL;

		// Load the initial color values
		Win32PerThreadInfo.crText = RGB(ForegroundColor.GetR(), ForegroundColor.GetG(), ForegroundColor.GetB());
		Win32PerThreadInfo.crBack = RGB(BackgroundColor.GetR(), BackgroundColor.GetG(), BackgroundColor.GetB());
		Win32PerThreadInfo.crBottom = RGB(BottomColor.GetR(), BottomColor.GetG(), BottomColor.GetB());

		// Install the hook
		Win32PerThreadInfo.hHook = ::SetWindowsHookExA(WH_CBT, Win32CallbackProc, GetModuleHandle(NULL), GetCurrentThreadId());

		auto Result = MessageBoxA(Handle, (const char*)Text, (const char*)Caption, Style);

		// Remove the hook
		::UnhookWindowsHookEx(Win32PerThreadInfo.hHook);

		// Convert the result
		return Win32ToDialogResult(Result);
	}

	DialogResult MessageBox::Show(const String& Text)
	{
		return ShowCore(Text, "", MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0, false, nullptr);
	}

	DialogResult MessageBox::Show(const String& Text, const String& Caption)
	{
		return ShowCore(Text, Caption, MessageBoxButtons::OK, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0, false, nullptr);
	}

	DialogResult MessageBox::Show(const String& Text, const String& Caption, MessageBoxButtons Buttons)
	{
		return ShowCore(Text, Caption, Buttons, MessageBoxIcon::None, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0, false, nullptr);
	}

	DialogResult MessageBox::Show(const String& Text, const String& Caption, MessageBoxButtons Buttons, MessageBoxIcon Icon)
	{
		return ShowCore(Text, Caption, Buttons, Icon, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0, false, nullptr);
	}

	DialogResult MessageBox::Show(Control* Owner, const String& Text, const String& Caption, MessageBoxButtons Buttons, MessageBoxIcon Icon)
	{
		return ShowCore(Text, Caption, Buttons, Icon, MessageBoxDefaultButton::Button1, (MessageBoxOptions)0, false, Owner);
	}

	DialogResult MessageBox::Show(Control* Owner, const String& Text, const String& Caption, MessageBoxButtons Buttons, MessageBoxIcon Icon, MessageBoxDefaultButton DefaultButton)
	{
		return ShowCore(Text, Caption, Buttons, Icon, DefaultButton, (MessageBoxOptions)0, false, Owner);
	}

	DialogResult MessageBox::Show(Control* Owner, const String& Text, const String& Caption, MessageBoxButtons Buttons, MessageBoxIcon Icon, MessageBoxDefaultButton DefaultButton, MessageBoxOptions Options)
	{
		return ShowCore(Text, Caption, Buttons, Icon, DefaultButton, Options, false, Owner);
	}

	void MessageBox::SetMessageBoxColors(Drawing::Color Foreground, Drawing::Color Background, Drawing::Color Bottom)
	{
		ForegroundColor = Foreground;
		BackgroundColor = Background;
		BottomColor = Bottom;
	}
}
