#include "stdafx.h"
#include "resource.h"
#include "OpenGLComposite.h"

#include <algorithm>
#include <shellapi.h>
#include <string>

#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#endif
#ifndef WGL_CONTEXT_MINOR_VERSION_ARB
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#endif
#ifndef WGL_CONTEXT_PROFILE_MASK_ARB
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#endif
#ifndef WGL_CONTEXT_CORE_PROFILE_BIT_ARB
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#endif

#define MAX_LOADSTRING 100

// Declaration for Window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);

namespace
{
const int kStatusPanelWidth = 680;
const int kStatusPanelHeight = 92;
const int kStatusPadding = 8;
const int kStatusLabelWidth = 58;
const int kStatusButtonWidth = 86;
const int kStatusRowHeight = 24;
const int kStatusGap = 6;
const UINT kCreateStatusStripMessage = WM_APP + 1;

enum StatusControlId
{
	kControlUrlEditId = 2001,
	kDocsUrlEditId = 2002,
	kOscAddressEditId = 2003,
	kOpenControlButtonId = 2004,
	kOpenDocsButtonId = 2005
};

struct StatusStripControls
{
	HWND panel = NULL;
	HWND controlLabel = NULL;
	HWND controlUrl = NULL;
	HWND openControl = NULL;
	HWND docsLabel = NULL;
	HWND docsUrl = NULL;
	HWND openDocs = NULL;
	HWND oscLabel = NULL;
	HWND oscAddress = NULL;
};

bool StatusStripCreated(const StatusStripControls& controls)
{
	return controls.panel != NULL;
}

HWND CreateStatusChild(HWND parent, const char* className, const char* text, DWORD style, DWORD exStyle, int controlId)
{
	return CreateWindowExA(
		exStyle,
		className,
		text,
		WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | style,
		0,
		0,
		0,
		0,
		parent,
		reinterpret_cast<HMENU>(static_cast<INT_PTR>(controlId)),
		reinterpret_cast<HINSTANCE>(GetWindowLongPtr(parent, GWLP_HINSTANCE)),
		NULL);
}

void CreateStatusStrip(HWND hWnd, StatusStripControls& controls)
{
	controls.panel = CreateStatusChild(hWnd, "STATIC", "", SS_LEFT, WS_EX_CLIENTEDGE, 0);
	controls.controlLabel = CreateStatusChild(hWnd, "STATIC", "Control", SS_LEFT, 0, 0);
	controls.controlUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kControlUrlEditId);
	controls.openControl = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON | WS_TABSTOP, 0, kOpenControlButtonId);
	controls.docsLabel = CreateStatusChild(hWnd, "STATIC", "Docs", SS_LEFT, 0, 0);
	controls.docsUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kDocsUrlEditId);
	controls.openDocs = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON | WS_TABSTOP, 0, kOpenDocsButtonId);
	controls.oscLabel = CreateStatusChild(hWnd, "STATIC", "OSC", SS_LEFT, 0, 0);
	controls.oscAddress = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY | WS_TABSTOP, WS_EX_CLIENTEDGE, kOscAddressEditId);

	HFONT guiFont = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
	HWND children[] = {
		controls.controlLabel,
		controls.controlUrl,
		controls.openControl,
		controls.docsLabel,
		controls.docsUrl,
		controls.openDocs,
		controls.oscLabel,
		controls.oscAddress
	};
	for (HWND child : children)
	{
		if (child)
			SendMessage(child, WM_SETFONT, reinterpret_cast<WPARAM>(guiFont), TRUE);
	}

	SetWindowTextA(controls.controlUrl, "Starting control server...");
	SetWindowTextA(controls.docsUrl, "Starting API docs...");
	SetWindowTextA(controls.oscAddress, "Starting OSC listener...");
}

