#include "StdAfx.h"

//#include <windows.h>
//#include <d3d11.h>
//#include <d3dx11.h>
//#include <cassert>

// Linker input: d3d11.lib d3dx11.lib

static const char * const CLASS_NAME = "DX11_TEST";
static const char * const WINDOW_TITLE = "DirectX 11 Test (C) 2010 Adam Sawicki";
static const int SIZEX = 1024;
static const int SIZEY = 576;

HINSTANCE g_Instance;
HWND g_Wnd;

ID3D11Device *g_Dev;
ID3D11DeviceContext *g_Ctx;
IDXGISwapChain *g_SwapChain;
ID3D11Texture2D *g_BackBuf;
ID3D11RenderTargetView *g_RenderTargetView;

ID3D11Buffer *g_VertexBuffer;
ID3D10Blob *g_VertexShaderCode, *g_PixelShaderCode;
ID3D11VertexShader *g_VertexShader;
ID3D11PixelShader *g_PixelShader;
ID3D11InputLayout *g_InputLayout;

struct MyVertex
{
	float Position[2];
	unsigned Color;
};

static void OnCreate()
{
	//assert(XMVerifyCPUSupport());

	// Create device, immediate device context and swap chain

	D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0 };
	
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	swapChainDesc.BufferDesc.Width  = (UINT)SIZEX;
	swapChainDesc.BufferDesc.Height = (UINT)SIZEY;
	swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
	swapChainDesc.BufferDesc.RefreshRate.Denominator = 0;
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_CENTERED;
	swapChainDesc.SampleDesc.Count = 1;
	swapChainDesc.SampleDesc.Quality = 0;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.BufferCount = 2;
	swapChainDesc.OutputWindow = g_Wnd;
	swapChainDesc.Windowed = TRUE;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapChainDesc.Flags = 0;

	HRESULT hr = D3D11CreateDeviceAndSwapChain(
		NULL,
		D3D_DRIVER_TYPE_HARDWARE,
		NULL,
		D3D11_CREATE_DEVICE_SINGLETHREADED,
		featureLevels,
		_countof(featureLevels),
		D3D11_SDK_VERSION,
		&swapChainDesc,
		&g_SwapChain,
		&g_Dev,
		NULL,
		&g_Ctx);
	assert(SUCCEEDED(hr));

	// Get swap chain's back buffer, create its render target view and set that view as render target

	hr = g_SwapChain->GetBuffer(0, __uuidof(*g_BackBuf), (void**)&g_BackBuf);
	assert(SUCCEEDED(hr));

	hr = g_Dev->CreateRenderTargetView(g_BackBuf, NULL, &g_RenderTargetView);
	assert(SUCCEEDED(hr));

	g_Ctx->OMSetRenderTargets(1, &g_RenderTargetView, NULL);

	// Set viewport

	D3D11_VIEWPORT viewport;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	viewport.Width  = (FLOAT)SIZEX;
	viewport.Height = (FLOAT)SIZEY;
	viewport.MinDepth = 0.f;
	viewport.MaxDepth = 1.f;
	g_Ctx->RSSetViewports(1, &viewport);

	// Create and initialize vertex buffer

	MyVertex myVertices[] = {
		{  0.0f,  0.5f, 0xFF0000FF },
		{  0.3f, -0.5f, 0xFF00FF00 },
		{ -0.3f, -0.5f, 0xFFFF0000 },
	};

	D3D11_BUFFER_DESC bufDesc;
	ZeroMemory(&bufDesc, sizeof(bufDesc));
	bufDesc.ByteWidth = sizeof(MyVertex) * _countof(myVertices);
	bufDesc.Usage = D3D11_USAGE_IMMUTABLE;
	bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA bufData;
	ZeroMemory(&bufData, sizeof(bufData));
	bufData.pSysMem = myVertices;

	hr = g_Dev->CreateBuffer(&bufDesc, &bufData, &g_VertexBuffer);
	assert(SUCCEEDED(hr));

	// Compile and create vertex shader

	ID3D10Blob *errorBlob = 0;
	hr = D3DX11CompileFromFile(
		"Effect1.fx",
		0,
		0,
		"BasicVS",
		"vs_5_0",
		D3D10_SHADER_OPTIMIZATION_LEVEL2,
		0,
		0,
		&g_VertexShaderCode,
		&errorBlob,
		0);
	assert(SUCCEEDED(hr));

	hr = g_Dev->CreateVertexShader(g_VertexShaderCode->GetBufferPointer(), g_VertexShaderCode->GetBufferSize(), 0, &g_VertexShader);
	assert(SUCCEEDED(hr));

	if (errorBlob) errorBlob->Release();

	// Compile and create pixel shader

	errorBlob = 0;
	hr = D3DX11CompileFromFile(
		"Effect1.fx",
		0,
		0,
		"BasicPS",
		"ps_5_0",
		D3D10_SHADER_OPTIMIZATION_LEVEL2,
		0,
		0,
		&g_PixelShaderCode,
		&errorBlob,
		0);
	assert(SUCCEEDED(hr));

	hr = g_Dev->CreatePixelShader(g_PixelShaderCode->GetBufferPointer(), g_PixelShaderCode->GetBufferSize(), 0, &g_PixelShader);
	assert(SUCCEEDED(hr));

	if (errorBlob) errorBlob->Release();

	// Create input layout

	D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
		{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};

	hr = g_Dev->CreateInputLayout(inputDesc, _countof(inputDesc), g_VertexShaderCode->GetBufferPointer(), g_VertexShaderCode->GetBufferSize(), &g_InputLayout);
	assert(SUCCEEDED(hr));

	// Setup minimum necessary context states

	UINT vbStride = sizeof(MyVertex), vbOffset = 0;
	g_Ctx->IASetVertexBuffers(0, 1, &g_VertexBuffer, &vbStride, &vbOffset);
	g_Ctx->IASetInputLayout(g_InputLayout);
	g_Ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	g_Ctx->VSSetShader(g_VertexShader, 0, 0);
	g_Ctx->PSSetShader(g_PixelShader, 0, 0);
}

