//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007-2008)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: d3d9_graphicDriver.hpp
//      Краткое описание: графический драйвер Direc3D9
//

#ifdef GD_D3D9

#include <Darkstar/Graphic System/Direct3D/d3d9_graphicDrvier.hpp>
#include <Darkstar/Kernel/logh.hpp>
#include <Darkstar/Graphic System/Render State/common_render_state.hpp>

using namespace gs;
using rs::DEViewport;
using ker::Log;

GraphicDriver::GraphicDriver()
{
	device = NULL;
	d3d = NULL;

	change_display_mode_on = false;
	current_primitive_topology = rs::DEPT_UNDEFINED;
	cur_color.reset();
	cur_depth.reset();
};

GraphicDriver::~GraphicDriver()
{
	if(device)
	{
		backBuffer.reset();
		depthBuffer.reset();
		if(FAILED(device->Release()))
			lwar << "Cannot release d3d device";
		device = NULL;
	};
	if(d3d)
	{
		if(FAILED(d3d->Release()))
			lwar << "Cannot release direct 3d interface";
		d3d = NULL;
	};

};

IDirect3DDevice9* GraphicDriver::getDevice() const
{
	return device;
};

bool GraphicDriver::_setBackAndDepthBuffers()
{
	backBuffer.reset(new RenderTarget());
	depthBuffer.reset(new RenderTarget());
	LPDIRECT3DSURFACE9 back_buf;
	if(FAILED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buf)))
	{
		lwar << "Cannot get back buffer";
		return false;
	};
	D3DSURFACE_DESC desc;
	back_buf->GetDesc(&desc);
	shared_ptr<DisplayMode> new_display_mode(new DisplayMode(desc.Width, desc.Height, current_display_mode->getPixelFormat(), current_display_mode->getRefreshRate()));
	current_display_mode = new_display_mode;
	if(!backBuffer->create2D(back_buf))
	{
		lerr << "Cannot create render target for back buffer";
		return false;
	};
	if(!depthBuffer->create2D(current_display_mode->getWidth(), current_display_mode->getHeight(), DRTFMT_D16_UNORM, current_aa_mode, false))
	{
		lerr << "Cannot create depth buffer";
		return false;
	};
	return true;
};

bool GraphicDriver::changeDisplayMode(const shared_ptr<DisplayMode>& new_mode, const DRTAATYPE& new_aa_type, const DISPLAY_MODE& display_mode)
{
	/*if(((is_fullscreen && (display_mode == DDM_FULLSCREEN)) || 
		(!is_fullscreen && (display_mode == DDM_WINDOWED)) || display_mode == DDM_WITHOUT_CHANGE)
		&& *current_display_mode == *new_mode && current_aa_mode.count == new_aa_type.count && current_aa_mode.quality == new_aa_type.quality)
		return true;
	change_display_mode_on = true;
	if(display_mode == DDM_WINDOWED)
		is_fullscreen = false;
	else
		if(display_mode == DDM_FULLSCREEN)
			is_fullscreen = true;
		else
			if(display_mode == DDM_MAKE_CHANGE)
				is_fullscreen = !is_fullscreen;
	DXGI_SWAP_CHAIN_DESC swap_chain_desc = _fillSwapChainDesc(new_mode, new_aa_type, !is_fullscreen, output_window);
	this->current_aa_mode = new_aa_type;
	if(swap_chain)
	{
		swap_chain->SetFullscreenState(0, NULL);
		if(FAILED(swap_chain->Release()))
		{
			lwar << "Cannot release swap chain during changing display mode";
			change_display_mode_on = false;
			return false;
		};
	};
	if(FAILED(dxgi_factory->CreateSwapChain(device, &swap_chain_desc, &swap_chain)))
	{
		lerr << "Cannot create new swap chain. Fatal error";
		change_display_mode_on = false;
		return false;
	};
	current_display_mode = new_mode;
	if(!_setBackAndDepthBuffers())
	{
		lerr << "Cannot set back or depth buffer during changing display mode";
		change_display_mode_on = false;
		return false;
	};
	change_display_mode_on = false;
	return true;*/
	return true;
};