void RaiseStatusControls(const StatusStripControls& controls)
{
	if (!StatusStripCreated(controls))
		return;

	SetWindowPos(controls.panel, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

	HWND interactiveControls[] = {
		controls.controlLabel,
		controls.controlUrl,
		controls.openControl,
		controls.docsLabel,
		controls.docsUrl,
		controls.openDocs,
		controls.oscLabel,
		controls.oscAddress
	};
	for (HWND control : interactiveControls)
	{
		if (control)
			SetWindowPos(control, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
	}
}

void LayoutStatusStrip(HWND hWnd, const StatusStripControls& controls)
{
	RECT clientRect = {};
	if (!GetClientRect(hWnd, &clientRect) || !controls.panel)
		return;

	const int clientWidth = static_cast<int>(clientRect.right - clientRect.left);
	const int clientHeight = static_cast<int>(clientRect.bottom - clientRect.top);
	const int panelWidth = std::max(280, std::min(kStatusPanelWidth, clientWidth - (kStatusPadding * 2)));
	const int panelHeight = kStatusPanelHeight;
	const int panelLeft = kStatusPadding;
	const int panelTop = std::max(kStatusPadding, clientHeight - panelHeight - kStatusPadding);
	MoveWindow(controls.panel, panelLeft, panelTop, panelWidth, panelHeight, TRUE);

	const int rowX = panelLeft + kStatusPadding;
	const int editX = rowX + kStatusLabelWidth + kStatusGap;
	const int buttonX = panelLeft + panelWidth - kStatusPadding - kStatusButtonWidth;
	const int editWidth = std::max(80, buttonX - editX - kStatusGap);
	const int oscWidth = std::max(80, panelLeft + panelWidth - editX - kStatusPadding);
	const int row1 = panelTop + kStatusPadding;
	const int row2 = row1 + kStatusRowHeight + kStatusGap;
	const int row3 = row2 + kStatusRowHeight + kStatusGap;

	MoveWindow(controls.controlLabel, rowX, row1 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.controlUrl, editX, row1, editWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.openControl, buttonX, row1, kStatusButtonWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.docsLabel, rowX, row2 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.docsUrl, editX, row2, editWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.openDocs, buttonX, row2, kStatusButtonWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.oscLabel, rowX, row3 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
	MoveWindow(controls.oscAddress, editX, row3, oscWidth, kStatusRowHeight, TRUE);
	RaiseStatusControls(controls);
}

void UpdateStatusStrip(const StatusStripControls& controls, const OpenGLComposite& composite)
{
	if (!StatusStripCreated(controls))
		return;

	SetWindowTextA(controls.controlUrl, composite.GetControlUrl().c_str());
	SetWindowTextA(controls.docsUrl, composite.GetDocsUrl().c_str());
	SetWindowTextA(controls.oscAddress, composite.GetOscAddress().c_str());
}

void OpenUrl(const char* url)
{
	ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
}
}

void ShowUnhandledExceptionMessage(const char* prefix)
{
	try
	{
		throw;
	}
	catch (const std::exception& exception)
	{
		std::string message = std::string(prefix) + "\n\n" + exception.what();
		MessageBoxA(NULL, message.c_str(), "Unhandled exception", MB_OK | MB_ICONERROR);
	}
	catch (...)
	{
		MessageBoxA(NULL, prefix, "Unhandled exception", MB_OK | MB_ICONERROR);
	}
}

// Select the pixel format for a given device context
void SetDCPixelFormat(HDC hDC)
{
	int nPixelFormat;

	static PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),	// Size of this structure
		1,								// Version of this structure
		PFD_DRAW_TO_WINDOW |			// Draw to Window (not to bitmap)
		PFD_SUPPORT_OPENGL |			// Support OpenGL calls in window
		PFD_DOUBLEBUFFER,				// Double buffered mode
		PFD_TYPE_RGBA,					// RGBA Color mode
		32,								// Want 32 bit color
		0,0,0,0,0,0,					// Not used to select mode
		0,0,							// Not used to select mode
		0,0,0,0,0,						// Not used to select mode
		16,								// Size of depth buffer
		0,								// Not used
		0,								// Not used
		0,								// Not used
		0,								// Not used
		0,0,0 };						// Not used

	// Choose a pixel format that best matches that described in pfd
	nPixelFormat = ChoosePixelFormat(hDC, &pfd);

	// Set the pixel format for the device context
	SetPixelFormat(hDC, nPixelFormat, &pfd);
}

