#include "ClothsAnimator.h"

LPD3DXFONT pFont = NULL;
RECT Rec;
HFONT hFont;
char KeyStateBuffer[256];
LPDIRECT3D9 pDirect3D = NULL;
LPDIRECT3DDEVICE9 pDirect3DDevice = NULL;

// Pointer to Mesh
LPD3DXMESH pbodyMesh = NULL;
LPD3DXMESH pbodySkinMesh = NULL;//Container for skinMesh
LPD3DXMESH pclothesMesh = NULL;
// Pointers to mesh buffers
LPD3DXBUFFER pbodyMeshBuffer = NULL;
LPD3DXBUFFER pclothesMeshBuffer = NULL;
// Pointers to material buffers
D3DMATERIAL9 *pbodyMeshMaterial = NULL;
D3DMATERIAL9 *pclothesMeshMaterial = NULL;
// Pointers to texture buffers
LPDIRECT3DTEXTURE9 *pbodyMeshTexture = NULL;
LPDIRECT3DTEXTURE9 *pclothesMeshTexture = NULL;

LPDIRECT3DVERTEXBUFFER9 v_buffer = NULL;    // the pointer to the vertex buffer
LPDIRECT3DINDEXBUFFER9 i_buffer = NULL;

ID3DXSkinInfo *pSkinInfo;


//Model loader
MeshObject *bodyMeshObject;

DWORD startAnimTime;
DWORD prevFrameTime = 0;

// Numbers of materials and textures
DWORD dwbodyNumber;
DWORD dwclothesNumber = 0;

DWORD OneTick = 0;

HINSTANCE hInstance;

HRESULT hr = 0;

SubdivisionAlgorithm *sub;

cClothMesh g_Cloth;

cCollision g_Collisions;
D3DXMATRIX g_matCollision;

// Function for processing messages
LRESULT CALLBACK MainWinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
// Function for initialization of Direct3D
HRESULT InitDirect3D(HWND hwnd);
// Function for rendering Direct3D
void RenderingDirect3D(void);
// Function for deleting resourses
void DeleteDirect3D(void);
// Functions for initialization of objects
HRESULT InitBody(void);
HRESULT InitClothes(void);
HRESULT InitAnimatedModel(void);
// Function for setting transformation matrix
void Matrix(void);
// Function for drawing meshes
void DrawBody(void);
void DrawClothes(void);
void Subdivision(void);


float fAngleX = 0;
float fAngleY = 0;
float fAngleZ = 0;
LONG ñpx = 0.0f, cpy = 200.0f, cpz = -300.0f;
LONG polx = 0.0f, poly = 0.0f, polz = 0.0f;
D3DXVECTOR3 pEye(ñpx, cpy, cpz);
D3DXVECTOR3 pPol(polx, poly, polz);

std::vector<float> distancesToBody;
float avgDistance = 0;
float minDistance = 0;
float maxDistance = 0;

IDirectInput8 *g_pDI;
IDirectInputDevice8* pKeyBoard = NULL;
IDirectInputDevice8* InitKeyboard(HWND hWnd, IDirectInput8 *pDI);
void ChekKB(IDirectInputDevice8 * pDIDevice);


