//
// Game.cpp
//

#include "pch.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include "TwitchInGames.h"
#include "Game.h"

#pragma comment(lib, "WS2_32.lib")

using namespace DirectX;
using namespace Windows::Xbox::Input;
using namespace Windows::Foundation::Collections;

using Microsoft::WRL::ComPtr;

Game::Game() :
	m_window(nullptr),
	m_outputWidth(1920),
	m_outputHeight(1080),
	m_featureLevel(D3D_FEATURE_LEVEL_12_0),
	m_backBufferIndex(0),
	m_frame(0),
	m_fenceValues{} {}

// Initialize the Direct3D resources required to run.
void Game::Initialize(IUnknown* window) {
	m_window = window;

	CreateDevice();
	CreateResources();

	// TODO: Change the timer settings if you want something other than the default variable timestep mode.
	// e.g. for 60 FPS fixed timestep update logic, call:
	/*
	m_timer.SetFixedTimeStep(true);
	m_timer.SetTargetElapsedSeconds(1.0 / 60);
	*/
}

#pragma region Frame Update
// Executes the basic game loop.
void Game::Tick() {
	PIXBeginEvent(EVT_COLOR_FRAME, L"Frame %I64u", m_frame);

	m_timer.Tick([&]() {
		Update(m_timer);
	});

	Render();

	PIXEndEvent();
	m_frame++;
}

#include "../Win32App1/FlowOne.inl"

// Updates the world.
void Game::Update(DX::StepTimer const& timer) {
	PIXBeginEvent(EVT_COLOR_UPDATE, L"Update");

	// Allow the game to exit by pressing the view button on a controller.
	// This is just a helper for development.
	IVectorView<IGamepad^>^ gamepads = Gamepad::Gamepads;

	for(unsigned i = 0; i < gamepads->Size; i++) {
		IGamepadReading^ reading = gamepads->GetAt(i)->GetCurrentReading();
		if(reading->IsViewPressed) {
			Windows::ApplicationModel::Core::CoreApplication::Exit();
		} else if(reading->IsYPressed) {
			auto token= DoFlow1();
			auto user= TwitchInGames::User::FetchCurrent(clientId, token.c_str()).get();
			OutputDebugStringW(user.Email.c_str());
			OutputDebugStringW(L"\n");
		}
	}

	float elapsedTime = float(timer.GetElapsedSeconds());

	// TODO: Add your game logic here.
	elapsedTime;

	PIXEndEvent();
}
#pragma endregion

#pragma region Frame Render
// Draws the scene.
void Game::Render() {
	// Don't try to render anything before the first Update.
	if(m_timer.GetFrameCount() == 0) {
		return;
	}

	// Prepare the command list to render a new frame.
	Clear();

	PIXBeginEvent(m_commandList.Get(), EVT_COLOR_RENDER, L"Render");

	// TODO: Add your rendering code here.

	PIXEndEvent(m_commandList.Get());

	// Show the new frame.
	Present();
}

