#include "graphics.h"

GraphicsManager::GraphicsManager()
{ 
	// Set the start time.
	StartTime = timeGetTime();
}

GraphicsManager::~GraphicsManager() { }

VOID GraphicsManager::Release()
{	
	// Release the effect object.
	if (Effect != NULL)
	{
		Effect->Release();
		Effect = NULL;
	}

	// Release the viewframe mesh.
	if(ViewFrame != NULL)
	{
		ViewFrame->Release();
		ViewFrame = NULL;
	}

	// Release the back buffer storage.
	if(CoreBuffer != NULL)
	{
		CoreBuffer->Release();
		CoreBuffer = NULL;
	}

	// Release the device.
	if(Device != NULL)
	{
		Device->Release();
		Device = NULL;
	}
}

VOID GraphicsManager::SetOutput(OutputManager* output)
{
	// Set the output manager and pass the device for rendering.
	Output = output;
	Output->Init(Device);
}

BOOL GraphicsManager::Init(HWND hWnd, int width, int height, bool fullscreen)
{
	HRESULT hr = 0;

	// Create the Direct3D hook.
	IDirect3D9* D3D9 = 0;
    D3D9 = Direct3DCreate9(D3D_SDK_VERSION);

	// If the hook could not be established.
    if(!D3D9)
	{
		::MessageBox(0, "Direct3D Creation Failed", "Major Mosaic Error", MB_ICONERROR | MB_TASKMODAL);
		return FALSE;
	}

	// Get the device capabilities.
	D3DCAPS9 D3D9Caps;
	D3D9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3D9Caps);

	// Change the vertex processing based from capabilities.
	DWORD vertexProcessing = 0;
	if(D3D9Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
		vertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		vertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	// Set the presentation parameters.
	Parameters.BackBufferWidth            = width;
	Parameters.BackBufferHeight           = height;
	Parameters.BackBufferFormat           = D3DFMT_A8R8G8B8;
	Parameters.BackBufferCount            = 1;
	Parameters.MultiSampleType            = D3DMULTISAMPLE_NONE;
	Parameters.MultiSampleQuality         = 0;
	Parameters.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	Parameters.hDeviceWindow              = hWnd;
	Parameters.Windowed                   = !fullscreen;
	Parameters.EnableAutoDepthStencil     = true; 
	Parameters.AutoDepthStencilFormat     = D3DFMT_D24S8;
	Parameters.Flags                      = 0;
	Parameters.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	Parameters.PresentationInterval		  = D3DPRESENT_INTERVAL_IMMEDIATE;

	// Create the Direct3D Device.
	hr = D3D9->CreateDevice(
		D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,         
		hWnd,
		vertexProcessing,
	    &Parameters, 
	    &Device);

	// If the device creation failed, try a simpler format.
	if(FAILED(hr))
	{
		Parameters.AutoDepthStencilFormat = D3DFMT_D16;
		
		hr = D3D9->CreateDevice(
			D3DADAPTER_DEFAULT,
			D3DDEVTYPE_HAL,
			hWnd,
			vertexProcessing,
			&Parameters,
			&Device);

		// If the device creation failed, bail out.
		if(FAILED(hr))
		{
			D3D9->Release();
			::MessageBox(0, "Direct3D Device Creation Failed", "Major Mosaic Error", MB_ICONERROR | MB_TASKMODAL);
			return FALSE;
		}
	}

	// Release the Direct3D 9 hook.
	D3D9->Release();
	D3D9 = NULL;
	
	return TRUE;
}

