#include "DirectXInterface.h"

#include "DirectX2dInterface.h"
#include "FileLogger.h"

#include <dxgi.h>
#include <dxgi1_6.h>
#include <d3d11.h>
#include <d3d11on12.h>
#include <d3d12.h>
#include <d3d12sdklayers.h>

DirectXInterface::DirectXInterface()
	: mD2dInterface(std::make_unique<DirectX2dInterface>(this))
{

}

void DirectXInterface::getHardwareAdapter(IDXGIAdapter1** ppAdapter)
{
	*ppAdapter = nullptr;
	for (UINT adapterIndex = 0; ; ++adapterIndex)
	{
		IDXGIAdapter1* pAdapter = nullptr;
		if (mDxgiFactory->EnumAdapters1(adapterIndex, &pAdapter) == DXGI_ERROR_NOT_FOUND)
		{
			break;
		}
		if (SUCCEEDED(::D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
		{
			*ppAdapter = pAdapter;
			return;
		}
		pAdapter->Release();
	}
}

bool DirectXInterface::isValid() const
{
	return mIsValid;
}

ID3D12Device* DirectXInterface::getD3dDevice() const
{
	return mD3dDevice.Get();
}

ID3D11On12Device* DirectXInterface::get11On12Device() const
{
	return mD3d11On12Device.Get();
}

IDXGIFactory7* DirectXInterface::getDxgiFactory() const
{
	return mDxgiFactory.Get();
}

ID3D12CommandQueue* DirectXInterface::getCommandQueue() const
{
	return mCommandQueue.Get();
}

ID3D11DeviceContext* DirectXInterface::getD3d11DeviceContext() const
{
	return mD3d11DeviceContext.Get();
}

DirectX2dInterface* DirectXInterface::getD2dInterface() const
{
	return mD2dInterface.get();
}

void DirectXInterface::initialize()
{
	MLOG_INFO("Initialize DirectX");
	createD3dFactory();

	Microsoft::WRL::ComPtr<IDXGIAdapter1> pAdapter;
	getHardwareAdapter(&pAdapter);
	createD3dDevice(pAdapter.Get());

	createCommandQueue();

	if (!initializeD11on12())
	{
		mIsValid = false;
		return;
	}

	Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
	mD3d11On12Device.As(&dxgiDevice);
	mIsValid = mD2dInterface->initialize(dxgiDevice.Get());
}

bool DirectXInterface::createD3dFactory()
{
	UINT dxgiFactoryFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
	{
		Microsoft::WRL::ComPtr<ID3D12Debug> debugController;
		if (SUCCEEDED(::D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
		{
			debugController->EnableDebugLayer();
			dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
		}
	}
#endif
	if (FAILED(::CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&mDxgiFactory))))
	{
		MLOG_ERROR("Failed to create DXGI Factory");
	}
	return true;
}

bool DirectXInterface::createD3dDevice(IDXGIAdapter1* pAdapter)
{
	if (pAdapter)
	{
		if (FAILED(::D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mD3dDevice))))
		{
			MLOG_ERROR("Failed to create D3D12 device, will try WARP.");
			return false;
		}
		else
		{
			Microsoft::WRL::ComPtr<IDXGIAdapter> pWarpAdapter;
			mDxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter));
			if (FAILED(::D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mD3dDevice))))
			{
				MLOG_ERROR("Failed to create WARP device.");
				return false;
			}
		}
	}
	else
	{
		MLOG_ERROR("Failed to get DirectX adapter.");
		return false;
	}
	return true;
}

bool DirectXInterface::createCommandQueue()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	if (FAILED(mD3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue))))
	{
		MLOG_ERROR("Failed to create command queue.");
		return false;
	}
	return true;
}

bool DirectXInterface::initializeD11on12()
{
	Microsoft::WRL::ComPtr<ID3D11Device> d3d11Device;
	UINT d3d11DeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
	auto pCommandQueue = reinterpret_cast<IUnknown**>(mCommandQueue.GetAddressOf());
	if (FAILED(D3D11On12CreateDevice(mD3dDevice.Get(), d3d11DeviceFlags, nullptr, 0, pCommandQueue, 1, 0, &d3d11Device, &mD3d11DeviceContext, nullptr)))
	{
		MLOG_ERROR("Failed to create D3D11 on 12 Device");
		return false;
	}
	d3d11Device.As(&mD3d11On12Device);
	return true;
}