bool GraphicDriver::changeDisplayMode(const DRTAATYPE& new_aa_type, const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(current_display_mode, new_aa_type, display_mode);
};

bool GraphicDriver::changeDisplayMode(const shared_ptr<DisplayMode>& new_mode, const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(new_mode, current_aa_mode, display_mode);
};

bool GraphicDriver::changeDisplayMode(const DISPLAY_MODE& display_mode)
{
	return changeDisplayMode(current_display_mode, current_aa_mode, display_mode);
};

bool GraphicDriver::init(HWND hwnd, const shared_ptr<DisplayMode>& display_mode, 
						 const bool is_windowed /* = false */, 
						 const DRTAATYPE& aa_type /* = WITHOUT_AA */, 
						 const unsigned int back_buffer_count /* = 2 */)
{
	this->back_buffer_count = back_buffer_count;
	current_aa_mode = aa_type;
	output_window = hwnd;
	is_fullscreen = !is_windowed;
	
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if(!d3d)
	{
		lerr << "Cannot create direct3d 9 object";
		return false;
	};

	D3DDISPLAYMODE dmode;
	if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dmode)))
	{
		lwar << "Cannot get adapter display mode";
		return false;
	};

	UINT a = d3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8B8G8R8);

	UINT adapter_to_use = D3DADAPTER_DEFAULT;
	D3DDEVTYPE device_type = D3DDEVTYPE_HAL;

	for(unsigned int adapter_counter=0; adapter_counter < d3d->GetAdapterCount(); adapter_counter++)
	{
		D3DADAPTER_IDENTIFIER9 identifier;
		HRESULT res;
		res = d3d->GetAdapterIdentifier(adapter_counter, 0, &identifier);
		if(strstr(identifier.Description, "PerfHUD") != 0)
		{
			adapter_to_use = adapter_counter;
			device_type = D3DDEVTYPE_REF;
			break;
		};
	};

	D3DPRESENT_PARAMETERS present_params;
	present_params.BackBufferCount = back_buffer_count;
	present_params.AutoDepthStencilFormat = D3DFMT_D16;
	present_params.EnableAutoDepthStencil = true;
	present_params.BackBufferFormat = dmode.Format;
	present_params.BackBufferWidth = display_mode->getWidth();
	present_params.BackBufferHeight = display_mode->getHeight();
	present_params.Flags = 0;
	present_params.FullScreen_RefreshRateInHz = 0;
	present_params.hDeviceWindow = hwnd;
	present_params.MultiSampleQuality = aa_type.quality;
	present_params.MultiSampleType = aa_type.count == 1 ? D3DMULTISAMPLE_NONE : (D3DMULTISAMPLE_TYPE)aa_type.count;
	present_params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
	present_params.Windowed = !is_fullscreen;
	if(FAILED(d3d->CreateDevice(adapter_to_use, device_type, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &present_params, &device)))
	{
		lerr << "Cannot create device";
		return false;
	};

	current_display_mode = display_mode;

	// MAKE ENUMERATION OF ALL SUPPORTED 32 BIT MODES!!!!

	if(!_setBackAndDepthBuffers())
		return false;

	device->SetRenderTarget(0, backBuffer->getDataTexture());
	device->SetDepthStencilSurface(depthBuffer->getDataTexture());
	cur_color = backBuffer;
	cur_depth = depthBuffer;

	setViewport(display_mode->getWidth(), display_mode->getHeight());

	setPrimitiveTopology(rs::DEPT_TRIANGLELIST);

	return true;
};