// Helper method to prepare the command list for rendering and clear the back buffers.
void Game::Clear() {
	// Reset command list and allocator.
	DX::ThrowIfFailed(m_commandAllocators[m_backBufferIndex]->Reset());
	DX::ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_backBufferIndex].Get(), nullptr));

	// Transition the render target into the correct state to allow for drawing into it.
	D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
	m_commandList->ResourceBarrier(1, &barrier);

	// Clear the views.
	PIXBeginEvent(m_commandList.Get(), PIX_COLOR_DEFAULT, L"Clear");

	XMVECTORF32 clearColor;
	clearColor.v = XMColorSRGBToRGB(Colors::CornflowerBlue);

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), m_backBufferIndex, m_rtvDescriptorSize);
	CD3DX12_CPU_DESCRIPTOR_HANDLE dsvDescriptor(m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
	m_commandList->OMSetRenderTargets(1, &rtvDescriptor, FALSE, &dsvDescriptor);
	m_commandList->ClearRenderTargetView(rtvDescriptor, clearColor, 0, nullptr);
	m_commandList->ClearDepthStencilView(dsvDescriptor, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

	// Set the viewport and scissor rect.
	D3D12_VIEWPORT viewport ={ 0.0f, 0.0f, static_cast<float>(m_outputWidth), static_cast<float>(m_outputHeight), D3D12_MIN_DEPTH, D3D12_MAX_DEPTH };
	D3D12_RECT scissorRect ={ 0, 0, m_outputWidth, m_outputHeight };
	m_commandList->RSSetViewports(1, &viewport);
	m_commandList->RSSetScissorRects(1, &scissorRect);

	PIXEndEvent(m_commandList.Get());
}

// Submits the command list to the GPU and presents the back buffer contents to the screen.
void Game::Present() {
	PIXBeginEvent(EVT_COLOR_FRAME, L"Present");

	// Transition the render target to the state that allows it to be presented to the display.
	D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
	m_commandList->ResourceBarrier(1, &barrier);

	// Send the command list off to the GPU for processing.
	DX::ThrowIfFailed(m_commandList->Close());
	m_commandQueue->ExecuteCommandLists(1, CommandListCast(m_commandList.GetAddressOf()));

	// The first argument instructs DXGI to block until VSync, putting the application
	// to sleep until the next VSync. This ensures we don't waste any cycles rendering
	// frames that will never be displayed to the screen.
	DX::ThrowIfFailed(m_swapChain->Present(1, 0));

	// Xbox One apps do not need to handle DXGI_ERROR_DEVICE_REMOVED or DXGI_ERROR_DEVICE_RESET.

	MoveToNextFrame();

	PIXEndEvent();
}
#pragma endregion

#pragma region Message Handlers
// Occurs when the game is being suspended.
void Game::OnSuspending() {
	m_commandQueue->SuspendX(0);

	// TODO: Save game progress using the ConnectedStorage API.
}

// Occurs when the game is resuming.
void Game::OnResuming() {
	m_commandQueue->ResumeX();
	m_timer.ResetElapsedTime();

	// TODO: Handle changes in users and input devices.
}
#pragma endregion

#pragma region Direct3D Resources
// These are the resources that depend on the device.
void Game::CreateDevice() {
	// Create the DX12 API device object.
	D3D12XBOX_CREATE_DEVICE_PARAMETERS params ={};
	params.Version = D3D12_SDK_VERSION;

#if defined(_DEBUG)
	// Enable the debug layer.
	params.ProcessDebugFlags = D3D12_PROCESS_DEBUG_FLAG_DEBUG_LAYER_ENABLED;
#elif defined(PROFILE)
	// Enable the instrumented driver.
	params.ProcessDebugFlags = D3D12XBOX_PROCESS_DEBUG_FLAG_INSTRUMENTED;
#endif

	params.GraphicsCommandQueueRingSizeBytes = static_cast<UINT>(D3D12XBOX_DEFAULT_SIZE_BYTES);
	params.GraphicsScratchMemorySizeBytes = static_cast<UINT>(D3D12XBOX_DEFAULT_SIZE_BYTES);
	params.ComputeScratchMemorySizeBytes = static_cast<UINT>(D3D12XBOX_DEFAULT_SIZE_BYTES);

	DX::ThrowIfFailed(D3D12XboxCreateDevice(
		nullptr,
		&params,
		IID_GRAPHICS_PPV_ARGS(m_d3dDevice.ReleaseAndGetAddressOf())
	));

	// Create the command queue.
	D3D12_COMMAND_QUEUE_DESC queueDesc ={};
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

	DX::ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_GRAPHICS_PPV_ARGS(m_commandQueue.ReleaseAndGetAddressOf())));

	// Create descriptor heaps for render target views and depth stencil views.
	D3D12_DESCRIPTOR_HEAP_DESC rtvDescriptorHeapDesc ={};
	rtvDescriptorHeapDesc.NumDescriptors = c_swapBufferCount;
	rtvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;

	D3D12_DESCRIPTOR_HEAP_DESC dsvDescriptorHeapDesc ={};
	dsvDescriptorHeapDesc.NumDescriptors = 1;
	dsvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;

	DX::ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&rtvDescriptorHeapDesc, IID_GRAPHICS_PPV_ARGS(m_rtvDescriptorHeap.ReleaseAndGetAddressOf())));
	DX::ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&dsvDescriptorHeapDesc, IID_GRAPHICS_PPV_ARGS(m_dsvDescriptorHeap.ReleaseAndGetAddressOf())));

	m_rtvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	m_dsvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

	// Create a command allocator for each back buffer that will be rendered to.
	for(UINT n = 0; n < c_swapBufferCount; n++) {
		DX::ThrowIfFailed(m_d3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_GRAPHICS_PPV_ARGS(m_commandAllocators[n].ReleaseAndGetAddressOf())));
	}

	// Create a command list for recording graphics commands.
	DX::ThrowIfFailed(m_d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocators[0].Get(), nullptr, IID_GRAPHICS_PPV_ARGS(m_commandList.ReleaseAndGetAddressOf())));
	DX::ThrowIfFailed(m_commandList->Close());

	// Create a fence for tracking GPU execution progress.
	DX::ThrowIfFailed(m_d3dDevice->CreateFence(m_fenceValues[m_backBufferIndex], D3D12_FENCE_FLAG_NONE, IID_GRAPHICS_PPV_ARGS(m_fence.ReleaseAndGetAddressOf())));
	m_fenceValues[m_backBufferIndex]++;

	m_fenceEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
	if(!m_fenceEvent.IsValid()) {
		throw Platform::Exception::CreateException(HRESULT_FROM_WIN32(GetLastError()));
	}

	// Check for 4k swapchain support
	D3D12XBOX_GPU_HARDWARE_CONFIGURATION hwConfig ={};
	m_d3dDevice->GetGpuHardwareConfigurationX(&hwConfig);
	if(hwConfig.HardwareVersion >= D3D12XBOX_HARDWARE_VERSION_XBOX_ONE_X) {
		m_outputWidth = 3840;
		m_outputHeight = 2160;
	}

	// TODO: Initialize device dependent objects here (independent of window size).
}

