/* MasterPeaceD3D - written by Jared Bruni
purposely written a mix between C++-style && C-style for fun :)
*/

//#define _DEBUG_ON

#include"mx3d.h"
#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ | D3DFVF_TEX1 )
mxHwnd mxhwnd;
mxScreen &screen = mxhwnd.screen;
const int STARTUP_SCREEN = 0;
LPDIRECT3DTEXTURE9 textures[11];
IDirect3DVertexBuffer9 *g_pVertexBuffer;
int num_images = 10;
static char *img_array[] = { "img\\block_clear.bmp", "img\\block_dblue.bmp", "img\\block_gray.bmp", "img\\block_green.bmp", "img\\block_ltblue.bmp", "img\\block_orange.bmp", "img\\block_pink.bmp", "img\\block_purple.bmp","img\\block_red.bmp","img\\block_yellow.bmp" };
HFONT fnt[2] = {CreateFont(17,0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,NONANTIALIASED_QUALITY,VARIABLE_PITCH,"Arial"), CreateFont(50,0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,NONANTIALIASED_QUALITY,VARIABLE_PITCH,"Arial")};
int cursor_pos = 0, score = 0, blocks = 0, speed = 15;


struct block {
	int x,y,color;
};
struct tile {
	int color;
	bool ani;
	int prev_color;

	tile() {
		prev_color = 0;
		ani = false;
	}

	bool operator==(const int &x) {
		if(this->color == x)
			return true;
			return false;
	}

	void operator=(int x) {
		if(x == 0) {
			prev_color = color, ani = true;
		}
		this->color = x;
	}
	bool operator!=(const int &x) {
		if(this->color != x)
			return true;
		return false;
	}
};

struct tile tiles[8][17];
struct block game_blocks[3];
static float cube_rot_x = 1.0f;

struct Vertex
{
	float x, y, z;
	float tu, tv;
};

Vertex Vertices[] =
{
	{-1.0f, 1.0f,-1.0f,  0.0f,0.0f },
	{ 1.0f, 1.0f,-1.0f,  1.0f,0.0f },
	{-1.0f,-1.0f,-1.0f,  0.0f,1.0f },
	{ 1.0f,-1.0f,-1.0f,  1.0f,1.0f },

	{-1.0f, 1.0f, 1.0f,  1.0f,0.0f },
	{-1.0f,-1.0f, 1.0f,  1.0f,1.0f },
	{ 1.0f, 1.0f, 1.0f,  0.0f,0.0f },
	{ 1.0f,-1.0f, 1.0f,  0.0f,1.0f },

	{-1.0f, 1.0f, 1.0f,  0.0f,0.0f },
	{ 1.0f, 1.0f, 1.0f,  1.0f,0.0f },
	{-1.0f, 1.0f,-1.0f,  0.0f,1.0f },
	{ 1.0f, 1.0f,-1.0f,  1.0f,1.0f },

	{-1.0f,-1.0f, 1.0f,  0.0f,0.0f },
	{-1.0f,-1.0f,-1.0f,  1.0f,0.0f },
	{ 1.0f,-1.0f, 1.0f,  0.0f,1.0f },
	{ 1.0f,-1.0f,-1.0f,  1.0f,1.0f },

	{ 1.0f, 1.0f,-1.0f,  0.0f,0.0f },
	{ 1.0f, 1.0f, 1.0f,  1.0f,0.0f },
	{ 1.0f,-1.0f,-1.0f,  0.0f,1.0f },
	{ 1.0f,-1.0f, 1.0f,  1.0f,1.0f },

	{-1.0f, 1.0f,-1.0f,  1.0f,0.0f },
	{-1.0f,-1.0f,-1.0f,  1.0f,1.0f },
	{-1.0f, 1.0f, 1.0f,  0.0f,0.0f },
	{-1.0f,-1.0f, 1.0f,  0.0f,1.0f }
};

