/*!
	@file RuRendererPlugin_DistortFX.cpp

	Copyright (c) 2004-2007 All rights reserved
*/

#include "../../Renderer/Plugin/RuRendererPlugin_DistortFX.h"

#pragma managed(push, off)

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuRendererPlugin_DistortFX, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuRendererPlugin_DistortFX, "CRuRendererPlugin_DistortFX", "IRuObject")
ruCLASSGUID_DEFINE(CRuRendererPlugin_DistortFX, 0x1D950CA4, 0x531D4c0d, 0x98A2AD96, 0xF85E1E91)

// ************************************************************************************************************************************************************

CRuRendererPlugin_DistortFX::CRuRendererPlugin_DistortFX()
:	m_distortFXEnabled(FALSE),
	m_effectTextureSize(256),
	m_dummyCamera(NULL),
	m_rootRendererNode_0(NULL),
	m_rootRendererNode_1(NULL),
	m_texCopyNode(NULL)
{
	// Apply settings
	GetSettingsFromEngine();

	// Register listener on engine settings
	g_ruEngineSettings->RegisterSettingsChangeHandler(this->GetGUID(), boost::bind(&CRuRendererPlugin_DistortFX::EngineSettingsChangedHandler, this, _1, _2));

	m_materialHash = ruNEW CRuHashMap<CRuGUID, CRuMaterial*>(512);
}

CRuRendererPlugin_DistortFX::~CRuRendererPlugin_DistortFX()
{
	m_materialHash->ForAllElements(boost::bind(&CRuRendererPlugin_DistortFX::MaterialClearCallback, this, _1, _2));
	delete m_materialHash;

	ruSAFE_RELEASE(m_dummyCamera);
	ruSAFE_RELEASE(m_rootRendererNode_0);
	ruSAFE_RELEASE(m_rootRendererNode_1);
	ruSAFE_RELEASE(m_texCopyNode);
}

BOOL CRuRendererPlugin_DistortFX::RegisterPlugin(CRuRendererNode_RenderContext *renderContext)
{
	// Register event handlers
	renderContext->Event_Submitted().RegisterHandler(this->GetGUID(), boost::bind(&CRuRendererPlugin_DistortFX::HandleEvent_Submitted, this, _1));
	renderContext->Event_Begin().RegisterHandler(this->GetGUID(), boost::bind(&CRuRendererPlugin_DistortFX::HandleEvent_Begin, this, _1));
	renderContext->Event_Execute().RegisterHandler(this->GetGUID(), boost::bind(&CRuRendererPlugin_DistortFX::HandleEvent_Execute, this, _1));

	// Initialize dummy camera
	m_dummyCamera = ruNEW CRuCamera();

	// Initialize the root renderer node
	m_rootRendererNode_0 = ruNEW CRuRendererNode();
	m_rootRendererNode_0->PersistAcrossFrames(FALSE);

	m_rootRendererNode_1 = ruNEW CRuRendererNode();
	m_rootRendererNode_1->PersistAcrossFrames(FALSE);

	// Create the texture copy node
	m_texCopyNode = ruNEW CRuRendererNode_TextureCopy();
	m_texCopyNode->PersistAcrossFrames(FALSE);

	return TRUE;
}

BOOL CRuRendererPlugin_DistortFX::HandleEvent_Submitted(RuEventArgs *eventArgs)
{
	RuRendererEvent_Submitted_Args *submittedArgs = reinterpret_cast<RuRendererEvent_Submitted_Args *>(eventArgs);

	CRuEntity *entity = submittedArgs->m_entity;
	CRuCamera *camera = submittedArgs->m_camera;

	return TRUE;
}

BOOL CRuRendererPlugin_DistortFX::HandleEvent_Begin(RuEventArgs *eventArgs)
{
	m_managedEntitiesFlushed = FALSE;
	m_managedEntities.Clear();
	m_managedMeshes.Clear();
	m_backbufferTexture = NULL;

	// Attach the root node to the renderer
	g_ruRenderer->AttachRendererNode(m_rootRendererNode_0);
	g_ruRenderer->AttachRendererNode(m_rootRendererNode_1);

	// Setup dependency
	m_rootRendererNode_0->AddDependency(g_ruDefaultRenderContext, TRUE);
	m_rootRendererNode_1->AddDependency(m_rootRendererNode_0, TRUE);

	return TRUE;
}