int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow)
{
	WNDCLASSEX windowsclass;
	HWND hwnd;
	MSG msg;
	hInstance = hInst;

	windowsclass.cbSize = sizeof(WNDCLASSEX);
	windowsclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	windowsclass.lpfnWndProc = MainWinProc;
	windowsclass.cbClsExtra = 0;
	windowsclass.cbWndExtra = 0;
	windowsclass.hInstance = hInst;
	windowsclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	windowsclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	windowsclass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
	windowsclass.lpszMenuName = NULL;
	windowsclass.lpszClassName = L"WINDOWSCLASS";
	windowsclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

	if (!RegisterClassEx(&windowsclass))
		return 0;

	hwnd = CreateWindowEx(NULL, L"WINDOWSCLASS", L"Girl", WS_OVERLAPPEDWINDOW |
		WS_VISIBLE, 0, 0, 770, 500, NULL, NULL, hInst, NULL);
	if (!hwnd)
		return 0;
	HRESULT hr;
	hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_pDI, NULL);
	if (FAILED(hr)) return FALSE;
	pKeyBoard = InitKeyboard(hwnd, g_pDI);


	if (SUCCEEDED(InitDirect3D(hwnd)))
	{
		//if (SUCCEEDED(InitCloths() && InitBody()))
		if (SUCCEEDED(InitAnimatedModel() && InitClothes()))
		{
			ShowWindow(hwnd, SW_SHOWDEFAULT);
			UpdateWindow(hwnd);
			InitClothes();
			InitBody();
			sub = new SubdivisionAlgorithm(bodyMeshObject, "dress.x", pDirect3DDevice, pbodyMesh, pclothesMesh);
			sub->Subdivision();
			g_Cloth.Create(sub->animatedClothes->MeshData.pMesh, sub->GetPointsForMassSpringSystem());

			// Clear collision object transformation
			D3DXMatrixIdentity(&g_matCollision);

			ZeroMemory(&msg, sizeof(msg));
			while (msg.message != WM_QUIT)
			{
				if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
				ChekKB(pKeyBoard);
				RenderingDirect3D();

			}
		}
	}
	return (int)msg.wParam;
}
void ChekKB(IDirectInputDevice8 * pDIDevice)
{
	HRESULT hr;
	if SUCCEEDED(hr = pDIDevice->GetDeviceState(sizeof(KeyStateBuffer), (LPVOID)KeyStateBuffer))
	{
		//Turn scene to the left
		if (KeyState(DIK_A) == TRUE)
		{
			fAngleY += .05f;
		};
		//Turn scene to the right
		if (KeyState(DIK_D) == TRUE)
		{
			fAngleY -= .05f;
		};
		//Exit from program
		if (KeyState(DIK_ESCAPE) == TRUE)
		{
			PostQuitMessage(0);
		};
	};
}

IDirectInputDevice8* InitKeyboard(HWND hWnd, IDirectInput8 *pDI)
{
	IDirectInputDevice8 *pDIDevice;

	// Create device
	if (FAILED(pDI->CreateDevice(GUID_SysKeyboard,
		&pDIDevice, NULL)))
		return NULL;

	// Set data format
	if (FAILED(pDIDevice->SetDataFormat(&c_dfDIKeyboard))) {
		pDIDevice->Release();
		return NULL;
	}

	// Set cooperative level
	if (FAILED(pDIDevice->SetCooperativeLevel(hWnd,
		DISCL_FOREGROUND | DISCL_EXCLUSIVE))) {
		pDIDevice->Release();
		return NULL;
	}
	HRESULT hr;
	// Acquire device for using
	if (hr = pDIDevice->Acquire()) {
		pDIDevice->Release();
		return NULL;
	}

	// If succeded - return pointer
	return pDIDevice;
}

// Function for proccessing messages
LRESULT CALLBACK MainWinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	switch (msg)
	{
		// Create object
	case WM_CREATE:
		break;

		//Proccessing messages from controls
	case WM_COMMAND:
		break;

		//WM_PAINT - for drawing
	case WM_PAINT:
		break;

		// WM_DESTROY - for closing window
	case WM_DESTROY:
		DeleteDirect3D();
		PostQuitMessage(0);
		break;
	}
	return (DefWindowProc(hwnd, msg, wparam, lparam));
}

// Function for initialization of Direct3D
HRESULT InitDirect3D(HWND hwnd)
{
	if (NULL == (pDirect3D = Direct3DCreate9(D3D_SDK_VERSION)))
		return E_FAIL;

	D3DDISPLAYMODE Display;
	if (FAILED(pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &Display)))
		return E_FAIL;

	D3DPRESENT_PARAMETERS Direct3DParameter;
	ZeroMemory(&Direct3DParameter, sizeof(Direct3DParameter));
	Direct3DParameter.Windowed = true;
	Direct3DParameter.SwapEffect = D3DSWAPEFFECT_DISCARD;
	Direct3DParameter.BackBufferFormat = Display.Format;
	// Turn on z-buffer
	Direct3DParameter.EnableAutoDepthStencil = TRUE;
	// Surface format of z-buffer
	Direct3DParameter.AutoDepthStencilFormat = D3DFMT_D16;

	if (FAILED(pDirect3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, &Direct3DParameter, &pDirect3DDevice)))
		return E_FAIL;
	// Turn on z-buffer
	pDirect3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

	return S_OK;
}

// Function for rendering Direct3D
void RenderingDirect3D(void)
{
	if (pDirect3DDevice == NULL)
		return;
	// Set a world transformation
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);
	pDirect3DDevice->SetTransform(D3DTS_WORLD, &matWorld);
	
	Matrix();

}