inline void randtile() {
	unsigned int i;
	for(i = 0; i < 3; i++) {
		game_blocks[i].color = (rand()%num_images)+1;
	}

	if(game_blocks[0].color == game_blocks[1].color && game_blocks[0].color == game_blocks[2].color) {
		randtile();
		return;
	}

	game_blocks[0].x = 3;
	game_blocks[0].y = 0;
	game_blocks[1].x = 3;
	game_blocks[1].y = 1;
	game_blocks[2].x = 3;
	game_blocks[2].y = 2;
}

inline void merge_blocks() {
	int p;
	for(p = 0; p < 3; p++)
		tiles[game_blocks[p].x][game_blocks[p].y] = game_blocks[p].color;
	randtile();
}

inline void init_game() {
	memset(tiles,0,sizeof(tiles));
	score = 0;
	blocks = 0;
	speed = 15;
	randtile();
}

inline void move_left() {
	int p;
	for(p = 0; p < 3; p++) {
		if(game_blocks[p].x-1 < 0 || tiles[game_blocks[p].x-1][game_blocks[p].y] != 0)
			return;
	}

	for(p = 0; p < 3; p++) {
		game_blocks[p].x--;
	}
}

inline void move_right() {
	int p;
	for(p = 0; p < 3; p++) {
		if(game_blocks[p].x+1 >= 8 || tiles[game_blocks[p].x+1][game_blocks[p].y] != 0)
			return;
	}

	for(p = 0; p < 3; p++) {
		game_blocks[p].x++;
	}
}

inline void move_down() {
	if(game_blocks[2].y+1 < 17 && tiles[game_blocks[2].x][game_blocks[2].y+1] == 0)
	{
		int p;
		for(p = 0; p < 3; p++) {
			game_blocks[p].y++;
		}
	}
	else if(game_blocks[2].y <= 2) {
		mxhwnd.SetScreen(3);
	}
	else
		merge_blocks();
}

inline void move_blocks_down() {
	static int cnt = 0;
	for(int i = 0; i < 8; i++)
		for(int z = 0; z < 16; z++)
			if(tiles[i][z] != 0 && tiles[i][z+1] == 0) {
				tiles[i][z+1] = tiles[i][z];
				tiles[i][z] = 0;
				move_blocks_down();
				return;
			}

			if(++cnt > 3)
			{
				cnt = 0;
				cube_rot_x = 1;
			}
}

inline bool bounds(int x, int y) {
	if(x < 0 || y < 0 || x >= 8 || y >= 17)
		return false;
	return true;
}

inline void inc_score() {
	score += 10;
	blocks++;
	if(blocks > 10)
		speed = 12;
	else if(blocks > 20) {
		speed = 8;
	}
}

inline void proc_blocks() {

	for(int i = 0; i < 8; i++) {
		for(int z = 0; z < 17; z++) {
			int cur_color = tiles[i][z].color;
			if(cur_color != 0) {
				// 3 in a row going across
				if(bounds(i+2,z) && tiles[i+1][z] == cur_color && tiles[i+2][z] == cur_color) {
					tiles[i][z] = 0;
					tiles[i+1][z] = 0;
					tiles[i+2][z] = 0;
					inc_score();
				}
				// 3 in a row going down
				if(bounds(i,z+2) && tiles[i][z+1] == cur_color && tiles[i][z+2] == cur_color) {
					tiles[i][z] = 0;
					tiles[i][z+1] = 0;
					tiles[i][z+2] = 0;
					inc_score();
				}

				// 3 in a row diagnoal

				if(bounds(i+2,z+2) && tiles[i+1][z+1] == cur_color && tiles[i+2][z+2] == cur_color) {
					tiles[i][z] = 0;
					tiles[i+1][z+1] = 0;
					tiles[i+2][z+2] = 0;
					inc_score();
				}

				if(bounds(i-2,z-2) && tiles[i-1][z-1] == cur_color && tiles[i-2][z-2] == cur_color) {
					tiles[i][z] = 0;
					tiles[i-1][z-1] = 0;
					tiles[i-2][z-2] = 0;
					inc_score();
				}

				if(bounds(i-2,z+2) && tiles[i-1][z+1] == cur_color && tiles[i-2][z+2] == cur_color) {
					tiles[i][z] = 0;
					tiles[i-1][z+1] = 0;
					tiles[i-2][z+2] = 0;
					inc_score();
				}

				if(bounds(i+2,z-2) && tiles[i+1][z-1] == cur_color && tiles[i+2][z-2] == cur_color) {
					tiles[i][z] = 0;
					tiles[i+1][z-1] = 0;
					tiles[i+2][z-2] = 0;
					inc_score();
				}
			}
		}
	}
}