// Allocate all memory resources that change on a window SizeChanged event.
void Game::CreateResources() {
	// Wait until all previous GPU work is complete.
	WaitForGpu();

	// Release resources that are tied to the swap chain and update fence values.
	for(UINT n = 0; n < c_swapBufferCount; n++) {
		m_renderTargets[n].Reset();
		m_fenceValues[n] = m_fenceValues[m_backBufferIndex];
	}

	DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
	DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D32_FLOAT;
	UINT backBufferWidth = static_cast<UINT>(m_outputWidth);
	UINT backBufferHeight = static_cast<UINT>(m_outputHeight);

	// If the swap chain already exists, resize it, otherwise create one.
	if(m_swapChain) {
		DX::ThrowIfFailed(m_swapChain->ResizeBuffers(c_swapBufferCount, backBufferWidth, backBufferHeight, backBufferFormat, 0));

		// Xbox One apps do not need to handle DXGI_ERROR_DEVICE_REMOVED or DXGI_ERROR_DEVICE_RESET.
	} else {
		// First, retrieve the underlying DXGI device from the D3D device.
		ComPtr<IDXGIDevice1> dxgiDevice;
		DX::ThrowIfFailed(m_d3dDevice.As(&dxgiDevice));

		// Identify the physical adapter (GPU or card) this device is running on.
		ComPtr<IDXGIAdapter> dxgiAdapter;
		DX::ThrowIfFailed(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));

		// And obtain the factory object that created it.
		ComPtr<IDXGIFactory2> dxgiFactory;
		DX::ThrowIfFailed(dxgiAdapter->GetParent(IID_GRAPHICS_PPV_ARGS(dxgiFactory.GetAddressOf())));

		// Create a descriptor for the swap chain.
		DXGI_SWAP_CHAIN_DESC1 swapChainDesc ={};
		swapChainDesc.Width = backBufferWidth;
		swapChainDesc.Height = backBufferHeight;
		swapChainDesc.Format = backBufferFormat;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferCount = c_swapBufferCount;
		swapChainDesc.SampleDesc.Count = 1;
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
		swapChainDesc.Flags = DXGIX_SWAP_CHAIN_MATCH_XBOX360_AND_PC;

		// Create a swap chain for the window.
		DX::ThrowIfFailed(dxgiFactory->CreateSwapChainForCoreWindow(
			m_d3dDevice.Get(),
			m_window,
			&swapChainDesc,
			nullptr,
			m_swapChain.ReleaseAndGetAddressOf()
		));
	}

	// Obtain the back buffers for this window which will be the final render targets
	// and create render target views for each of them.
	for(UINT n = 0; n < c_swapBufferCount; n++) {
		DX::ThrowIfFailed(m_swapChain->GetBuffer(n, IID_GRAPHICS_PPV_ARGS(m_renderTargets[n].GetAddressOf())));

		wchar_t name[25] ={};
		if(swprintf_s(name, L"Render target %u", n) > 0) {
			m_renderTargets[n]->SetName(name);
		}

		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), n, m_rtvDescriptorSize);
		m_d3dDevice->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvDescriptor);
	}

	// Reset the index to the current back buffer.
	m_backBufferIndex = 0;

	// Allocate a 2-D surface as the depth/stencil buffer and create a depth/stencil view
	// on this surface.
	CD3DX12_HEAP_PROPERTIES depthHeapProperties(D3D12_HEAP_TYPE_DEFAULT);

	D3D12_RESOURCE_DESC depthStencilDesc = CD3DX12_RESOURCE_DESC::Tex2D(
		depthBufferFormat,
		backBufferWidth,
		backBufferHeight,
		1, // This depth stencil view has only one texture.
		1  // Use a single mipmap level.
	);
	depthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	CD3DX12_CLEAR_VALUE depthOptimizedClearValue(depthBufferFormat, 1.0f, 0);

	DX::ThrowIfFailed(m_d3dDevice->CreateCommittedResource(
		&depthHeapProperties,
		D3D12_HEAP_FLAG_NONE,
		&depthStencilDesc,
		D3D12_RESOURCE_STATE_DEPTH_WRITE,
		&depthOptimizedClearValue,
		IID_GRAPHICS_PPV_ARGS(m_depthStencil.ReleaseAndGetAddressOf())
	));

	m_depthStencil->SetName(L"Depth stencil");

	D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc ={};
	dsvDesc.Format = depthBufferFormat;
	dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;

	m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &dsvDesc, m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

	// TODO: Initialize windows-size dependent objects here.
}

void Game::WaitForGpu() {
	// Schedule a Signal command in the GPU queue.
	DX::ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), m_fenceValues[m_backBufferIndex]));

	// Wait until the Signal has been processed.
	DX::ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_backBufferIndex], m_fenceEvent.Get()));
	WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE);

	// Increment the fence value for the current frame.
	m_fenceValues[m_backBufferIndex]++;
}

void Game::MoveToNextFrame() {
	// Schedule a Signal command in the queue.
	const UINT64 currentFenceValue = m_fenceValues[m_backBufferIndex];
	DX::ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));

	// Update the back buffer index.
	m_backBufferIndex = (m_backBufferIndex + 1) % c_swapBufferCount;

	// If the next frame is not ready to be rendered yet, wait until it is ready.
	if(m_fence->GetCompletedValue() < m_fenceValues[m_backBufferIndex]) {
		DX::ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_backBufferIndex], m_fenceEvent.Get()));
		WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE);
	}

	// Set the fence value for the next frame.
	m_fenceValues[m_backBufferIndex] = currentFenceValue + 1;
}
#pragma endregion