void GraphicDriver::begin(const shared_ptr<RenderTarget>& color, const shared_ptr<RenderTarget>& depth, const unsigned int color_rt_layer)
{
	unsigned int cwidth = color->getWidth();
	unsigned int cheight = color->getHeight();
	unsigned int dwidth = depth->getWidth();
	unsigned int dheight = depth->getHeight();
	if(cwidth != dwidth || cheight != dheight || depth->getAAType().count != color->getAAType().count || depth->getAAType().quality != color->getAAType().quality)
	{
		lwar << cwidth << " " << dwidth << " " << cheight << " " << dheight;
		lwar << "Try to begin scene with non-compatible render target";
		return;
	};
	DRTFORMAT form = color->getFormat();
	if(form == DRTFMT_D16_UNORM || form == DRTFMT_D24_UNORM_S8_UINT || form == DRTFMT_D32_FLOAT)
	{
		lwar << "Color render target has depth stencil surface format";
		return;
	}
	form = depth->getFormat();
	if(form != DRTFMT_D16_UNORM && form != DRTFMT_D24_UNORM_S8_UINT && form != DRTFMT_D32_FLOAT)
	{
		lwar << "Depth stencil surface has unsupported format";
		return;
	};
	device->SetRenderTarget(0, color->getDataTexture(color_rt_layer));
	device->SetDepthStencilSurface(depth->getDataTexture());
	cur_color = color;
	cur_depth = depth;
	cur_color_rt_layer = color_rt_layer;

	device->BeginScene();
};

void GraphicDriver::begin(const shared_ptr<RenderTarget>& color, const shared_ptr<RenderTarget>& depth)
{
	begin(color, depth, 0);
};

void GraphicDriver::beginWithColor(const shared_ptr<RenderTarget>& color, const unsigned int color_rt_layer)
{
	DRTFORMAT form = color->getFormat();
	if(form == DRTFMT_D16_UNORM || form == DRTFMT_D24_UNORM_S8_UINT || form == DRTFMT_D32_FLOAT)
	{
		lwar << "Color render target has depth stencil surface format";
		return;
	};

	device->SetRenderTarget(0, color->getDataTexture(color_rt_layer));
	device->SetDepthStencilSurface(NULL);
	cur_color = color;
	cur_depth = shared_ptr<RenderTarget>();
	cur_color_rt_layer = color_rt_layer;
	device->BeginScene();
};

void GraphicDriver::beginWithColor(const shared_ptr<RenderTarget>& color)
{
	beginWithColor(color, 0);
};

void GraphicDriver::present()
{
	device->Present(NULL, NULL, NULL, NULL);
};

void GraphicDriver::end()
{
#ifdef _NOTFINAL
	if(!cur_color && !cur_depth)
	{
		lwar << "Cannot end scene";
		return;
	};
#endif
	device->EndScene();
	if(cur_color->getIsNeedTexture() && cur_color->getAAType().count != 1)
		device->StretchRect(cur_color->getDataTexture(), NULL, cur_color->getDataWithoutAATexture(), NULL, D3DTEXF_LINEAR);
	cur_color_rt_layer = 0;
	cur_color.reset();
	cur_depth.reset();
};

const shared_ptr<RenderTarget>& GraphicDriver::getBackBuffer() const
{
	return backBuffer;
};

const shared_ptr<RenderTarget>& GraphicDriver::getDepthStencilSurface() const
{
	return depthBuffer;
};

const shared_ptr<RenderTarget>& GraphicDriver::getCurrentColorRenderTarget() const
{
	return cur_color;
};

const shared_ptr<RenderTarget>& GraphicDriver::getCurrentDepthStencilRenderTarget() const
{
	return cur_depth;
};

unsigned int GraphicDriver::getCurrentColorRTLayer() const
{
	return cur_color_rt_layer;
};