HGLRC CreateModernOpenGLContext(HDC hDC)
{
	PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB =
		reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(wglGetProcAddress("wglCreateContextAttribsARB"));
	if (!wglCreateContextAttribsARB)
		return NULL;

	const int versionCandidates[][2] =
	{
		{ 4, 5 },
		{ 4, 3 },
		{ 3, 3 }
	};

	for (const auto& version : versionCandidates)
	{
		const int attribs[] =
		{
			WGL_CONTEXT_MAJOR_VERSION_ARB, version[0],
			WGL_CONTEXT_MINOR_VERSION_ARB, version[1],
			WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
			0
		};

		HGLRC modernContext = wglCreateContextAttribsARB(hDC, 0, attribs);
		if (modernContext != NULL)
			return modernContext;
	}

	return NULL;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG			msg;									// Windows message structure
	WNDCLASS	wc;										// Windows class structure
	HWND		hWnd;									// Storeage for window handle
	TCHAR		szTitle[MAX_LOADSTRING];				// The title bar text
	TCHAR		szWindowClass[MAX_LOADSTRING];			// the main window class name

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_OPENGLOUTPUT, szWindowClass, MAX_LOADSTRING);

	// Register Window style
	wc.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc		= (WNDPROC) WndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= hInstance;
	wc.hIcon			= NULL;
	wc.hCursor			= LoadCursor(NULL, IDC_ARROW);

	// No need for background brush for OpenGL window
	wc.hbrBackground	= NULL;
	wc.lpszMenuName		= NULL;
	wc.lpszClassName	= szWindowClass;

	// Register the window class
	if (RegisterClass(&wc) == 0)
		return FALSE;

	// Create the main application window
	hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
						CW_USEDEFAULT, 0, 250, 250, NULL, NULL, hInstance, NULL);

	// If window was not created, quit
	if (hWnd == NULL)
		return FALSE;

	// Display the window
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);

	// Process application messages until the application closes
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

