
#include "stdafx.h"
#include <assert.h>
#include <Shellapi.h>
#include <Objbase.h>
#include "DLUConv.h"

HFONT _hBaseFont = 0;
LPCWSTR _pszClass = L"StartNRT";
LRESULT CALLBACK WP(HWND, UINT, WPARAM, LPARAM);
int _progress = 0;

DWORD WINAPI TP(LPVOID param)
{
	HANDLE hTargetProcess = (HANDLE)param;
	WaitForInputIdle(hTargetProcess, (DWORD)-1);
	return 0;
}

int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int)
{
	assert(sizeof(DWORD) == sizeof(unsigned int));
	srand(GetTickCount());

	// Make base font for DLU sizing and alignment
	LOGFONT lfBaseFont = {0};
	HFONT hStockFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
	GetObject(hStockFont, sizeof(lfBaseFont), &lfBaseFont);
#ifdef _DEBUG
	LOGFONT lfNull = {0};
	assert(memcmp(&lfBaseFont, &lfNull, sizeof(lfBaseFont)) != 0);
#endif
	_hBaseFont = CreateFontIndirect(&lfBaseFont);
	assert(_hBaseFont != 0);

	// Register window class
	WNDCLASS wc = {0};
	wc.hCursor = LoadCursor(0, IDC_APPSTARTING);
	wc.hInstance = hInst;
	wc.lpszClassName = _pszClass;
	wc.lpfnWndProc = WP;
	ATOM atom = RegisterClass(&wc);
	assert(atom != 0);

	//
	// Start the main application
	// Note: according to MS COM should always be initialised
	//  if using Shell functions such as ShellExecuteEx()
	//
	WCHAR szPathName[33792] = {0};
	GetModuleFileName(0, szPathName, sizeof(szPathName) / sizeof(WCHAR));
	WCHAR *p = wcsrchr(szPathName, L'\\');
	assert(p != 0);
	wcscpy(p, L"\\NetworkResponseTester.exe");
	CoInitializeEx(0, COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE);
	SHELLEXECUTEINFO i = {0};
	i.cbSize = sizeof(i);
	i.fMask = SEE_MASK_NOCLOSEPROCESS|SEE_MASK_FLAG_NO_UI;
	i.lpVerb = L"Open";
	i.lpFile = szPathName;
	i.nShow = SW_SHOW;
	ShellExecuteEx(&i);
	assert(i.hProcess != 0);

	//
	// Create secondary thread to exist parallel to
	//  process becomming input-idle
	//
	DWORD dwID = 0;
	HANDLE hThread = CreateThread(0, 0, &TP, i.hProcess, CREATE_SUSPENDED, &dwID);
	assert(hThread != 0);
	SetThreadPriority(hThread, THREAD_PRIORITY_BELOW_NORMAL);
	ResumeThread(hThread);

	// Make (one-and-only) window
	const int cxDLU = 190;
	const int cyDLU = 80;
	HWND hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, _pszClass, L"", WS_POPUP|WS_DLGFRAME, 0, 0, 
		DLUConv::DLU2PxX(_hBaseFont, cxDLU), DLUConv::DLU2PxY(_hBaseFont, cyDLU), 0, 0, hInst, 0);
	assert(hwnd != 0);

	// Show and position window
	RECT rc;
	int cxPxS = GetSystemMetrics(SM_CXSCREEN);
	int cyPxS = GetSystemMetrics(SM_CYSCREEN);
	GetWindowRect(hwnd, &rc);
	OffsetRect(&rc, -rc.left, -rc.top);
	MoveWindow(hwnd, (cxPxS - rc.right) / 2, (cyPxS - rc.bottom) / 2, rc.right, rc.bottom, 0);
	ShowWindow(hwnd, SW_SHOWNORMAL);
	UpdateWindow(hwnd);

	// Move window to top of non-topmost z-order range
	SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);

	// Message loop
	for(;;)
	{
		//
		// Block thread until either secondary thread has finished 
		//  (meaning target process is input idle) or any system/app
		//  event requiring a call to one of the message retrieval
		//  functions
		// Note: PeekMessage() facilitates everything GetMessage() does
		//  so anything the system needs to do as in a normal message loop
		//  is fine
		//
		DWORD res = MsgWaitForMultipleObjects(1, &hThread, 0, (DWORD)-1, QS_ALLINPUT);
		if(res == WAIT_OBJECT_0)
		{
			// Since thread has finished we want to show 100%
			//  so we set to 99 to cause next timer-induced
			//  redraw to increase directly to 100%; if we set
			//  to 100% here, no redraw would happen
			if(_progress < 99)
				_progress = 99;

			const DWORD dwFinal = 2000;

			//
			// Once 100% is reached, maintain the display
			//  for [dwFinal] milliseconds
			//
			if(_progress == 100)
			{
				static BYTE bDone = 0;
				static DWORD dwDoneAt = 0;
				if(!bDone)
				{
					bDone = 1;
					dwDoneAt = GetTickCount();
				}

				// Let user see 100% 
				if(GetTickCount() - dwDoneAt > dwFinal)
				{
					SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
					DestroyWindow(hwnd);
					break;
				}
			}
		}

		MSG msg = {0};
		while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			// Ignore all input messages
			if(msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
				continue;
			if(msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
				continue;

			// No need for TranslateMessage() 
			//  since we are ignoring input
			DispatchMessage(&msg);
		}
	}

	// Clean up
	CloseHandle(hThread);
	CloseHandle(i.hProcess);
	DeleteObject(_hBaseFont);
	return 0;
}