inline void shift_blocks() {
	int ic1;
	int ic2;
	int ic3;
	ic1 = game_blocks[0].color;
	ic2 = game_blocks[1].color;
	ic3 = game_blocks[2].color;
	game_blocks[0].color = ic3;
	game_blocks[1].color = ic1;
	game_blocks[2].color = ic2;
}
LRESULT CALLBACK MpProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) {
	switch(msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_ACTIVATEAPP:
			mxhwnd.active = wParam;
			break;
		case WM_KEYDOWN:
			{
				switch(mxhwnd.GetScreen()) 
				{
				case 1:
					if(wParam == VK_UP || wParam == VK_DOWN)
						cursor_pos = !cursor_pos;
					else if(wParam == VK_RETURN) {
						switch(cursor_pos) 
						{
						case 0:
							mxhwnd.text.setfont(fnt[0]);
							mxhwnd.SetScreen(2);
							break;
						case 1:
							mxhwnd.Kill();
							break;
						}
					}
					break;
				case 2:
					{
						switch(wParam) 
						{
						case VK_LEFT:
							move_left();
							break;
						case VK_RIGHT:
							move_right();
							break;
						case VK_DOWN:
							move_down();
							break;
						case 'A':
						case 'a':
							shift_blocks();
							break;
						}
					}
					break;
				}
			}
			break;
		default:
			return DefWindowProc(hwnd,msg,wParam,lParam);
	}
	return 0;
}

void drawCube(LPDIRECT3DTEXTURE9 tex) {
	screen.pd3d_dev->SetTexture( 0, tex );
	screen.pd3d_dev->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(Vertex) );
	screen.pd3d_dev->SetFVF( D3DFVF_CUSTOMVERTEX );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP,  4, 2 );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP,  8, 2 );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP, 12, 2 );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP, 16, 2 );
	screen.pd3d_dev->DrawPrimitive( D3DPT_TRIANGLESTRIP, 20, 2 );
}

void render_intro() {
	D3DXMATRIX matView;
	static float y = 45.0f;
	y++;
	D3DXMatrixPerspectiveFovLH( &matView, D3DXToRadian( y ), 640.0f / 480.0f, 0.1f, 100.0f );
	mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_VIEW,&matView);

	mxhwnd.screen.pd3d_dev->BeginScene();

	static float roll = 0.0f, angle_x = 0, angle_y = 0, angle_z = 0;
	roll += 0.1f;
	D3DXMATRIX trans,rot_y, rot_x, rot_z,world,rotf;
	for(float x = -12; x < 13; x += 3.0f)
		for(float y = 8; y > -9; y -= 3.0f) {
			D3DXMatrixTranslation( &trans, .5f+x, y, 50.0f );
			D3DXMatrixRotationX(&rot_x, D3DXToRadian(angle_x));
			D3DXMatrixRotationZ(&rot_y, D3DXToRadian(angle_z));
			D3DXMatrixRotationY(&rot_z, D3DXToRadian(angle_y));
			world = rot_x * rot_y * rot_z * trans;
			mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_WORLD,&world);

			drawCube(textures[(rand()%num_images)+1]);
		}
		mxhwnd.screen.pd3d_dev->EndScene();
		if(mxhwnd.input.key.KeyCheck(DIK_UP)) {
			angle_x += 0.2f;
		}
		if(mxhwnd.input.key.KeyCheck(DIK_DOWN)) {
			angle_x -= 0.2f;
		}
		if(mxhwnd.input.key.KeyCheck(DIK_LEFT)) {
			angle_y += 0.2f;
		}
		if(mxhwnd.input.key.KeyCheck(DIK_RIGHT)) {
			angle_y -= 0.2f;
		}
		if(mxhwnd.input.key.KeyCheck(DIK_Z)) {
			angle_z += 0.2f;
		}
		if(mxhwnd.input.key.KeyCheck(DIK_X)) {
			angle_z -= 0.2f;
		}


		angle_y += 0.5f;
		angle_x += 0.5f;
		angle_z += 0.5f;

		mxhwnd.text.settextcolor(D3DCOLOR_XRGB(rand()%255,rand()%255,rand()%255));
		mxhwnd.text.printtextf(100,100,"%s Start New Game\n %s Exit.", cursor_pos == 0 ? "=)>" : "" , cursor_pos == 1 ? "=)>" : "");


}