// Window procedure, handles all messages for this program
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HGLRC				hRC = NULL;					// Permenant Rendering context
	static HDC					hDC = NULL;					// Private GDI Device context
	static OpenGLComposite*		pOpenGLComposite = NULL;
	static bool					sInteractiveResize = false;
	static StatusStripControls	sStatusStrip;

	switch (message)
	{
		// Window creation, setup for OpenGL context
		case WM_CREATE:
		{
			try
			{
			// Store the device context
			hDC = GetDC(hWnd);

			// Select the pixel format
			SetDCPixelFormat(hDC);

			// Create the rendering context and make it current
			hRC = wglCreateContext(hDC);
			wglMakeCurrent(hDC, hRC);

			HGLRC modernRC = CreateModernOpenGLContext(hDC);
			if (modernRC == NULL)
			{
				MessageBox(NULL, _T("This application requires an OpenGL 3.3+ core profile context."), _T("OpenGL initialization Error."), MB_OK);
				PostMessage(hWnd, WM_CLOSE, 0, 0);
				break;
			}

			wglMakeCurrent(NULL, NULL);
			wglDeleteContext(hRC);
			hRC = modernRC;
			wglMakeCurrent(hDC, hRC);

			// Initialize COM
			HRESULT result;
			result = CoInitialize(NULL);
			if (FAILED(result))
			{
				MessageBox(NULL, _T("Initialization of COM failed."), _T("Application initialization Error."),MB_OK);
				PostMessage(hWnd, WM_CLOSE, 0, 0);
				break;
			}

			// Setup OpenGL and DeckLink capture and playout object
			pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC);

			if (pOpenGLComposite->InitDeckLink())
			{
				wglMakeCurrent( NULL, NULL );
				if (pOpenGLComposite->Start())
				{
					PostMessage(hWnd, kCreateStatusStripMessage, 0, 0);
					break;						// success
				}
				MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
			}
			else
			{
				MessageBoxA(NULL, "The OpenGL/DeckLink runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
			}

			// Failed to initialize - cleanup
			delete pOpenGLComposite;
			pOpenGLComposite = NULL;
			PostMessage(hWnd, WM_CLOSE, 0, 0);
			break;
			}
			catch (...)
			{
				ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime.");
				PostMessage(hWnd, WM_CLOSE, 0, 0);
				break;
			}
		}

		case kCreateStatusStripMessage:
			if (pOpenGLComposite)
			{
				if (!StatusStripCreated(sStatusStrip))
					CreateStatusStrip(hWnd, sStatusStrip);

				UpdateStatusStrip(sStatusStrip, *pOpenGLComposite);
				LayoutStatusStrip(hWnd, sStatusStrip);
				RECT clientRect = {};
				if (GetClientRect(hWnd, &clientRect))
				{
					pOpenGLComposite->resizeGL(
						static_cast<WORD>(clientRect.right - clientRect.left),
						static_cast<WORD>(clientRect.bottom - clientRect.top));
				}
				InvalidateRect(hWnd, NULL, FALSE);
			}
			break;

		case WM_DESTROY:
			try
			{
				if (pOpenGLComposite)
				{
					pOpenGLComposite->Stop();
					delete pOpenGLComposite;
				}
			}
			catch (...)
			{
				ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime.");
			}

			// Deselect the current rendering context and delete it
			wglMakeCurrent(NULL, NULL);
			wglDeleteContext(hRC);

			// Tell the application to terminate after the window is gone
			PostQuitMessage(0);
			break;

		case WM_ENTERSIZEMOVE:
			sInteractiveResize = true;
			break;

		case WM_EXITSIZEMOVE:
			sInteractiveResize = false;
			if (pOpenGLComposite)
			{
				RECT clientRect = {};
				if (GetClientRect(hWnd, &clientRect))
				{
					pOpenGLComposite->resizeGL(
						static_cast<WORD>(clientRect.right - clientRect.left),
						static_cast<WORD>(clientRect.bottom - clientRect.top));
				}
			}
			InvalidateRect(hWnd, NULL, FALSE);
			break;

		case WM_SIZE:
			try
			{
				if (StatusStripCreated(sStatusStrip))
					LayoutStatusStrip(hWnd, sStatusStrip);
				if (pOpenGLComposite)
					pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
			}
			catch (...)
			{
				ShowUnhandledExceptionMessage("Resize failed inside the OpenGL runtime.");
			}
			break;

		case WM_ERASEBKGND:
			return 1;

		case WM_PAINT:
			try
			{
				PAINTSTRUCT paint = {};
				BeginPaint(hWnd, &paint);
				EndPaint(hWnd, &paint);

				if (!sInteractiveResize && pOpenGLComposite)
				{
					pOpenGLComposite->paintGL(true);
					RaiseStatusControls(sStatusStrip);
				}
			}
			catch (...)
			{
				ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
			}
			break;

		case WM_KEYDOWN:
			try
			{
				if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
				{
					pOpenGLComposite->ReloadShader();
					InvalidateRect(hWnd, NULL, FALSE);
				}
			}
			catch (...)
			{
				ShowUnhandledExceptionMessage("Shader reload failed inside the OpenGL runtime.");
			}
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam))
			{
			case kOpenControlButtonId:
				if (pOpenGLComposite)
				{
					std::string url = pOpenGLComposite->GetControlUrl();
					OpenUrl(url.c_str());
				}
				break;
			case kOpenDocsButtonId:
				if (pOpenGLComposite)
				{
					std::string url = pOpenGLComposite->GetDocsUrl();
					OpenUrl(url.c_str());
				}
				break;
			default:
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
			break;

		default:
			return (DefWindowProc(hWnd, message, wParam, lParam));
	}

	return (0L);
}