BOOL GraphicsManager::Setup()
{
	// Bail if no device has been created.
	if (!Device)
	{
		MessageBox(0, "No Direct3D 9 Device Found.", "Major Mosaic Error", MB_ICONERROR | MB_TASKMODAL);
		return FALSE;
	}

	// Set initial transition values.
	Transition.Active = FALSE;
	Transition.Direction = FALSE;
	Transition.Count = 0;
	Transition.Smoothness = 3.0f;

	// Compile the main rendering effect from the shader file in resources.
	ID3DXBuffer *pCompilationErrors = 0;
	HRESULT hr = D3DXCreateEffectFromResource(Device, NULL, MAKEINTRESOURCE(ID_CORE_SHADER), 0, 0,
		D3DXSHADER_DEBUG, 0, &Effect, &pCompilationErrors);

	// If the compilation failed, explain why.
	if (FAILED(hr))
    {
        if (pCompilationErrors)
        {
            std::string compilationErrors(static_cast<const char *>(
                pCompilationErrors->GetBufferPointer()));

			MessageBox(0, compilationErrors.c_str(), "Mosiac Error", MB_ICONERROR | MB_TASKMODAL);
		} 
		else 
		{
			Output->Write(OUTPUT_ERROR, "Error Loading Core Shader");
		}

		return FALSE;
	} 

	// Set the transition smoothness in the effect.
	Effect->SetFloat("TransitionSmoothness", Transition.Smoothness);

	// Set the core technique.
	D3DXHANDLE hTechnique = Effect->GetTechniqueByName("Core");
	Effect->SetTechnique(hTechnique);

	// Construct viewframe mesh.
	CreateViewFrame();

	// Release the compilation errors buffer.
	if (pCompilationErrors != NULL)
	{
		pCompilationErrors->Release();
		pCompilationErrors = NULL;
	}

	return TRUE;
}

VOID GraphicsManager::Update()
{
	// Set the current time.
	CurrentTime = timeGetTime() - StartTime;

	D3DXCOLOR visualisationColor; 

	// Set the output text color.
	if (GetActive() == NULL)
		visualisationColor = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
	else
		visualisationColor = GetActive()->Color;

	if (GetActive() != NULL)
	{
		float averageColor = (visualisationColor.r + visualisationColor.g + visualisationColor.b) / 3.0f;

		if (averageColor < 0.5f)
				visualisationColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
			else
				visualisationColor = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
	}

	Output->SetHUDColor(visualisationColor);

	if (Transition.Active)
	{
		// Set the transition increment value from the direction.
		if (Transition.Direction)
			Transition.Increment = 1.0f - abs(cosf(CurrentTime/Transition.Speed));
		else
			Transition.Increment = abs(cosf(CurrentTime/Transition.Speed));

		// If we're transitioning.
		ActiveBuffer = TRANSITION_BUFFER;

		if (CurrentTime >= Transition.Speed) 
		{
			// Front buffer now active.
			if (Transition.Increment > 1.0f - 0.01f)
			{
				// Stop transitioning.
				Transition.Increment = 1.0f;
				Transition.Active = FALSE;

				Output->Write(OUTPUT_NORMAL, 
					"<GRPHCS> Transition Complete - [%s] Active", 
					FrontVisualisation->Name.c_str());

				// Report status if not silent.
				if (!FrontVisualisation->IsSilent)
					Output->ToHUD("[" + ToUpper(FrontVisualisation->Name) + "] Visualisation Active"); 

				// Set active as front and increment counter.
				ActiveBuffer = FRONT_BUFFER;
				Transition.Count++;
			}
			// Back buffer now active.
			else if (Transition.Increment < 0.0f + 0.01f)
			{
				// Stop transitioning.
				Transition.Increment = 0.0f;
				Transition.Active = FALSE;

				Output->Write(OUTPUT_NORMAL, 
					"<GRPHCS> Transition Complete - [%s] Active", 
					BackVisualisation->Name.c_str());

				// Report status if not silent.
				if (!BackVisualisation->IsSilent)
					Output->ToHUD("[" + ToUpper(BackVisualisation->Name) + "] Visualisation Active"); 

				// Set active as back and increment counter.
				ActiveBuffer = BACK_BUFFER;
				Transition.Count++;
				
			}
		}
	}
}