void render_startup() {
	static bool enter_pressed = false;
	static float ang_x = 0.0f, ang_y = 0.0f, ang_z = 0.0f, far_z = 2.8f;

	D3DXMATRIX trans,rot_y,rot_x,rot_z,world;
	D3DXMatrixTranslation( &trans, 0, 0, far_z );
	D3DXMatrixRotationZ(&rot_z, ang_z);
	D3DXMatrixRotationY(&rot_y, ang_y);
	D3DXMatrixRotationX(&rot_x, ang_x);
	if(enter_pressed == false)
		mxhwnd.text.printtextf(640/2-70,0,"Press [Enter] To continue..\n");
	world = rot_x * rot_y * rot_z * trans;
	mxhwnd.screen.pd3d_dev->BeginScene();
	mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_WORLD, &world);
	drawCube(textures[0]);
	mxhwnd.screen.pd3d_dev->EndScene();

	if(enter_pressed == false) {
		if(mxhwnd.input.key.KeyCheck(DIK_RETURN)) 
			enter_pressed = true;
	}
	else {
		if(ang_y < 10)
			ang_y += 0.1f;
		else {
			ang_x += 0.1f;
			ang_y += 0.1f;
			if(ang_y > 10) {
				ang_z += 0.1f;
				if(ang_z > 10) {
					far_z += 0.1f;
					if(far_z > 30) {
						mxhwnd.SetScreen(1);
						mxhwnd.text.setfont(fnt[1]);
						ang_x = ang_y = ang_z = 0;
						far_z = 2.8f;
					}
				}
			}
		}
#ifdef _DEBUG_ON
		mxhwnd.text.printtextf(0,0,"%f %f %f %f\n", ang_y, ang_x, ang_z, far_z);
#endif
	}
}

void render_game_over() {
	mxhwnd.text.printtextf(100,100,"Game over!!");
}