void GraphicDriver::setViewport(const unsigned int width, const unsigned int height, const unsigned int topx /* = 0 */, const unsigned int topy /* = 0 */, const float min_depth /* = 0.0f */, const float max_depth /* = 1.0f */)
{
	unsigned int w = width;
	unsigned int h = height;
	if(width == 0 && height == 0)
	{
		w = current_display_mode->getWidth();
		h = current_display_mode->getHeight();
	};
	D3DVIEWPORT9 view;
	view.Height = h;
	view.Width = w;
	view.MaxZ = max_depth;
	view.MinZ = min_depth;
	view.X = topx;
	view.Y = topy;

	cur_viewport.width = w;
	cur_viewport.height = h;
	cur_viewport.topx = topx;
	cur_viewport.topy = topy;
	cur_viewport.min_depth = min_depth;
	cur_viewport.max_depth = max_depth;

	device->SetViewport(&view);
};

void GraphicDriver::setViewport(const DEViewport& new_viewport)
{
	setViewport(new_viewport.width, new_viewport.height, new_viewport.topx, new_viewport.topy, new_viewport.min_depth, new_viewport.max_depth);
};

const DEViewport& GraphicDriver::getCurViewport() const
{
	return cur_viewport;
};

void GraphicDriver::resizeBuffers(unsigned int new_width, unsigned int new_height)
{
	/*if(change_display_mode_on)
		return;
	if(!swap_chain)
		return;
	ID3D10RenderTargetView* const rt[1] = {NULL};
	device->OMSetRenderTargets(1, rt, NULL);
	backBuffer.reset();
	depthBuffer.reset();
	if(FAILED(swap_chain->ResizeBuffers(back_buffer_count, new_width, new_height, (DXGI_FORMAT)current_display_mode->getPixelFormat(), DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH)))
	{	
		lwar << "Cannot resize buffers with new width = " << new_width << " and new height = " << new_height;
		return;
	};
	IDXGIOutput* output;
	BOOL is_full;
	if(FAILED(swap_chain->GetFullscreenState(&is_full, &output)))
		lwar << "Cannot get full screen state";
	if(is_full)
		is_fullscreen = true;
	else
		is_fullscreen = false;
	if(output)
	{
		if(FAILED(output->Release()))
			lwar << "Cannot release dxgi output";
	};	
	if(!_setBackAndDepthBuffers())
		return;
	setViewport(0, 0);
	onResizeBuffers();*/
};

void GraphicDriver::addMessageListenerOnResize(const boost::function2<void, const unsigned int, const unsigned int>& new_listener)
{
	resize_message_listeners.push_back(new_listener);
};

void GraphicDriver::onResizeBuffers()
{
	for(unsigned int i = 0; i < resize_message_listeners.size(); ++i)
		resize_message_listeners[i](current_display_mode->getWidth(), current_display_mode->getHeight());
};

const shared_ptr<DisplayMode>& GraphicDriver::getCurrentDisplayMode() const
{
	return current_display_mode;
};

const std::vector<shared_ptr<DisplayMode> >& GraphicDriver::getSupported32BitDisplayMode() const
{
	return supported_modes_32bit;
};

void GraphicDriver::unsetAll()
{
	device->SetVertexShader(NULL);
	device->SetPixelShader(NULL);
	device->SetIndices(NULL);
	for(unsigned int i = 0; i < 8; ++i)
		device->SetStreamSource(i, NULL, 0, 0);
	for(unsigned int i = 0; i < sampler_num; ++i)
		device->SetTexture(i, NULL);
};

void GraphicDriver::setPrimitiveTopology(const rs::DE_PRIMITIVE_TOPOLOGY& prim_topl)
{
	return;
};

void GraphicDriver::clear(const math::Vect4& color /* = math::vect4_zero */, const float depth_value /* = 1 */, const unsigned int stencil_value/* = 0 */)
{
	DWORD flags = 0;
	if(cur_depth)
		flags |= D3DCLEAR_ZBUFFER;
	if(cur_color)
		flags |= D3DCLEAR_TARGET;
	DWORD d3d_color = D3DCOLOR_ARGB((unsigned int)(color.w*255.0f), (unsigned int)(color.x*255.0f), (unsigned int)(color.y*255.0f), (unsigned int)(color.z*255.0f));
	device->Clear(0, NULL, flags, d3d_color, depth_value, stencil_value);
};


#endif