// Function for deleting captured resources
void DeleteDirect3D(void)
{
	if (pDirect3DDevice != NULL)
		pDirect3DDevice->Release();
	if (pDirect3D != NULL)
		pDirect3D->Release();
	if (pbodyMeshMaterial != NULL)
		delete[] pbodyMeshMaterial;
	if (pclothesMeshMaterial != NULL)
		delete[] pclothesMeshMaterial;
	if (pbodyMeshTexture)
	{
		for (DWORD i = 0; i < dwbodyNumber; i++)
		{
			if (pbodyMeshTexture[i])
				pbodyMeshTexture[i]->Release();
		}
		delete[] pbodyMeshTexture;
	}
	if (pclothesMeshTexture)
	{
		for (DWORD i = 0; i < dwclothesNumber; i++)
		{
			if (pclothesMeshTexture[i])
				pclothesMeshTexture[i]->Release();
		}
		delete[] pclothesMeshTexture;
	}
	if (pbodyMesh != NULL)
		pbodyMesh->Release();
	if (pclothesMesh != NULL)
		pclothesMesh->Release();

	v_buffer->Release();
	i_buffer->Release();
}

HRESULT InitBody(void)
{
	hr = D3DXLoadMeshFromX(L"body.X", D3DXMESH_SYSTEMMEM, pDirect3DDevice, NULL, &pbodyMeshBuffer, NULL, &dwbodyNumber, &pbodyMesh);
	//pMesh->CloneMeshFVF(0, pMesh->GetFVF(), pDirect3DDevice, &pbodySkinMesh);
	D3DXMATERIAL *D3DXMeshMaterial = (D3DXMATERIAL *)pbodyMeshBuffer->GetBufferPointer();
	// Extract material properties and structure names
	pbodyMeshMaterial = new D3DMATERIAL9[dwbodyNumber];
	pbodyMeshTexture = new LPDIRECT3DTEXTURE9[dwbodyNumber];
	for (UINT i = 0; i < dwbodyNumber; i++){
		pbodyMeshMaterial[i] = D3DXMeshMaterial[i].MatD3D;
		pbodyMeshMaterial[i].Ambient = pbodyMeshMaterial[i].Diffuse;
		if (!(D3DXCreateTextureFromFileA(pDirect3DDevice, D3DXMeshMaterial[i].pTextureFilename, &pbodyMeshTexture[i])))
			pbodyMeshTexture[i] = NULL;
	}
	pbodyMeshBuffer->Release();
	if (!(pbodyMesh->GetFVF() & D3DFVF_NORMAL))
	{
		// if no - clone mesh and add flag D3DFVF_NORMAL to vertex format
		ID3DXMesh* pTempMesh = 0;
		pbodyMesh->CloneMeshFVF(
			D3DXMESH_MANAGED,
			pbodyMesh->GetFVF() | D3DFVF_NORMAL, // add flag
			pDirect3DDevice,
			&pTempMesh);

		// Calculate normals
		D3DXComputeNormals(pTempMesh, 0);

		pbodyMesh->Release();  // Delete old mesh
		pbodyMesh = pTempMesh; // Save new mesh with normals
	}
	const D3DXCOLOR colorWhite(D3DCOLOR_XRGB(255, 255, 255));
	D3DLIGHT9 dir;
	D3DXVECTOR3 Light(0.0f, 0.0f, 3.0f);
	D3DXVec3Normalize(&Light, &Light);
	dir.Type = D3DLIGHT_DIRECTIONAL;
	dir.Diffuse = colorWhite;
	dir.Specular = colorWhite;
	dir.Ambient = D3DXCOLOR(D3DCOLOR_XRGB(0, 0, 0));
	dir.Direction = Light;
	pDirect3DDevice->SetLight(0, &dir);
	pDirect3DDevice->LightEnable(0, true);
	return S_OK;
}