void render_game() {
	static float spacing = 3.0f, per = 140.0f;
	float trans_x = -16.0f, trans_y = 30.0f;
	static int movement = 0;
	if(++movement > speed) {
		move_down();
		movement = 0;
	}

	proc_blocks();
	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian( per ), 640.0f / 480.0f, 0.1f, 150.0f );
	static float rot_y = 1.0f;
	static bool rotate = false;
	static bool found_ani = false;
	rot_y ++;
	screen.pd3d_dev->SetTransform( D3DTS_VIEW, &matProj );
	mxhwnd.screen.pd3d_dev->BeginScene();
	
	for(int i = 0; i < 8; i++) {
		for(int z = 0; z < 17; z++) {
			D3DXMATRIX trans,mrot_y,mrot_z, mrot_x, mat;
			D3DXMatrixTranslation(&trans,trans_x,trans_y,30.0f);
			D3DXMatrixRotationY(&mrot_y, rot_y);
			D3DXMatrixRotationZ(&mrot_z, rot_y);
			D3DXMatrixRotationX(&mrot_x, rot_y);
			mat = trans;
			mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_WORLD,&mat);
			bool drawn = false;
			for(int p = 0; p < 3; p++) {
				if(game_blocks[p].x == i && game_blocks[p].y == z) {
					if(mxhwnd.input.key.KeyCheck(DIK_S))
						rotate = true;
					else
						rotate = false;
					if(rotate == true) {
						mat = mrot_x * mrot_y * mrot_z * trans;
						mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_WORLD,&mat);
					}
					drawCube(textures[game_blocks[p].color]);
					drawn = true;
				}
			}
			if(drawn == false) {
				if(tiles[i][z].ani == false)
				drawCube(textures[tiles[i][z].color]);
				else {
					
					
					D3DXMatrixRotationY(&mrot_y, cube_rot_x);
					D3DXMatrixRotationZ(&mrot_z, cube_rot_x);
					D3DXMatrixRotationX(&mrot_x, cube_rot_x);
					mat = mrot_y * mrot_x * mrot_z * trans;
					mxhwnd.screen.pd3d_dev->SetTransform(D3DTS_WORLD,&mat);
					drawCube(textures[(rand()%num_images)+1]);
					if((cube_rot_x += 0.1f) > 10) {
						tiles[i][z].ani = false;
						tiles[i][z].color = 0;
						move_blocks_down();
					}

					
				}
			}
			trans_y -= 3.5f;
		}
		trans_x += 4.5f;
		trans_y = 30.0f;
	}
	mxhwnd.screen.pd3d_dev->EndScene();
	mxhwnd.text.printtextf(10,10,"Score: %d , Blocks: %d, Speed: %d", score, blocks, speed);

	if(mxhwnd.input.key.KeyCheck(DIK_SPACE))
		spacing += .01f;

	if(mxhwnd.input.key.KeyCheck(DIK_Z)) {
		per -= 0.1f;
	}

	if(mxhwnd.input.key.KeyCheck(DIK_X)) {
		per += 0.1f;
	}

}

void render(int screen) {
	mxhwnd.SetTicks();
	switch(screen) {
		case 0:
			render_startup();
			break;
		case 1:
			render_intro();
			break;
		case 2:
			render_game();
			break;
		case 3:
			render_game_over();
			break;
	}
	if(screen == 2)
		mxhwnd.WaitTicks(30);
	else
		mxhwnd.WaitTicks(5);
}

inline void load() {
	srand(unsigned int(GetTickCount()));

	D3DXCreateTextureFromFile( screen.pd3d_dev, "img\\logo.bmp", &textures[0] );
	for(int i = 0; i < num_images; i++) 
		D3DXCreateTextureFromFile(screen.pd3d_dev, img_array[i], &textures[i+1] );

	screen.pd3d_dev->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
	screen.pd3d_dev->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );

	screen.pd3d_dev->CreateVertexBuffer( 24*sizeof(Vertex),0, D3DFVF_CUSTOMVERTEX,D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL );
	void *pVertices = NULL;

	g_pVertexBuffer->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 );
	memcpy( pVertices, Vertices, sizeof(Vertices) );
	g_pVertexBuffer->Unlock();

	screen.pd3d_dev->SetRenderState( D3DRS_LIGHTING, FALSE );
	screen.pd3d_dev->SetRenderState( D3DRS_ZENABLE, TRUE );

	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian( 45.0f ), 640.0f / 480.0f, 0.1f, 100.0f );
	screen.pd3d_dev->SetTransform( D3DTS_PROJECTION, &matProj );
	mxhwnd.SetScreen(STARTUP_SCREEN);
	init_game();
}

inline void rls() {
	for(int i = 0; i < num_images+1; i++) {
		if(textures[i]) 
			textures[i]->Release();
	}
	if(g_pVertexBuffer)
		g_pVertexBuffer->Release();

	DeleteObject(fnt[0]);
	DeleteObject(fnt[1]);
}

int PASCAL WinMain(HINSTANCE hInst,HINSTANCE hPrev, LPSTR line, int cmd) {

	if(mxhwnd.Init("mpd3d", false, 0,0,640,480,MpProc,0) && mxhwnd.InitDX(false,32)) {
		memset(textures, 0, sizeof(textures));
		load();
		mxhwnd.text.setfont(fnt[0]);
		mxhwnd.InitLoop(render);
		rls();
	}


	return 0;
}