#include "xna/xna-dx.hpp"
#include "xna/game/gdevicemanager.hpp"

namespace xna {
	static void reset(GraphicsDevice::PlatformImplementation& impl)
	{
		if (impl._device) {
			impl._device->Release();
			impl._device = nullptr;
		}

		if (impl._context) {
			impl._context->Release();
			impl._context = nullptr;
		}

		if (impl._factory) {
			impl._factory->Release();
			impl._factory = nullptr;
		}
	}

	static void createDevice(GraphicsDevice::PlatformImplementation& impl) {
		//
		// See ref
		//
		// D3D_DRIVER_TYPE
		// https://learn.microsoft.com/en-us/windows/win32/api/d3dcommon/ne-d3dcommon-d3d_driver_type
		//
		// D3D11CreateDevice function 
		// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice
		//		

		auto createDeviceFlags = 0;
#if _DEBUG
		createDeviceFlags = D3D11_CREATE_DEVICE_FLAG::D3D11_CREATE_DEVICE_DEBUG;
#endif        

		const auto& currentAdapter = impl._adapter;
		const auto& pAdapter = GraphicsAdapter::UseNullDevice() ? NULL : currentAdapter->impl->dxAdapter.Get();		
		
		//
		// if pAdapter is not NULL driverType must be D3D_DRIVER_TYPE_UNKNOWN
		//
		auto driverType = D3D_DRIVER_TYPE_UNKNOWN;

		if (GraphicsAdapter::UseReferenceDevice())
			driverType = D3D_DRIVER_TYPE_WARP;
		else if (GraphicsAdapter::UseNullDevice())
			driverType = D3D_DRIVER_TYPE_HARDWARE;

		auto hr = D3D11CreateDevice(
			//_In_opt_ IDXGIAdapter* pAdapter,
			pAdapter,
			//D3D_DRIVER_TYPE DriverType,
			driverType,
			//HMODULE Software,
			NULL,
			//UINT Flags,
			createDeviceFlags,
			//_In_reads_opt_( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
			impl.featureLevels,
			//UINT FeatureLevels,
			7,
			//UINT SDKVersion,
			D3D11_SDK_VERSION,
			//_COM_Outptr_opt_ ID3D11Device** ppDevice
			impl._device.GetAddressOf(),
			//_Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
			&impl.currentFeatureLevel,
			//_COM_Outptr_opt_ ID3D11DeviceContext** ppImmediateContext
			impl._context.GetAddressOf());

		if FAILED(hr)
			Exception::Throw(Exception::FAILED_TO_CREATE);
	}

	static void initAndApplyState(GraphicsDevice::PlatformImplementation& impl, PGraphicsDevice const& device) {
		impl._blendState->Bind(device);
		impl._blendState->Initialize();
		impl._blendState->Apply();

		impl._rasterizerState->Bind(device);
		impl._rasterizerState->Initialize();
		impl._rasterizerState->Apply();

		impl._depthStencilState->Bind(device);
		impl._depthStencilState->Initialize();
		impl._depthStencilState->Apply();

		impl._samplerStates->Apply(*device);
	}

	GraphicsDevice::GraphicsDevice() {		
		impl = unew<PlatformImplementation>();
		impl->_adapter = GraphicsAdapter::DefaultAdapter();		
	}

	GraphicsDevice::GraphicsDevice(GraphicsDeviceInformation const& info) {
		impl = unew<PlatformImplementation>();
		
		impl->_adapter = info.Adapter;
		impl->_presentationParameters = info.PresentParameters;		
	}	

	GraphicsDevice::GraphicsDevice(sptr<GraphicsAdapter> const& adapter, GraphicsProfile const& graphicsProfile, sptr<PresentationParameters> const& presentationParameters) {
		impl = unew<PlatformImplementation>();
		impl->_adapter = adapter;			
		impl->_presentationParameters = presentationParameters;		
	}

	bool GraphicsDevice::Initialize() {
		auto _this = shared_from_this();

		if (!impl)
			impl = uptr<PlatformImplementation>();

		reset(*impl);
		
		createDevice(*impl);

		auto hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&impl->_factory);
		
		if FAILED(hr)
			Exception::Throw(Exception::FAILED_TO_CREATE);

		//const auto bounds = impl->_gameWindow->ClientBounds();

		impl->_viewport = xna::Viewport(0.0F, 0.0F,
			impl->_presentationParameters->BackBufferWidth,
			impl->_presentationParameters->BackBufferHeight,
			0.0F, 1.F);

		//COLORREF color = impl->_gameWindow->impl->Color();
		const auto backColor = Colors::CornflowerBlue;
		const auto backColorV3 = backColor.ToVector3();

