#include "Dx11OglRenderer.hpp"
#include "GlDxInteropExtensions.hpp"

#include <dxgi.h>
#include <wrl.h>
#include <DXGI1_5.h>

#include <Windows.h>
#include <cassert>

#include <iostream>
#include <sstream>
#include <string>


Dx11OglRenderer::~Dx11OglRenderer()
{
	if (_shrStereoColorBufferHandleLeft)
	{
		wglDXUnregisterObjectNV(_glDevice, _shrStereoColorBufferHandleLeft);
		_shrStereoColorBufferHandleLeft = nullptr;
	}

	if (_shrStereoColorBufferHandleRight)
	{
		wglDXUnregisterObjectNV(_glDevice, _shrStereoColorBufferHandleRight);
		_shrStereoColorBufferHandleRight = nullptr;
	}
	
	if (_glDevice)
		wglDXCloseDeviceNV(_glDevice);
}

bool Dx11OglRenderer::init(LUID adapterId, unsigned int eyeWidth, unsigned int eyeHeight)
{
	_eyeWidth = eyeWidth;
	_eyeHeight = eyeHeight;
	
	if (!_createDeviceFromAdapterLuid(adapterId))
		return false;

	if (!_initGl())
		return false;

	return true;
}

bool Dx11OglRenderer::_initGl()
{
	glewExperimental = GL_TRUE;
	if (glewInit() != 0)
		return false;

	if (!_initOglDxInterop())
		return false;

	return true;
}

bool Dx11OglRenderer::_initSharedTexturesLike(ComPtr<ID3D11Texture2D> tex)
{
	D3D11_TEXTURE2D_DESC td;
	tex->GetDesc(&td);
	td.ArraySize = 1;
	td.MiscFlags = 0;

	auto hr = _device->CreateTexture2D(&td, nullptr, &_oglSharedStereoColorBufferLeft);
	if (!SUCCEEDED(hr))
		return false;

	hr = _device->CreateTexture2D(&td, nullptr, &_oglSharedStereoColorBufferRight);
	if (!SUCCEEDED(hr))
		return false;

	return true;
}

bool Dx11OglRenderer::_initOglDxInterop()
{
	if (!loadInteropExtensions())
		return false;

	_glDevice = wglDXOpenDeviceNV(_device.Get());
	
	if (_glDevice == nullptr)
		return false;

	return true;
}

bool Dx11OglRenderer::_hookSharedTextures()
{
	glGenTextures(1, &_shrStereoColorBufferLeft);
	_shrStereoColorBufferHandleLeft = wglDXRegisterObjectNV(_glDevice, _oglSharedStereoColorBufferLeft.Get(), _shrStereoColorBufferLeft, GL_TEXTURE_2D, WGL_ACCESS_WRITE_DISCARD_NV);

	if (_shrStereoColorBufferHandleLeft == nullptr)
		return false;

	glGenTextures(1, &_shrStereoColorBufferRight);
	_shrStereoColorBufferHandleRight = wglDXRegisterObjectNV(_glDevice, _oglSharedStereoColorBufferRight.Get(), _shrStereoColorBufferRight, GL_TEXTURE_2D, WGL_ACCESS_WRITE_DISCARD_NV);

	if (_shrStereoColorBufferHandleRight == nullptr)
		return false;

	return true;
}

bool Dx11OglRenderer::_createDeviceFromAdapterLuid(LUID adapterID)
{
	UINT createFlags = 0;
#ifndef NDEBUG
	createFlags |= DXGI_CREATE_FACTORY_DEBUG;
#endif
	
	ComPtr<IDXGIFactory5> factory;
	auto hr = CreateDXGIFactory2(createFlags, __uuidof(factory), (void**)(&factory));
	if (!SUCCEEDED(hr))
		return false;

	ComPtr<IDXGIAdapter> dxgiAdapter;
	hr = factory->EnumAdapterByLuid(adapterID, __uuidof(dxgiAdapter), (void**)(&dxgiAdapter));
	if (!SUCCEEDED(hr))
		return false;

	//D3D11_CREATE_DEVICE_BGRA_SUPPORT !!!MUST!!! be present in order to attach the device to holographic space!
	//This is not documented in MSDN
	UINT creationFlags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#ifndef NDEBUG
	//"Graphics Tools" is required for this flag
//	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_FEATURE_LEVEL featureLevels[] =
	{
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0
	};

	hr = D3D11CreateDevice(dxgiAdapter.Get(),
		D3D_DRIVER_TYPE_UNKNOWN,
		0,
		creationFlags,
		featureLevels,
		ARRAYSIZE(featureLevels),
		D3D11_SDK_VERSION,
		&_device,
		nullptr,
		&_context);
	if (!SUCCEEDED(hr))
		return false;

	return true;
}

void Dx11OglRenderer::getGlTextures(GLuint& left, GLuint& right)
{
	left = _shrStereoColorBufferLeft;
	right = _shrStereoColorBufferRight;
}

bool Dx11OglRenderer::createOrRecreateCameraData(ComPtr<ID3D11Texture2D> backbufferTexture, unsigned int Id)
{
	if (!_cameras[Id])
		_cameras[Id] = std::make_shared<CameraRenderingResources>();

	if(!_cameras[Id]->createOrRecreateFromCameraBackbuffer(_device, backbufferTexture))
		return false;

	return true;
}

void Dx11OglRenderer::getEyeWidthHeight(unsigned int& width, unsigned int& height) const
{
	width = _eyeWidth;
	height = _eyeHeight;
}

bool Dx11OglRenderer::initSharedTexturesAfterLoadingCameraData()
{
	if (!_initSharedTexturesLike(_cameras[0]->colorBuffer))
		return false;

	if (!_hookSharedTextures())
		return false;

	return true;
}

void Dx11OglRenderer::_copyTextureToRendertarget(ComPtr<ID3D11Texture2D> tex, unsigned int eye)
{
	if (_cameras.size() == 1)
		_context->CopySubresourceRegion(_cameras[0]->colorBuffer.Get(), eye, 0, 0, 0, tex.Get(), 0, nullptr);
	else if (_cameras.size() == 2)
		_context->CopyResource(_cameras[eye]->colorBuffer.Get(), tex.Get());
}

void Dx11OglRenderer::copyTextures()
{
	_copyTextureToRendertarget(_oglSharedStereoColorBufferLeft, LEFT_EYE);
	_copyTextureToRendertarget(_oglSharedStereoColorBufferRight, RIGHT_EYE);
}

void Dx11OglRenderer::lockTexturesForGL()
{
	wglDXLockObjectsNV(_glDevice, 1, &_shrStereoColorBufferHandleLeft);
	wglDXLockObjectsNV(_glDevice, 1, &_shrStereoColorBufferHandleRight);
}

void Dx11OglRenderer::unlockTexturesFromGL()
{
	wglDXUnlockObjectsNV(_glDevice, 1, &_shrStereoColorBufferHandleLeft);
	wglDXUnlockObjectsNV(_glDevice, 1, &_shrStereoColorBufferHandleRight);
}
void Dx11OglRenderer::removeCameraByID(unsigned int id)
{
	_cameras[id].reset();
}
