//David Oguns
//October 25, 2010
//graphics.cpp

#include <iostream>
#include "graphics.h"
#include "util.h"

#include <DxErr.h>

using namespace std;

#define PRIMARY_DEVICE_INDEX		0

#define PRIMARY_FORMAT DXGI_FORMAT_R8G8B8A8_UNORM

GraphicsLayer::GraphicsLayer() :
	pDevice(nullptr),
	pSwapChain(nullptr),
	pImmediateDeviceContext(nullptr),
	pDXGIFactory(nullptr),
	pBackBuffer(nullptr),
	pBackBufferRT(nullptr),
	pPixelShader(nullptr),
	pVertexShader(nullptr),
	pVB(nullptr),
	pIL(nullptr),
	pVSConstants(nullptr),
	m_rasterState(nullptr),
	m_depthStencilBuffer(nullptr),
	m_depthStencilState(nullptr),
	m_depthStencilView(nullptr),
	camera(nullptr),
	pPIL(nullptr)
{
	vGraphicsDevices.clear();
}

GraphicsLayer::~GraphicsLayer()
{
	Shutdown();
}

bool GraphicsLayer::Initialize(HWND hwnd, bool fullscreen, Camera *c)
{	
	camera = c;
	//only request DirectX 11 or fail creation
	D3D_FEATURE_LEVEL requestedLevels[] = {
		D3D_FEATURE_LEVEL_11_0//,
		//D3D_FEATURE_LEVEL_10_1,
		//D3D_FEATURE_LEVEL_10_0,
		//D3D_FEATURE_LEVEL_9_3,
		//D3D_FEATURE_LEVEL_9_2,
		//D3D_FEATURE_LEVEL_9_1,
	};
	D3D_FEATURE_LEVEL levelAchieved;
	
	hr = D3D11CreateDevice(
		//adapters[PRIMARY_DEVICE_INDEX],		//pointer to IDXGIAdapter1 - refers to a graphics card
		NULL,
		D3D_DRIVER_TYPE_HARDWARE,			//what kind of driver to create (we obviously want hardware)
		NULL,								//module handle to software rasterizer DLL if we have one
		0,									//no special flags for this device (debug, threading behavior, etc)
		requestedLevels, 1,					//array of feature levels to ask for and number of feature levels in array
		//NULL, NULL,					//array of feature levels to ask for and number of feature levels in array
		D3D11_SDK_VERSION,					//D3D SDK version being used to develop
		&pDevice,							//pointer to device out
		&levelAchieved,						//pointer to a D3D_FEATURE_LEVEL to output achieved level to
		&pImmediateDeviceContext);

	if(FAILED(hr))
	{
		wcout << L"Failed to create Direct3D device." << endl << 
			L"Error String: "<< DXGetErrorString(hr) <<  endl <<
			L"Error Description: " << DXGetErrorDescription(hr) << endl;
		return false;
	}
	
	IDXGIDevice * pDXGIDevice = nullptr;
	hr = pDevice->QueryInterface(__uuidof(IDXGIDevice), (void **)&pDXGIDevice); 

	IDXGIAdapter1 * pDXGIAdapter = nullptr;
	hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter1), (void **)&pDXGIAdapter);

	pDXGIAdapter->GetParent(__uuidof(IDXGIFactory1), (void **)&pDXGIFactory);
	
	unsigned int i = 0;
	IDXGIAdapter1 *pAdapter;
	while(pDXGIFactory->EnumAdapters1(i++, &pAdapter) != DXGI_ERROR_NOT_FOUND)
	{
		wcout << L"Found adapter" << endl;
		GraphicsDevicePtr gd(new GraphicsDevice());
		gd->pAdapter = pAdapter;
		
		DXGI_ADAPTER_DESC1 deviceDesc;
		pAdapter->GetDesc1(&deviceDesc);
		wcout << L"===========================================" << endl;
		wcout << L"Description: " << deviceDesc.Description << endl;
		wcout << L"Video Memory: " << deviceDesc.DedicatedVideoMemory / (1024*1024) << "MB" << endl;
		wcout << L"Shared Sys Memory: " << deviceDesc.SharedSystemMemory << endl;
 		
		IDXGIOutput *pOutput = nullptr;
		int j = 0;
		while(pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND)
		{
			MonitorOutputPtr mo(new MonitorOutput());
			mo->pOutputHandle = pOutput;

			wcout << L"Found output..." << endl;
			DXGI_OUTPUT_DESC outputDesc;
			pOutput->GetDesc(&outputDesc);
			wcout << L"Output Name: ";
			wcout << outputDesc.DeviceName << endl;
			wcout << L"Attached to desktop? " << outputDesc.AttachedToDesktop << endl;
			
			pOutput->GetDisplayModeList(PRIMARY_FORMAT,
				0, &mo->numDisplayModes, NULL);
			wcout << L"Found " << mo->numDisplayModes << L" display modes..." << endl;

			//allocate dynamic memory
			mo->pDisplayModes = new DXGI_MODE_DESC[mo->numDisplayModes];			
			pOutput->GetDisplayModeList(PRIMARY_FORMAT,
				0, &mo->numDisplayModes, mo->pDisplayModes);
			//loop over vector
			for(UINT k = 0; k < mo->numDisplayModes; ++k)
			{
				wcout << L"Found display mode [" << mo->pDisplayModes[k].Width
					<< L"x" << mo->pDisplayModes[k].Height << "]" << endl
					<< "Refresh rate ~ [" << mo->pDisplayModes[k].RefreshRate.Numerator
					<< " / " << mo->pDisplayModes[k].RefreshRate.Denominator
					<< "]  ==  "
					<< mo->pDisplayModes[k].RefreshRate.Numerator/(float)mo->pDisplayModes[k].RefreshRate.Denominator << endl
					<< L"Scaling: " << mo->pDisplayModes[k].Scaling << endl
					<< L"Scanline Ordering: " << mo->pDisplayModes[k].ScanlineOrdering << endl;
			}
			gd->vMonitorOutputs.push_back(mo);
			++j;
		}
		vGraphicsDevices.push_back(gd);
		++i;
	}

	//choose last display mode on primary graphics adapter, primary monitor
	//which *might* result in the highest resolution
	GraphicsDevicePtr primaryDev = *vGraphicsDevices.begin();
	MonitorOutputPtr primaryMon = *primaryDev->vMonitorOutputs.begin();
	DXGI_MODE_DESC *pMode = &primaryMon->pDisplayModes[primaryMon->numDisplayModes-1];

	currMode = pMode;
	wcout << L"Chosen display mode [" << pMode->Width << "x"
		<< pMode->Height << "]" << endl;

	DXGI_SWAP_CHAIN_DESC scDesc;
	ZeroMemory(&scDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
	scDesc.BufferCount = 1;
	scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scDesc.BufferDesc.Format = pMode->Format;	
	scDesc.BufferDesc.Width = pMode->Width;
	scDesc.BufferDesc.Height = pMode->Height;
	scDesc.BufferDesc.RefreshRate.Numerator = pMode->RefreshRate.Numerator;
	scDesc.BufferDesc.RefreshRate.Denominator = pMode->RefreshRate.Denominator;
	scDesc.BufferDesc.Scaling = pMode->Scaling;
	scDesc.BufferDesc.ScanlineOrdering = pMode->ScanlineOrdering;
	scDesc.SampleDesc.Count = 1;		//one sample per pixel
	scDesc.SampleDesc.Quality = 0;		//regular quality
	scDesc.OutputWindow = hwnd;
	scDesc.Windowed = !fullscreen;
	scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	scDesc.Flags = 0;
	
	hr = pDXGIFactory->CreateSwapChain(pDevice, &scDesc, &pSwapChain);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create swap chain!!!", hr);
		return false;
	}
	
	//now get the pointer to the back buffer and create render target view from it
	hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&pBackBuffer);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not get back buffer texture!!!", hr);
		return false;
	}

	pDevice->CreateRenderTargetView(pBackBuffer, NULL, &pBackBufferRT);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not get create back buffer render target view!!!", hr);
		return false;
	}
	pBackBuffer->Release();
	pBackBuffer = nullptr;

	pImmediateDeviceContext->OMSetRenderTargets(1, &pBackBufferRT, NULL);

	D3D11_VIEWPORT viewport;
	// Setup the viewport for rendering.
	viewport.Width = (float)currMode->Width;
    viewport.Height = (float)currMode->Height;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;

	//pImmediateDeviceContext->OMSetRenderTargets(1, &pBackBufferRT, NULL);
	pImmediateDeviceContext->RSSetViewports(1, &viewport);

	c->setPerspective(D3DX_PI/4.0f, (float)currMode->Width/(float)currMode->Height,
		0.1f, 1000.0f);

	if(!InitShaders())
	{
		wcout << "Could not initialize shaders!!!" << endl;
		return false;
	}

	if(!InitGeometry())
	{
		wcout << "Could not initialize geometry!!!" << endl;
		return false;
	}

	return true;
}