		impl->_backgroundColor[0] = backColorV3.X;
		impl->_backgroundColor[1] = backColorV3.Y;
		impl->_backgroundColor[2] = backColorV3.Z;
		impl->_backgroundColor[3] = 1.0f;

		impl->_swapChain = snew<xna::SwapChain>(_this);
		impl->_swapChain->Initialize();

		auto hwnd = reinterpret_cast<HWND>(impl->_presentationParameters->DeviceWindowHandle);
		hr = impl->_factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
		
		if (FAILED(hr)) 
			Exception::Throw(Exception::FAILED_TO_MAKE_WINDOW_ASSOCIATION);

		impl->_renderTarget2D = snew<RenderTarget2D>(_this);
		
		if (!impl->_renderTarget2D->Initialize())
			return false;

		impl->_renderTarget2D->Apply();

		D3D11_VIEWPORT view{};
		view.TopLeftX = impl->_viewport.X;
		view.TopLeftY = impl->_viewport.Y;
		view.Width = impl->_viewport.Width;
		view.Height = impl->_viewport.Height;
		view.MinDepth = impl->_viewport.MinDetph;
		view.MaxDepth = impl->_viewport.MaxDepth;

		impl->_context->RSSetViewports(1, &view);

		initAndApplyState(*impl, _this);

		return true;
	}

	bool GraphicsDevice::Present() {
		if (!impl) return false;

		const auto result = impl->_swapChain->Present(impl->_usevsync);
		impl->_context->OMSetRenderTargets(
			1, 
			impl->_renderTarget2D->render_impl->_renderTargetView.GetAddressOf(), 
			nullptr);

		return result;
	}		

	void GraphicsDevice::Clear(Color const& color) {
		if (!impl) return;

		const auto v4 = color.ToVector4();

		impl->_backgroundColor[0] = v4.X;
		impl->_backgroundColor[1] = v4.Y;
		impl->_backgroundColor[2] = v4.Z;
		impl->_backgroundColor[3] = v4.W;
		
		impl->_context->ClearRenderTargetView(
			impl->_renderTarget2D->render_impl->_renderTargetView.Get(),
			impl->_backgroundColor);
	}

	void GraphicsDevice::Clear(ClearOptions options, Color const& color, float depth, Int stencil) {
		if (!impl) return;

		switch (options)
		{
		case xna::ClearOptions::DepthBuffer:
			Exception::Throw(Exception::NOT_IMPLEMENTED);
			break;
		case xna::ClearOptions::Stencil:
			Exception::Throw(Exception::NOT_IMPLEMENTED);
			break;
		case xna::ClearOptions::Target:
			Clear(color);
			break;
		default:
			return;
		}
	}

	void GraphicsDevice::Clear(ClearOptions options, Vector4 const& color, float depth, Int stencil) {
		if (!impl) return;


	}

	sptr<GraphicsAdapter> GraphicsDevice::Adapter() const {
		if (!impl) return nullptr;

		return impl->_adapter;
	}	

	xna::Viewport GraphicsDevice::Viewport() const {
		if (!impl) return {};

		return impl->_viewport;
	}

	void GraphicsDevice::Viewport(xna::Viewport const& viewport) {
		if (!impl) return;

		impl->_viewport = viewport;
	}

	void GraphicsDevice::UseVSync(bool use) {
		if (!impl) return;

		impl->_usevsync = use;
	}	

	
	sptr<xna::BlendState> GraphicsDevice::BlendState() const {
		return impl->_blendState;
	}
	
	void GraphicsDevice::BlendState(sptr<xna::BlendState> const& value) {
		impl->_blendState = value;
	}
	
	sptr<xna::DepthStencilState> GraphicsDevice::DepthStencilState() const {
		return impl->_depthStencilState;
	}
	
	void GraphicsDevice::DepthStencilState(sptr<xna::DepthStencilState> const& value) {
		impl->_depthStencilState = value;
	}
	
	sptr<xna::RasterizerState> GraphicsDevice::RasterizerState() const {
		return impl->_rasterizerState;
	}
	
	void GraphicsDevice::RasterizerState(sptr<xna::RasterizerState> const& value) {
		impl->_rasterizerState = value;
	}

	sptr<SamplerStateCollection> GraphicsDevice::SamplerStates() const {
		return impl->_samplerStates;
	}

	Int GraphicsDevice::MultiSampleMask() const {
		return impl->_multiSampleMask;
	}

	void GraphicsDevice::MultiSampleMask(Int value) {
		impl->_multiSampleMask = value;
	}

	void GraphicsDevice::Reset(sptr<PresentationParameters> const& presentationParameters, sptr<GraphicsAdapter> const& graphicsAdapter){
		impl = unew<PlatformImplementation>();
		impl->_adapter = graphicsAdapter;
		impl->_presentationParameters = presentationParameters;
		
		Initialize();
	}
}