HRESULT InitClothes(void)
{
	hr = D3DXLoadMeshFromX(L"dress.X", D3DXMESH_SYSTEMMEM, pDirect3DDevice, NULL, &pclothesMeshBuffer, NULL, &dwclothesNumber, &pclothesMesh);
	D3DXMATERIAL *D3DXMeshMaterial = (D3DXMATERIAL *)pclothesMeshBuffer->GetBufferPointer();
	// Extract material properties and structure names
	pclothesMeshMaterial = new D3DMATERIAL9[dwclothesNumber];
	pclothesMeshTexture = new LPDIRECT3DTEXTURE9[dwclothesNumber];
	int a = pclothesMesh->GetNumVertices();
	for (UINT i = 0; i < dwclothesNumber; i++){
		pclothesMeshMaterial[i] = D3DXMeshMaterial[i].MatD3D;
		pclothesMeshMaterial[i].Ambient = pclothesMeshMaterial[i].Diffuse;
		if (!(D3DXCreateTextureFromFile(pDirect3DDevice, (LPCWSTR)D3DXMeshMaterial[i].pTextureFilename, &pclothesMeshTexture[i])))
			pclothesMeshTexture[i] = NULL;
	}
	// Delete material buffer
	pclothesMeshBuffer->Release();
	if (!(pclothesMesh->GetFVF() & D3DFVF_NORMAL))
	{
		// if no - clone mesh and add flag D3DFVF_NORMAL to vertex format
		ID3DXMesh* pTempMeshs = 0;
		pclothesMesh->CloneMeshFVF(
			D3DXMESH_MANAGED,
			pclothesMesh->GetFVF() | D3DFVF_NORMAL, // add flag
			pDirect3DDevice,
			&pTempMeshs);

		// Calculate normals
		D3DXComputeNormals(pTempMeshs, 0);

		pclothesMesh->Release();  // Delete old mesh
		pclothesMesh = pTempMeshs; // Save new mesh with normals
	}
	const D3DXCOLOR colorWhite(D3DCOLOR_XRGB(255, 255, 255));
	D3DLIGHT9 dir;
	D3DXVECTOR3 Light(0.0f, 0.0f, 3.0f);
	D3DXVec3Normalize(&Light, &Light);
	dir.Type = D3DLIGHT_DIRECTIONAL;
	dir.Diffuse = colorWhite;
	dir.Specular = colorWhite;
	dir.Ambient = D3DXCOLOR(D3DCOLOR_XRGB(0, 0, 0));
	dir.Direction = Light;
	pDirect3DDevice->SetLight(0, &dir);
	pDirect3DDevice->LightEnable(0, true);
	return S_OK;
}


//Initiation of animated body model
HRESULT InitAnimatedModel(void)
{
	bodyMeshObject = new MeshObject(pDirect3DDevice);
	HRESULT hr = bodyMeshObject->LoadSkinMeshFromFile("animatedModel.X");
	startAnimTime = GetTickCount();
	return S_OK;
}

// Draw body mesh without animation
void DrawBody(void)
{
	for (DWORD i = 0; i < dwbodyNumber; i++)
	{
		pDirect3DDevice->SetMaterial(&pbodyMeshMaterial[i]);
		pDirect3DDevice->SetTexture(0, pbodyMeshTexture[i]);
		pbodyMesh->DrawSubset(i);
	}
}

//Draw cloths mesh without animation
void DrawClothes(void)
{
	for (DWORD i = 0; i < dwclothesNumber; i++)
	{
		pDirect3DDevice->SetMaterial(&pclothesMeshMaterial[i]);
		pclothesMesh->DrawSubset(i);
	}
}