static void OnDestroy()
{
	if (g_InputLayout) g_InputLayout->Release();
	if (g_PixelShader) g_PixelShader->Release();
	if (g_VertexShader) g_VertexShader->Release();
	if (g_PixelShaderCode) g_PixelShaderCode->Release();
	if (g_VertexShaderCode) g_VertexShaderCode->Release();
	if (g_VertexBuffer) g_VertexBuffer->Release();

	if (g_RenderTargetView) g_RenderTargetView->Release();
	if (g_BackBuf) g_BackBuf->Release();
	if (g_SwapChain) g_SwapChain->Release();
	if (g_Ctx) g_Ctx->Release();
	if (g_Dev) g_Dev->Release();
}

static void OnFrame()
{
	// Fill screen with background color

	float clearColor[4] = { 0.f, 0.f, 0.3f, 1.f };
	g_Ctx->ClearRenderTargetView(g_RenderTargetView, clearColor);

	// Draw the triangle

	g_Ctx->Draw(3, 0);

	// Show the frame
	
	HRESULT hr = g_SwapChain->Present(1, 0);
	assert(SUCCEEDED(hr));
}

static void OnKeyDown(WPARAM key)
{
	switch (key)
	{
	case VK_ESCAPE:
		PostMessage(g_Wnd, WM_CLOSE, 0, 0);
		break;
	}
}

static LRESULT WINAPI WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_CREATE:
		g_Wnd = wnd;
		OnCreate();
		return 0;

	case WM_DESTROY:
		OnDestroy();
		PostQuitMessage(0);
		return 0;

	case WM_KEYDOWN:
		OnKeyDown(wParam);
		return 0;
	}

	return DefWindowProc(wnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char* cmdLine, int showCmd)
{
	g_Instance = instance;

	WNDCLASSEX wndClass;
	ZeroMemory(&wndClass, sizeof(wndClass));
	wndClass.cbSize = sizeof(wndClass);
	wndClass.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
	wndClass.hbrBackground = NULL;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hInstance = instance;
	wndClass.lpfnWndProc = &WndProc;
	wndClass.lpszClassName = CLASS_NAME;

	ATOM classR = RegisterClassEx(&wndClass);
	assert(classR);

	DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE;
	DWORD exStyle = 0;

	RECT rect = { 0, 0, SIZEX, SIZEY };
	AdjustWindowRectEx(&rect, style, FALSE, exStyle);
	g_Wnd = CreateWindowEx(
		exStyle,
		CLASS_NAME,
		WINDOW_TITLE,
		style,
		CW_USEDEFAULT, CW_USEDEFAULT,
		rect.right - rect.left, rect.bottom - rect.top,
		NULL,
		NULL,
		instance,
		0);
	assert(g_Wnd);

	MSG msg;
	for (;;)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
			OnFrame();
	}
	return (int)msg.wParam;
}