void GraphicsLayer::Shutdown()
{
	wcout << L"Shutting down graphics layer..." << endl;

	for(vector<GraphicsDevicePtr>::iterator gditr = vGraphicsDevices.begin();
		gditr != vGraphicsDevices.end();
		++gditr)
	{
		vector<MonitorOutputPtr> vOutputs = (*gditr)->vMonitorOutputs;
		for(vector<MonitorOutputPtr>::iterator oitr = vOutputs.begin();
			oitr != vOutputs.end();
			++oitr)
		{	
			if((*oitr)->pDisplayModes)
			{
				delete [] (*oitr)->pDisplayModes;
				(*oitr)->pDisplayModes = nullptr;
			}
			(*oitr)->pOutputHandle->Release();
		}
		//clear vector - boost does memory cleanup of MonitorOutputPtr
		vOutputs.clear();
		(*gditr)->pAdapter->Release();
	}
	//clear vector
	vGraphicsDevices.clear();

	if(pPSConstants)
	{
		pPSConstants->Release();
		pPSConstants = nullptr;
	}
	if(pVSConstants)
	{
		pVSConstants->Release();
		pVSConstants = nullptr;
	}
	if(m_depthStencilView)
	{
		m_depthStencilView->Release();
		m_depthStencilView = nullptr;
	}
	if(m_depthStencilState)
	{
		m_depthStencilState->Release();
		m_depthStencilState = nullptr;
	}
	if(m_depthStencilBuffer)
	{
		m_depthStencilBuffer->Release();
		m_depthStencilBuffer = nullptr;
	}
	if(m_rasterState)
	{
		m_rasterState->Release();
		m_rasterState = nullptr;
	}
	if(pVSConstants)
	{
		pVSConstants->Release();
		pVSConstants = nullptr;
	}
	if(pIL)
	{
		pIL->Release();
		pIL = nullptr;
	}
	if(pVB)
	{
		pVB->Release();
		pVB = nullptr;
	}
	if(pVertexShader)
	{
		pVertexShader->Release();
		pVertexShader = nullptr;
	}
	if(pPixelShader)
	{
		pPixelShader->Release();
		pPixelShader = nullptr;
	}
	if(pBackBufferRT)
	{
		pBackBufferRT->Release();
		pBackBufferRT = nullptr;
	}
	if(pBackBuffer)
	{
		pBackBuffer->Release();
		pBackBuffer = nullptr;
	}
	if(pSwapChain)
	{
		//exit out of fullscreen if app is in fullscreen - otherwise clean exit does not occur
		pSwapChain->SetFullscreenState(FALSE, NULL);
		pSwapChain->Release();
		pSwapChain = nullptr;
	}
	if(pDevice)
	{
		pDevice->Release();
		pDevice = nullptr;
	}
	if(pImmediateDeviceContext)
	{
		pImmediateDeviceContext->Release();
		pImmediateDeviceContext = nullptr;
	}
	if(pDXGIFactory)
	{
		pDXGIFactory->Release();
		pDXGIFactory = nullptr;
	}
}