// Set transformation matrix
void Matrix(void)
{
	// View matrix
	D3DXMATRIX MatrixView;
	//Projection matrix
	D3DXMATRIX MatrixProjection;
	D3DXMATRIX MatrixRotationX, MatrixRotationY, MatrixRotationZ;

	D3DXMatrixRotationX(&MatrixRotationX, fAngleX);
	D3DXMatrixRotationY(&MatrixRotationY, fAngleY);
	D3DXMatrixRotationZ(&MatrixRotationZ, fAngleZ);

	pDirect3DDevice->SetTransform(D3DTS_WORLD, &MatrixRotationY);

	D3DXMATRIX MatrixRotation1, MatrixRotation2;
	D3DXMatrixRotationY(&MatrixRotation1, 0.5f);
	D3DXMatrixRotationY(&MatrixRotation2, 0.5f);

	double currentTime = GetTickCount();
	//Update animation
	bodyMeshObject->UpdateSkinAnimation(currentTime - startAnimTime);
	sub->animatedClothes->UpdateSkinAnimation(currentTime - startAnimTime);

	//Set new matrix for bones
	bodyMeshObject->SetMatrix(&MatrixRotation1, bodyMeshObject->pSkinFrame);
	sub->animatedClothes->SetMatrix(&MatrixRotation2, sub->animatedClothes->pSkinFrame);

	//Update animation using new positions of bones
	bodyMeshObject->UpdateMesh();
	sub->animatedClothes->UpdateMesh();

	DWORD elapsedTime = timeGetTime() - prevFrameTime;
	static DWORD LastTime = timeGetTime() - 1; 
	DWORD ThisTime = timeGetTime();
	static BOOL First = TRUE;

	// Calculate elapsed time
	DWORD Elapsed = ThisTime - LastTime;
	LastTime = ThisTime;
	// Compute an angle to move the hero and cape
	float Angle = (float)timeGetTime() / 1000.0f;
	// Position the cloth
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);

	// Set gravity and wind forces
	D3DXVECTOR3 vecGravity = D3DXVECTOR3(0.0f, -9.8f, 0.0f);
	D3DXVECTOR3 vecWind = D3DXVECTOR3(0.0f, 10.0f, 0.0f);
	
	// Set the cloth's forces for simulation
	g_Cloth.SetForces(-0.05f, &vecGravity, &vecWind, &matWorld, First);
	First = FALSE;
	
	//Delete all collision objects
	g_Collisions.Free();

	bodyMeshObject->GetPointsForLegs();
	//Add new collision objects
	g_Collisions.AddCylinder(bodyMeshObject->leftLegTight_v3, bodyMeshObject->leftLegKnee_v3, bodyMeshObject->GetTightRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->rightLegTight_v3, bodyMeshObject->rightLegKnee_v3, bodyMeshObject->GetTightRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->leftLegKnee_v3, bodyMeshObject->leftLegAnkle_v3, bodyMeshObject->GetCalfRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->rightLegKnee_v3, bodyMeshObject->rightLegAnkle_v3, bodyMeshObject->GetCalfRadius());
	
	bodyMeshObject->GetPointsForArms();
	
	//Add new collision objects
	g_Collisions.AddCylinder(bodyMeshObject->leftForearm_v3, bodyMeshObject->leftElbow_v3, bodyMeshObject->GetForearmRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->rightForearm_v3, bodyMeshObject->rightElbow_v3, bodyMeshObject->GetForearmRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->rightElbow_v3, bodyMeshObject->rightHand_v3, bodyMeshObject->GetArmRadius());
	
	g_Collisions.AddCylinder(bodyMeshObject->leftElbow_v3, bodyMeshObject->leftHand_v3, bodyMeshObject->GetArmRadius());
	
	// Process the forces
	g_Cloth.ProcessForces((float)Elapsed / 1000.0f * 4.0f);
	
	// Process the collisions
	g_Cloth.ProcessCollisions(&g_Collisions, &g_matCollision);
	
	// Rebuild the cloth mesh
	g_Cloth.RebuildMesh(sub->animatedClothes->MeshData.pMesh);

	// Change view matrix
	D3DXMatrixLookAtLH(&MatrixView,      // Resulting view matrix
		&D3DXVECTOR3(pEye.x, pEye.y, pEye.z),   // point, from wich we look
		&D3DXVECTOR3(0.0f, 0.0f, 0.0f),     // where we look
		&D3DXVECTOR3(0.0f, 2.0f, 0.0f));   // top direction

	// Set view matrix
	pDirect3DDevice->SetTransform(D3DTS_VIEW, &MatrixView);
	// Change projection matrix
	D3DXMatrixPerspectiveFovLH(&MatrixProjection, // Resulting projection matrix
		D3DX_PI / 4,                                                      // Field of view in the direction of Y axis in radians
		1.54f,                                                               // Aspect ratio 770/500 = 1.54
		10.0f,                                                               // Front clipping scene
		20000.0f);                                                           // Background clipping scene
	// Set projection matrix
	pDirect3DDevice->SetTransform(D3DTS_PROJECTION, &MatrixProjection);

	pDirect3DDevice->Clear(NULL, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 64, 255), 1.0f, 0);
	if (SUCCEEDED(pDirect3DDevice->BeginScene())) {

		// Render skinned mesh
		bodyMeshObject->DrawModel();
		sub->animatedClothes->DrawModel();
		//DrawBody();
		//DrawClothes();
		prevFrameTime = timeGetTime();
		// End the scene
		pDirect3DDevice->EndScene();
	}

	// Present the scene to the user
	pDirect3DDevice->Present(NULL, NULL, NULL, NULL);
}
