//------------------------------------------------------------
// Tony Hyun Kim
// Spring 2007
// 18.354 Project: Lattice gas
// DRIVER
//------------------------------------------------------------

#include <windows.h>
#include "d3d_ResourceManager.h"
#include "Camera.h"
#include "node_manager.h"
#include "NodeGeometry.h"

// Clock
Clock  myClock(30);

// Selected node
NODE*  selectedNode = NULL;

// Window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

void	DrawNodes();
void	DrawSelectedNode(NODE* node);
void	DrawParticles();

float		GetVecAngle(D3DXVECTOR2& v);
D3DXVECTOR2 GetPlaneCoordinates(int x, int y);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
				   LPSTR lpszCmdLine, int iShowCmd)
{
	//------------------------------------------------------------
	// Main window initialization
	//------------------------------------------------------------
	int cxWidth  = 1024;
	int cyHeight = 768;

	WNDCLASS wc;
	wc.style		 = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc	 = WndProc;
	wc.cbClsExtra	 = 0;
	wc.cbWndExtra	 = 0;
	wc.hIcon		 = LoadIcon(0,IDI_APPLICATION);
	wc.hCursor		 = LoadCursor(0,IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.hInstance	 = 0;
	wc.lpszMenuName	 = NULL;
	wc.lpszClassName = "Lattice";

	if(!RegisterClass(&wc))
	{
		MessageBox(0,"Failure: RegisterClass",0,MB_ICONERROR);
		PostQuitMessage(0);
	}

	HWND hwnd;
	hwnd = CreateWindow("Lattice",
						"Lattice gas simulation",
						WS_OVERLAPPEDWINDOW,
						CW_USEDEFAULT,
						CW_USEDEFAULT,
						cxWidth, cyHeight,
						NULL, NULL,
						hInstance, NULL);

	ShowWindow(hwnd,iShowCmd);
	UpdateWindow(hwnd);

	// Initialize Direct3D
	myD3D.InitD3D(hwnd,cxWidth,cyHeight);
	
	Demo5();

	//------------------------------------------------------------
	// Main loop
	//------------------------------------------------------------
	MSG msg;
	msg.message = WM_NULL;

	D3DXMATRIX V;
	while(msg.message != WM_QUIT)
	{
		if(PeekMessage(&msg,0,0,0,PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			myCamera.GetViewMatrix(&V);
			myD3D.d3dDevice->SetTransform(D3DTS_VIEW,&V);

			myD3D.d3dDevice->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0x00000000,1.0f,0);
			myD3D.d3dDevice->BeginScene();

			DrawNodes();
			
			if(selectedNode)
				DrawSelectedNode(selectedNode);
			else
			{
				if( myClock.getTime() == myClock.getPeriod() )
					myNodes.Tick();
			}
			DrawParticles();

			myD3D.d3dDevice->EndScene();
			myD3D.d3dDevice->Present(0,0,0,0);
		}
	}

	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static int iScroll, index;
	static const float fNodeMovement = g_spacing/100.0f;
	switch(msg)
	{
	//------------------------------------------------------------
	// Handle keyboard input
	//------------------------------------------------------------
	case WM_KEYDOWN:
		switch(wParam)
		{
		case VK_ESCAPE:
			DestroyWindow(hwnd);
			break;
		case VK_DELETE:
		case 'Q':
			if(selectedNode)
			{
				myNodes.DelNode(selectedNode);
				selectedNode = 0;
			}
			break;
		case VK_RETURN:
			myNodes.Tick();
			break;
		case VK_SPACE:
			selectedNode = 0;
			break;

		case 'R':
			myCamera.Zoom(1.0f);
			break;
		case 'F':
			myCamera.Zoom(-1.0f);
			break;
		case 'C':
			myNodes.ClearAllParticles();
			break;
		case 'V':
			myNodes.RandomlyFillParticles();
			break;
		}

		if(selectedNode)
		{
			switch(wParam)
			{
			case 'W':
				selectedNode->pos.y += fNodeMovement;
				myNodes.ReorderNPy(selectedNode);
				break;
			case 'S':
				selectedNode->pos.y -= fNodeMovement;
				myNodes.ReorderNPy(selectedNode);
				break;
			case 'A':
				selectedNode->pos.x -= fNodeMovement;
				myNodes.ReorderNPx(selectedNode);
				break;
			case 'D':
				selectedNode->pos.x += fNodeMovement;
				myNodes.ReorderNPx(selectedNode);
				break;

			//------------------------------------------------------------
			// Add/remove particles
			// Unfortunately, the logic is slightly convoluted with the 
			// addition of two types of particles.
			//------------------------------------------------------------
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '0':
				index = wParam%6;
				if(selectedNode->neighbors[index])
				{
					if(GetAsyncKeyState(VK_SHIFT)<0) // Shift pressed?
					{
						// Already red?
						if( CheckPosByte(selectedNode->current.occ,  index) &&
							CheckPosByte(selectedNode->current.type, index) )
						{
							// In that case, turn off
							selectedNode->current.occ &= ~g_PosFlags[index];
						}
						else
						{
							// Otherwise, create red particle
							selectedNode->current.occ  |= g_PosFlags[index];
							selectedNode->current.type |= g_PosFlags[index];
						}
					}
					else
					{
						// Already blue?
						if(  CheckPosByte(selectedNode->current.occ,  index) &&
							!CheckPosByte(selectedNode->current.type, index) )
						{						
							// then, turn off
							selectedNode->current.occ &= ~g_PosFlags[index];
						}
						else
						{
							// Otherwise create blue particle
							selectedNode->current.occ  |=  g_PosFlags[index];
							selectedNode->current.type &= ~g_PosFlags[index];
						}
					}
				}
				break;
			}
			
			myNodes.UpdateNode(selectedNode);
		}
		else
		{
			switch(wParam)
			{
			case 'W':
				myCamera.Scroll(1.0f);
				break;
			case 'S':
				myCamera.Scroll(-1.0f);
				break;
			case 'A':
				myCamera.Strafe(-1.0f);
				break;
			case 'D':
				myCamera.Strafe(1.0f);
				break;
			}
		}


		break;

	//------------------------------------------------------------
	// Handle mouse input
	//------------------------------------------------------------
	case WM_LBUTTONDOWN:
		selectedNode = myNodes.GetNodeByPos(GetPlaneCoordinates(LOWORD(lParam),HIWORD(lParam)));
		break;

	case WM_RBUTTONDOWN:
		{
			NODE newNode = NODE(GetPlaneCoordinates(LOWORD(lParam),HIWORD(lParam)));
			myNodes.AddNode(newNode);
		}
		break;

	case WM_MOUSEWHEEL:
		if((short)HIWORD(wParam)>0)
			myCamera.Zoom(1.0f);
		else
			myCamera.Zoom(-1.0f);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

void DrawNodes()
{
	static list<NODE>::iterator iter;
	static D3DXMATRIX  W, R, S;
	static D3DXVECTOR2 v;
	static int i;

	myD3D.d3dDevice->SetMaterial(&myD3D.NetworkMaterial);
	list<NODE> nodes = myNodes.GetNodeData();

	for(iter = nodes.begin(); iter != nodes.end(); iter++)
	{
		D3DXMatrixTranslation(&W, (*iter).pos.x, (*iter).pos.y, 0.0f);
		myD3D.d3dDevice->SetTransform(D3DTS_WORLD,&W);
		
		myD3D.NodeMesh->DrawSubset(0);

		for(i=0; i<3; i++)
		{
			if((*iter).neighbors[i])
			{
				v = (*iter).neighbors[i]->pos - (*iter).pos;
				D3DXMatrixScaling(&S,1.0f,1.0f,D3DXVec2Length(&v));

				D3DXMatrixRotationZ(&R,GetVecAngle(v));
				myD3D.d3dDevice->SetTransform(D3DTS_WORLD,&(S*myD3D.CylinderMatrix*R*W));
				myD3D.CylinderMesh->DrawSubset(0);
			}
		}
	}
}

void DrawSelectedNode(NODE* node)
{
	static D3DXMATRIX W;
	static int i;

	myD3D.d3dDevice->SetMaterial(&myD3D.SelectedMaterial);

	D3DXMatrixTranslation(&W, node->pos.x, node->pos.y, 0.0f);
	myD3D.d3dDevice->SetTransform(D3DTS_WORLD,&W);
	myD3D.NodeMesh->DrawSubset(0);
}

void DrawParticles()
{
	static list<NODE>::iterator iter;
	static D3DXMATRIX  W;
	static D3DXVECTOR2 v;
	static int i;

	list<NODE> nodes = myNodes.GetNodeData();

	// Draw blue particles
	myD3D.d3dDevice->SetMaterial(&myD3D.BlueMaterial);
	for(iter = nodes.begin(); iter != nodes.end(); iter++)
	{
		for(i=0; i<6; i++)
			if(  CheckPosByte((*iter).current.occ,  i) &&
				!CheckPosByte((*iter).current.type, i) )  
			{
				v = (*iter).pos + (myClock.getFraction() - 1.0f)*g_dir[i];
				D3DXMatrixTranslation(&W, v.x, v.y, 0.0f);
				myD3D.d3dDevice->SetTransform(D3DTS_WORLD, &W);
				myD3D.ParticleMesh->DrawSubset(0);
			}
	}

	// Draw red particles
	myD3D.d3dDevice->SetMaterial(&myD3D.RedMaterial);
	for(iter = nodes.begin(); iter != nodes.end(); iter++)
	{
		for(i=0; i<6; i++)
			if( CheckPosByte((*iter).current.occ,  i) &&
				CheckPosByte((*iter).current.type, i) )  
			{
				v = (*iter).pos + (myClock.getFraction() - 1.0f)*g_dir[i];
				D3DXMatrixTranslation(&W, v.x, v.y, 0.0f);
				myD3D.d3dDevice->SetTransform(D3DTS_WORLD, &W);
				myD3D.ParticleMesh->DrawSubset(0);
			}
	}
}

float GetVecAngle(D3DXVECTOR2& v)
{
	return ((v.y>0)?1:-1)*acos(v.x/D3DXVec2Length(&v));
}

D3DXVECTOR2 GetPlaneCoordinates(int x, int y)
{
	static float px, py;
	static float cx, cy, cz; // Camera coordinates

	D3DVIEWPORT9 vp;
	myD3D.d3dDevice->GetViewport(&vp);
	D3DXMATRIX proj;
	myD3D.d3dDevice->GetTransform(D3DTS_PROJECTION, &proj);

	px = ((( 2.0f*x) / vp.Width)  - 1.0f) / proj(0,0);
	py = (((-2.0f*y) / vp.Height) + 1.0f) / proj(1,1);

	cx =  myCamera.GetPos().x;
	cy =  myCamera.GetPos().y;
	cz = -myCamera.GetPos().z;

	return D3DXVECTOR2( cz*px + cx,
						cz*py + cy );
}