void GraphicsLayer::Render()
{
	FLOAT clearColor[] = {0.15f, 0.15f, 0.15f, 1.0f};

	pImmediateDeviceContext->ClearRenderTargetView(pBackBufferRT, clearColor);
	
	unsigned int stride = sizeof(Vertex);
	unsigned int offset = 0;


	D3D11_MAPPED_SUBRESOURCE resc;
	pImmediateDeviceContext->Map(pVSConstants, 0, D3D11_MAP_WRITE_DISCARD,
		0, &resc);	
	VertexShaderConstants *vbConsts = (VertexShaderConstants*)resc.pData;
	vbConsts->proj = *camera->getProjectionMatrix();
	vbConsts->view = *camera->getViewMatrix();
	D3DXMatrixIdentity(&vbConsts->world);
	pImmediateDeviceContext->Unmap(pVSConstants, 0);


	pImmediateDeviceContext->Map(pPSConstants, 0, D3D11_MAP_WRITE_DISCARD,
		0, &resc);
	PixelShaderConstants *psConsts = (PixelShaderConstants*)resc.pData;
	psConsts->r = 0.3f;
	psConsts->g = 0.6f;
	psConsts->b = 0.9f;
	psConsts->a = 1.0f;
	pImmediateDeviceContext->Unmap(pPSConstants, 0);

	pImmediateDeviceContext->IASetVertexBuffers(0, 1, &pVB, &stride, &offset);
	pImmediateDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	pImmediateDeviceContext->IASetInputLayout(pIL);
	pImmediateDeviceContext->VSSetShader(pVertexShader, NULL, 0);
	pImmediateDeviceContext->VSSetConstantBuffers(0, 1, &pVSConstants);
	pImmediateDeviceContext->PSSetConstantBuffers(0, 1, &pPSConstants);
	pImmediateDeviceContext->PSSetShader(pPixelShader, NULL, 0);

	pImmediateDeviceContext->Draw(3, 0);

	//hr = pSwapChain->Present(0, 0);		//no v-sync
	hr = pSwapChain->Present(1, 0);
	if(FAILED(hr))
	{
		OutputDXError("Failed to call present on swap chain!!!", hr);
	}
}