VOID GraphicsManager::BeginTransition()
{
	// Begin a transition from a random location.
	BeginTransition(&D3DXVECTOR4(
		Random(StartTime / Transition.Count * timeGetTime(), 1.0f), 
		Random(StartTime * timeGetTime(), 1.0f), 
		0.0f, 
		0.0f));
}

VOID GraphicsManager::BeginTransition(D3DXVECTOR4* position)
{
	if (!Transition.Active)
	{
		// Begin transition from front to back.
		if (Transition.Direction)
			Output->Write(OUTPUT_INFO, 
			"<GRPHCS> Beginning Transition from [%s] > [%s]..", 
			GetVisualisationName(FrontVisualisation).c_str(), 
			GetVisualisationName(BackVisualisation).c_str());
		else
			// Begin transition from back to front.
			Output->Write(OUTPUT_INFO, 
			"<GRPHCS> Beginning Transition from [%s] > [%s]..", 
			GetVisualisationName(BackVisualisation).c_str(), 
			GetVisualisationName(FrontVisualisation).c_str());
		
		// Set some transition properties.
		Transition.Direction = !Transition.Direction;
		StartTime = timeGetTime();
		Transition.Active = TRUE;

		// Set the decided position of the transition effect.
		Effect->SetVector("TransitionPosition", position);
	}
}

std::string GraphicsManager::GetVisualisationName(Visualisation* visualisation)
{
	// Return EMPTY if there is no visualisation.
	if (visualisation == NULL)
		return "EMPTY";

	// Return the name.
	return visualisation->Name;
}

IDirect3DDevice9* GraphicsManager::GetDevice()
{
	// Get the device.
	return Device;
}

Visualisation* GraphicsManager::GetActive()
{
	// Return the active visualisation.
	switch (ActiveBuffer)
	{
		case FRONT_BUFFER:
			return FrontVisualisation;
		case BACK_BUFFER:
			return BackVisualisation;
		default:
			return NULL;
	}
}

VOID GraphicsManager::BeginRender()
{
	// Bail if there is no device.
	if (!Device)
		return;

	// Clear the back buffer.
	Device->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0 );

	// Begin rendering.
    Device->BeginScene();

		// Save the back buffer.
		Device->GetRenderTarget(0, &CoreBuffer);

		// Set the position and properties of the visualisation camera.
		// The Z value is 450.0f in order to get the whole visualisation in.
		WorldCamera.Position = D3DXVECTOR3(0.0f,0.0f,450.0f);
		WorldCamera.SetMatrices();

		// Set the various matrices.
		D3DXMatrixIdentity(&World);
		View = WorldCamera.GetViewMatrix();
		Projection = WorldCamera.GetProjectionMatrix();

		// Transform the device from the WVP matrices.
		Device->SetTransform(D3DTS_VIEW, &View);
		Device->SetTransform(D3DTS_PROJECTION, &Projection);
		Device->SetTransform(D3DTS_WORLD, &World);
}

VOID GraphicsManager::EndRender()
{
		// Render to the saved back buffer.
		Device->SetRenderTarget(0, CoreBuffer);

		// Set the position and properties of the ViewFrameCamera.
		ViewFrameCamera.Position = D3DXVECTOR3(0.0f,0.0f,-1.4f);
		ViewFrameCamera.SetMatrices();

		// Set the various matrices.
		D3DXMatrixIdentity(&World);
		View = ViewFrameCamera.GetViewMatrix();
		Projection = ViewFrameCamera.GetProjectionMatrix();

		// Transform the device from the WVP matrices.
		Device->SetTransform(D3DTS_VIEW, &View);
		Device->SetTransform(D3DTS_PROJECTION, &Projection);
		Device->SetTransform(D3DTS_WORLD, &World);

		// Render the viewframe.
		RenderViewFrame();

		// Draw the HUD.
		if (Output != NULL)
			Output->DrawHUD();

		// Draw the console.
		if (Output != NULL && Output->Show)
			Output->DrawConsole();	

	// End the rendering process.
	Device->EndScene();

	// Present the rendered frame to the screen.
    Device->Present(NULL, NULL, NULL, NULL);
}