BOOL CRuRendererPlugin_DistortFX::HandleEvent_Execute(RuEventArgs *eventArgs)
{
	CRuRendererEvent_Execute_Args *executeArgs = static_cast<CRuRendererEvent_Execute_Args *>(eventArgs);

	CRuRendererNode_RenderContext *targetRC = static_cast<CRuRendererNode_RenderContext *>(executeArgs->m_rendererNode);
	IRuObject *rtObject = targetRC->GetRenderTarget();

	if(rtObject->GetType().IsSubClassOf(IRuEngineResource::Type()))
	{
		// Prepare distortion texture
		if(m_backbufferTexture)
		{
			// Copy source render target onto the distortion texture
			m_texCopyNode->SetSourceTexture(static_cast<IRuEngineResource *>(rtObject));
			m_texCopyNode->SetTargetTexture(m_backbufferTexture);
			m_rootRendererNode_0->AddChild(m_texCopyNode);
		}
	}

	return TRUE;
}

BOOL CRuRendererPlugin_DistortFX::HandleEvent_Execute_2(RuEventArgs *eventArgs)
{
	return TRUE;
}
/*
BOOL CRuRendererPlugin_DistortFX::HandleEvent(CRuEntity *entity, IRuObject *eventProxy, INT32 eventID, PTRVALUE eventParameter0, PTRVALUE eventParameter1, const char *eventString, REAL eventDuration, void *userData)
{
	if(m_distortFXEnabled == FALSE)
		return FALSE;

	// Handle event
	switch(eventID)
	{
		case ruENTITYEVENT_BROADCAST_SUBMITTED:
			{
				if(entity->GetType().IsSubClassOf(IRuEntity_Renderable::Type()))
				{
					IRuEntity_Renderable *renderableEntity = static_cast<IRuEntity_Renderable *>(entity);

					RuBroadcast_Submitted_Args *eventArgs = reinterpret_cast<RuBroadcast_Submitted_Args *>(eventParameter0);
					IRuSceneRenderer *sceneRenderer = eventArgs->m_renderer;
					CRuCamera *camera = eventArgs->m_camera;

					// Iterate through every mesh
					BOOL hasFXMap = FALSE;

					for(INT32 i = 0, numMeshes = renderableEntity->GetNumMeshes(); i < numMeshes; ++i)
					{
						IRuMesh *mesh = NULL;
						IRuMaterial *material = NULL;

						if(renderableEntity->GetMesh(i, &mesh, &material))
						{
							RuPropertyType propertyValue;
							BOOL distortFX2Pass = material->GetProperty("material:distortFX2Pass", propertyValue) ? boost::get<BOOL>(propertyValue) : FALSE;
							BOOL distortFXProjected = material->GetProperty("material:distortFXProjected", propertyValue) ? boost::get<BOOL>(propertyValue) : FALSE;

							if(distortFX2Pass)
							{
								const CRuGUID &matGUID = material->GetGUID();

								CRuMaterial *alternateMaterial = NULL;

								if(m_materialHash->Get(matGUID, matGUID.HashCode(), alternateMaterial) == FALSE)
								{
									// Create alternate material
									alternateMaterial = static_cast<CRuMaterial *>(material->Clone());

									if(distortFXProjected)
										alternateMaterial->SetShader(g_ruShaderManager->GetShader("ruSHADER_STATIC_FX_DISTORT_PROJECTED"));
									else
										alternateMaterial->SetShader(g_ruShaderManager->GetShader("ruSHADER_STATIC_FX_DISTORT"));

									alternateMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_MATERIAL_TRANSLUCENT, TRUE);
									alternateMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_MATERIAL_ZTESTMODE, ruZTESTMODE_DEFAULT);
									alternateMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_MATERIAL_ZWRITEMODE, ruZWRITEMODE_DEFAULT);
									alternateMaterial->GetParameterBlock()->SetParameter(ruPARAMTYPE_MATERIAL_2SIDED, TRUE);

									// Insert into hash
									m_materialHash->Add(matGUID, matGUID.HashCode(), alternateMaterial);
								}

								if(alternateMaterial)
								{
									ManagedMeshDesc meshDesc;
									meshDesc.m_entity = renderableEntity;
									meshDesc.m_camera = eventArgs->m_camera;
									meshDesc.m_mesh = mesh;
									meshDesc.m_material = alternateMaterial;

									m_managedMeshes.Add(meshDesc);

									for(INT32 j = 0; j < material->GetNumTextureChannels(); ++j)
									{
										RuTextureChannel texChan = material->GetTextureChannel(j);
										IRuBaseTexture *tex = material->GetTexture(texChan);

										alternateMaterial->SetTexture(texChan, tex);
									}

									material = alternateMaterial;
								}
							}

							IRuShader *shader = material->GetShader();

							if(shader)
							{
								// Iterate through all samplers
								for(INT32 curSampler = 0; curSampler < 8; ++curSampler)
								{
									RuTextureChannel samplerChannel = shader->GetTextureChannel(0, curSampler);

									switch(samplerChannel)
									{
										case ruTEXCHANNEL_BACKBUFFER:
											{
												// Mark the entity as having an effect map
												hasFXMap = TRUE;

												if(m_backbufferTexture)
												{
													material->SetTexture(samplerChannel, m_backbufferTexture);
												}
												else
												{
													// No existing refraction map is allocated; allocate a new RT texture now
													IRuTexture *renderTarget = g_ruResourceManager->GetPooledRenderTarget(m_effectTextureSize, ruSURFACEFMT_A8R8G8B8);
													m_backbufferTexture = renderTarget;

													material->SetTexture(samplerChannel, renderTarget);
												}
											}

											break;
									}
								}
							}
						}
					}

					// If the entity needs a distortion map, queue it for rendering later
					if(hasFXMap)
					{
						// Stop submission of the mesh -- this mesh will be submitted by this plugin later
						eventArgs->m_continueSubmission = FALSE;

						ManagedEntityDesc managedEntityDesc = { entity, eventArgs->m_camera };
						m_managedEntities.Add(managedEntityDesc);
					}
				}
			}

			break;

		case ruENTITYEVENT_BROADCAST_BEGIN:
			{
				m_managedEntitiesFlushed = FALSE;
				m_managedEntities.Clear();
				m_managedMeshes.Clear();
				m_backbufferTexture = NULL;
			}

			break;

		case ruENTITYEVENT_BROADCAST_FLUSH_3D:
			{
			}

			break;

		case ruENTITYEVENT_BROADCAST_FLUSHED_3D:
			{
				if(m_managedEntitiesFlushed == FALSE)
				{
					IRuSceneRenderer *sceneRenderer = reinterpret_cast<IRuSceneRenderer *>(eventParameter0);

					sceneRenderer->Scene_PushEventSuppression(ruENTITYEVENT_BROADCAST_FLUSH_3D);
					sceneRenderer->Scene_PushEventSuppression(ruENTITYEVENT_BROADCAST_FLUSHED_3D);
					sceneRenderer->Scene_PushEventSuppression(ruENTITYEVENT_BROADCAST_SUBMITTED);

					// Prepare refraction texture
					if(m_backbufferTexture)
					{
						// Copy backbuffer contents to temporary render target texture
						IRuEngineResource *primaryRenderTarget = sceneRenderer->GetPrimaryRenderTarget();
						g_ruSceneRenderer->RuX_CopyTexture(primaryRenderTarget, m_backbufferTexture);

						// Force a pipeline flush
						sceneRenderer->Scene_Flush();
					}

					// Submit managed meshes
					for(INT32 i = 0; i < m_managedMeshes.Count(); ++i)
					{
						sceneRenderer->Scene_Submit(m_managedMeshes[i].m_entity, m_managedMeshes[i].m_mesh, m_managedMeshes[i].m_material);
					}

					// Force a pipeline flush
					sceneRenderer->Scene_Flush();

					// Submit managed entities
					for(INT32 i = 0; i < m_managedEntities.Count(); ++i)
					{
						sceneRenderer->Scene_Submit(m_managedEntities[i].m_entity, m_managedEntities[i].m_camera);
					}

					// Force a pipeline flush
					sceneRenderer->Scene_Flush();

					sceneRenderer->Scene_PopEventSuppression();
					sceneRenderer->Scene_PopEventSuppression();
					sceneRenderer->Scene_PopEventSuppression();

					m_managedEntitiesFlushed = TRUE;
				}
			}

			break;

		case ruENTITYEVENT_BROADCAST_END:
			{
			}

			break;
	}

	return FALSE;
}
*/
void CRuRendererPlugin_DistortFX::GetSettingsFromEngine()
{
	RuPropertyType propertyValue;

	m_distortFXEnabled = g_ruEngineSettings->GetSetting("distortFX:enable", propertyValue) ? boost::get<BOOL>(propertyValue) : FALSE;
	m_effectTextureSize = g_ruEngineSettings->GetSetting("distortFX:texture size", propertyValue) ? boost::get<INT32>(propertyValue) : m_effectTextureSize;
}

BOOL CRuRendererPlugin_DistortFX::EngineSettingsChangedHandler(const char *settingName, const RuPropertyType &setting)
{
	GetSettingsFromEngine();
	return TRUE;
}

BOOL CRuRendererPlugin_DistortFX::MaterialClearCallback(const void *key, void *data)
{
	CRuMaterial *material = *reinterpret_cast<CRuMaterial **>(data);
	ruSAFE_RELEASE(material);

	return TRUE;
}

IRuMaterial *CRuRendererPlugin_DistortFX::MaterialOverrideFunctor(IRuMaterial *sourceMaterial)
{
	return NULL;
}

// ************************************************************************************************************************************************************

#pragma managed(pop)