bool GraphicsLayer::InitShaders()
{
	ID3D10Blob *vsBlob = nullptr;
	ID3D10Blob *psBlob = nullptr;
	ID3D10Blob *errorBlob = nullptr;

	hr = D3DX11CompileFromFile(L"simple.vs",
		NULL, NULL,							//no macros, no includes
		"vs_main",							//name of shader entry point function
		"vs_5_0",							//shader target version
		D3D10_SHADER_ENABLE_STRICTNESS,		//disables old syntax
		NULL,								//only used if this is an FX file
		NULL,								//no thread pump - compile synchronously
		&vsBlob,
		&errorBlob,
		NULL);								//only needed if asynchronously called
	if(FAILED(hr))
	{
		OutputDXError(L"Could not compile vertex shader from file!!!", hr);
		
		if(errorBlob)
		{
			cout << (char *)errorBlob->GetBufferPointer() << endl;
			errorBlob->Release();
			errorBlob = nullptr;
		}
		return false;
	}
	
		hr = D3DX11CompileFromFile(L"static_color.ps",
		NULL, NULL,							//no macros, no includes
		"ps_main",							//name of shader entry point function
		"ps_5_0",							//shader target version
		D3D10_SHADER_ENABLE_STRICTNESS,		//disables old syntax
		NULL,								//only used if this is an FX file
		NULL,								//no thread pump - compile synchronously
		&psBlob,
		&errorBlob,
		NULL);								//only needed if asynchronously called
	if(FAILED(hr))
	{
		OutputDXError(L"Could not compile pixel shader from file!!!", hr);
		if(errorBlob)
		{
			cout << (char *)errorBlob->GetBufferPointer() << endl;
			errorBlob->Release();
			errorBlob = nullptr;
		}		
		return false;
	}

	hr = pDevice->CreateVertexShader(vsBlob->GetBufferPointer(),
		vsBlob->GetBufferSize(),
		NULL,		//no linking
		&pVertexShader);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create vertex shader!!!", hr);
		return false;
	}
	pImmediateDeviceContext->VSSetShader(pVertexShader, NULL, 0);

	hr = pDevice->CreatePixelShader(psBlob->GetBufferPointer(),
		psBlob->GetBufferSize(),
		NULL,		//no linking
		&pPixelShader);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create pixel shader!!!", hr);
		return false;
	}
	pImmediateDeviceContext->PSSetShader(pPixelShader, NULL, 0);
	
	D3D11_INPUT_ELEMENT_DESC vsInputDesc;
	vsInputDesc.SemanticName = "POSITION";
	vsInputDesc.SemanticIndex = 0;
	vsInputDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
	vsInputDesc.AlignedByteOffset = 0;
	vsInputDesc.InputSlot = 0;
	vsInputDesc.InstanceDataStepRate = 0;
	vsInputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

	hr = pDevice->CreateInputLayout(&vsInputDesc, 1, vsBlob->GetBufferPointer(),
		vsBlob->GetBufferSize(), &pIL);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create input layout for VS.", hr);
		return false;
	}

	/*
	vsInputDesc.SemanticName = "COLOR";
	vsInputDesc.SemanticIndex = 0;
	vsInputDesc.Format =  DXGI_FORMAT_R32G32B32A32_FLOAT;
	vsInputDesc.AlignedByteOffset = 0;
	vsInputDesc.InputSlot = 0;
	vsInputDesc.InstanceDataStepRate = 0;
	vsInputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

	hr = pDevice->CreateInputLayout(&vsInputDesc, 1, psBlob->GetBufferPointer(),
		psBlob->GetBufferSize(), &pPIL);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create input layout for PS.", hr);
		return false;
	}
	*/

	if(vsBlob)
	{
		vsBlob->Release();
		vsBlob = nullptr;
	}
	if(psBlob)
	{
		psBlob->Release();
		psBlob = nullptr;
	}
	if(errorBlob)
	{
		errorBlob->Release();
		errorBlob = nullptr;
	}

	return true;
}