void _Draw(HWND hwnd, HDC hdc)
{
	_progress += (rand() % 4);
	if(_progress > 100)
		_progress = 100;

	RECT rc = {0};
	GetClientRect(hwnd, &rc);
	HDC mdc = CreateCompatibleDC(0);
	HBITMAP mbm = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
	HBITMAP prevbm = (HBITMAP)SelectObject(mdc, mbm);
	BitBlt(mdc, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY);

	int cxb = GetSystemMetrics(SM_CXBORDER);
	int cyb = GetSystemMetrics(SM_CYBORDER);
	RECT rcFill = rc;
	rcFill.left += cxb;
	rcFill.top += cyb;
	rcFill.bottom -= cyb;
	rcFill.right -= cxb;
	FillRect(mdc, &rcFill, GetSysColorBrush(COLOR_WINDOW));

	RECT rcProgress = rc;
	rcProgress.left += DLUConv::DLU2PxX(_hBaseFont, 7);
	rcProgress.right -=  DLUConv::DLU2PxX(_hBaseFont, 7);
	rcProgress.bottom -= DLUConv::DLU2PxY(_hBaseFont, 7);
	rcProgress.top = rcProgress.bottom - DLUConv::DLU2PxY(_hBaseFont, 10);

	SelectObject(mdc, (HPEN)GetStockObject(BLACK_PEN));
	SelectObject(mdc, (HBRUSH)GetStockObject(WHITE_BRUSH));
	Rectangle(mdc, rcProgress.left, rcProgress.top, rcProgress.right, rcProgress.bottom);

	int xoff = rcProgress.left;
	OffsetRect(&rcProgress, -xoff, 0);
	rcProgress.right *= _progress;
	rcProgress.right /= 100;
	OffsetRect(&rcProgress, xoff, 0);
	SelectObject(mdc, GetSysColorBrush(COLOR_HIGHLIGHT));
	Rectangle(mdc, rcProgress.left, rcProgress.top, rcProgress.right, rcProgress.bottom);

	SelectObject(mdc, _hBaseFont);
	SetBkMode(mdc, TRANSPARENT);
	SetTextColor(mdc, GetSysColor(COLOR_WINDOWTEXT));
	DrawText(mdc, L"\r\n  Network Response Tester\r\n  Copyright © Chris Millward 2011"
		L"\r\n  www.chrismillward.com", -1, &rc, DT_WORDBREAK|DT_TOP);
	SelectObject(mdc, (HFONT)GetStockObject(SYSTEM_FONT));
	SelectObject(mdc, (HBRUSH)GetStockObject(WHITE_BRUSH));

	BitBlt(hdc, 0, 0, rc.right, rc.bottom, mdc, 0, 0, SRCCOPY);
	SelectObject(mdc, prevbm);
}

//
// WP()
//
LRESULT CALLBACK WP(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	switch(iMsg)
	{
		case WM_CREATE:
			SetTimer(hwnd, 1, 100, 0);
			break;
		case WM_ERASEBKGND:
			// Cheap enough to draw twice
			PostMessage(hwnd, WM_USER, 0, 0);
			_Draw(hwnd, (HDC)wParam);
			return 1;
		case WM_PAINT:
		{
			// Bypass any DWM-Windows caching
			//  by always direct drawing
			PostMessage(hwnd, WM_USER, 0, 0);
			PAINTSTRUCT ps = {0};
			BeginPaint(hwnd, &ps);
			EndPaint(hwnd, &ps);
			return 0;
		}
		case WM_USER:
		{
			// Purge message queue of duplicate calls
			MSG msg = {0};
			while(PeekMessage(&msg, hwnd, iMsg, iMsg, PM_REMOVE));

      // Whole client area is about to be drawn
      //  so no reason for any 'pending WM_PAINT'
      //  to get through
      ValidateRect(hwnd, 0);

			HDC hdc = GetDC(hwnd);
			_Draw(hwnd, hdc);
			ReleaseDC(hwnd, hdc);
			return 0;
		}
		case WM_TIMER:
			PostMessage(hwnd, WM_USER, 0, 0);
			return 0;
		case WM_CLOSE:
			return 0;
		case WM_DESTROY:
			KillTimer(hwnd, 1);
			PostQuitMessage(0);
			return 0;
	}

	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}