void GraphicsManager::RenderViewFrame()
{
	// Set the increment value in the effect.
	Effect->SetFloat("TransitionIncrement", Transition.Increment);

	// Draw the viewframe with the core render effect.
	UINT Pass, Passes;
	Effect->Begin(&Passes, 0);
	for (Pass = 0; Pass < Passes; Pass++)
	{
		Effect->BeginPass(Pass);
			ViewFrame->DrawSubset(0);
		Effect->EndPass();
	}

	Effect->End();
}

VOID GraphicsManager::SetSurface(BUFFER_TYPE Buffer)
{
	switch (Buffer)
	{
		case FRONT_BUFFER:
			// Set the front visualisation as active and clear the device.
			Device->SetRenderTarget(0, FrontVisualisation->Surface);
			Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, FrontVisualisation->Color, 1.0f, 0);
			break;
		case BACK_BUFFER:
			// Set the back visualisation as active and clear the device.
			Device->SetRenderTarget(0, BackVisualisation->Surface);
			Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, BackVisualisation->Color, 1.0f, 0);
			break;
	}
}

VOID GraphicsManager::SetVisualisation(BUFFER_TYPE Buffer, Visualisation* visualisation)
{
	switch (Buffer)
	{
		case FRONT_BUFFER:
			// Set the rendered textures of the front visualisation in the effect.
			FrontVisualisation = visualisation;
			Effect->SetTexture("FrontBufferTexture", FrontVisualisation->Texture);
			break;
		case BACK_BUFFER:
			// Set the rendered textures of the back visualisation in the effect.
			BackVisualisation = visualisation;
			Effect->SetTexture("BackBufferTexture", BackVisualisation->Texture);
			break;
	}
}

IDirect3DSurface9* GraphicsManager::GetBuffer(BUFFER_TYPE Buffer)
{
	switch (Buffer)
	{
		case FRONT_BUFFER:
			if (FrontVisualisation == NULL)
				return NULL;

			// Get the front visualisation surface.
			return FrontVisualisation->Surface;
		case BACK_BUFFER:
			if (BackVisualisation == NULL)
				return NULL;

			// Get the back visualisation surface.
			return BackVisualisation->Surface;
	}
}

void GraphicsManager::CreateViewFrame()
{
	// Create the mesh for the viewframe that will render the rendered textures
	// with the post-processing effect.

	D3DXCreateMeshFVF(2, 4, D3DXMESH_MANAGED, Vertex::FVF, Device, &ViewFrame);

	// Set vertex buffer.
	Vertex* v = 0;
	ViewFrame->LockVertexBuffer(0, (void**)&v);

	v[0] = Vertex( -1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
	v[1] = Vertex( -1.0f,  1.0f, 1.0f, 0.0f, 0.0f);
	v[2] = Vertex(  1.0f,  1.0f, 1.0f, 1.0f, 0.0f);
	v[3] = Vertex(  1.0f, -1.0f, 1.0f, 1.0f, 1.0f);

	ViewFrame->UnlockVertexBuffer();

	// Set index buffer.
	WORD* i = 0;
	ViewFrame->LockIndexBuffer(0, (void**)&i);

	i[0] = 0; i[1] = 1; i[2] = 2;
	i[3] = 0; i[4] = 2; i[5] = 3;

	ViewFrame->UnlockIndexBuffer();

	// Set adjacency buffer.
	std::vector<DWORD> adjacencyBuffer(ViewFrame->GetNumFaces() * 3);
	ViewFrame->GenerateAdjacency(0, &adjacencyBuffer[0]);
}