bool GraphicsLayer::InitGeometry()
{
	D3D11_BUFFER_DESC bufferDesc;
	bufferDesc.Usage = D3D11_USAGE_DEFAULT;				//GPU read/write
	bufferDesc.CPUAccessFlags = 0;						//cpu needs no access (after creation that is)
	bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bufferDesc.MiscFlags = 0;
	bufferDesc.StructureByteStride = 0;
	bufferDesc.ByteWidth = sizeof(Vertex) * 3;

	D3D11_SUBRESOURCE_DATA vData;
	vData.SysMemPitch = 0;
	vData.SysMemSlicePitch = 0;
	Vertex triangle[3];
	vData.pSysMem = &triangle;

	triangle[2].vec.x = -1.0f;
	triangle[2].vec.y = 0.0f;
	triangle[2].vec.z = 0.0f;

	triangle[1].vec.x = 0.0f;
	triangle[1].vec.y = 1.0f;
	triangle[1].vec.z = 0.0f;

	triangle[0].vec.x = 1.0f;
	triangle[0].vec.y = 0.0f;
	triangle[0].vec.z = 0.0f;

	hr = pDevice->CreateBuffer(&bufferDesc, &vData, &pVB);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create a vertex buffer!!!", hr);
		return false;
	}
	UINT stride = sizeof(Vertex);
	UINT offset = 0;

	pImmediateDeviceContext->IASetVertexBuffers(0, 1,
		&pVB, &stride, &offset);
	
	pImmediateDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	D3D11_BUFFER_DESC cbDesc;
	cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	cbDesc.ByteWidth = sizeof(VertexShaderConstants);
	cbDesc.MiscFlags = 0;
	cbDesc.Usage = D3D11_USAGE_DYNAMIC;
	cbDesc.StructureByteStride = 0;
	
	hr = pDevice->CreateBuffer(&cbDesc, NULL, &pVSConstants);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create VS constant buffer!!!", hr);
		return false;
	}

	cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	cbDesc.ByteWidth = sizeof(PixelShaderConstants);
	cbDesc.MiscFlags = 0;
	cbDesc.Usage = D3D11_USAGE_DYNAMIC;
	cbDesc.StructureByteStride = 0;

	hr = pDevice->CreateBuffer(&cbDesc, NULL, &pPSConstants);
	if(FAILED(hr))
	{
		OutputDXError(L"Could not create PS constant buffer!!!", hr);
		return false;
	}

	return true;
}
