#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN

#include <iostream>

extern "C" {

#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")

__declspec(dllimport) unsigned long __stdcall CreateWindowExA(
    unsigned long dwExStyle,
    const char * lpClassName,
    const char * lpWindowName,
    unsigned long dwStyle,
    int X,
    int Y,
    int nWidth,
    int nHeight,
    unsigned long hWndParent,
    unsigned long hMenu,
    unsigned long hInstance,
    void * lpParam);

#define WS_VISIBLE          0x10000000L
#define WS_CAPTION          0x00C00000L
#define WS_SYSMENU          0x00080000L

__declspec(dllimport) unsigned short __stdcall GetAsyncKeyState(int vKey);

#define VK_ESCAPE         0x1B

__declspec(dllimport) void __stdcall Sleep(
    unsigned long dwMilliseconds
    );

typedef struct tagMSG
    {
    unsigned long hwnd;
    unsigned int message;
    unsigned long wParam;
    unsigned long lParam;
    unsigned long time;
    long pt[2];
    } 	MSG;

__declspec(dllimport) int __stdcall PeekMessageA(
    MSG * lpMsg,
    unsigned long hWnd,
    unsigned int wMsgFilterMin,
    unsigned int wMsgFilterMax,
    unsigned int wRemoveMsg);

__declspec(dllimport) int __stdcall TranslateMessage(
    const MSG *lpMsg);

__declspec(dllimport) long __stdcall DispatchMessageA(
    const MSG *lpMsg);

__declspec(dllimport) __declspec(noreturn) void __stdcall ExitProcess(
    unsigned int uExitCode
    );

__declspec(dllimport) int __stdcall IsWindow(
    unsigned long hWnd);

__declspec(dllimport) unsigned long __stdcall GetTickCount();

__declspec(dllimport) unsigned long __stdcall GetDC(
    unsigned long hWnd);

__declspec(dllimport) int __stdcall PatBlt(unsigned long hdc, int x, int y, int w, int h, unsigned long rop);

#define BLACKNESS           (unsigned long)0x00000042 /* dest = BLACK                    */
#define WHITENESS           (unsigned long)0x00FF0062 /* dest = WHITE                    */

__declspec(dllimport) int __stdcall SetWindowTextA(
    unsigned long hWnd,
    const char * lpString);

}

#define MAX_LENGTH 50
#define WIDTH 60
#define HEIGHT 40
#define CELL_SIZE 10
#define MIN_SPEED 50

#define FIELD(x, y) ((x) * WIDTH + (y))

char apple[2];
char snake[MAX_LENGTH][2];
char dir, len;
bool field[WIDTH * HEIGHT];
unsigned short speed;
unsigned long score;
bool gameover;
unsigned short need_walls, curr_walls;

char score_text[] = "Score: 0                         Game over! Press F2 to new game.";

void draw_cell(unsigned long hdc, char x, char y, bool clear = false)
{
	if (clear)
	{
		PatBlt(hdc, x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, BLACKNESS);
		return;
	}
	PatBlt(hdc, x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, WHITENESS);
	PatBlt(hdc, x * CELL_SIZE + 1, y * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2, BLACKNESS);
	PatBlt(hdc, x * CELL_SIZE + 3, y * CELL_SIZE + 3, CELL_SIZE - 6, CELL_SIZE - 6, WHITENESS);
}

unsigned short random()
{
	static unsigned short prev = GetTickCount() % 0xffff;
	unsigned short curr = (35789L * (unsigned long)prev + 13849L + GetTickCount() / 0x10) % 0xffff;
	prev = curr;
	return curr;
}

unsigned long hdc;

void new_game()
{
	dir = 0;
	len = 3;
	speed = 300;
	snake[0][0] = snake[1][0] = snake[2][0] = WIDTH / 2;
	snake[0][1] = HEIGHT / 2;
	snake[1][1] = snake[0][1] + 1;
	snake[2][1] = snake[1][1] + 1;
	apple[0] = 1 + random() % (WIDTH - 2);
	apple[1] = 1 + random() % (HEIGHT - 2);
	score = 0;
	score_text[32] = 0;
	gameover = false;
	need_walls = 0;
	curr_walls = 0;
	
	for (int i = 0; i <= WIDTH; i++)
	{
		for (int j = 0; j <= HEIGHT; j++)
		{
			field[FIELD(i, j)] = (char)(i == 0 || j == 0 || i == WIDTH || j == HEIGHT);
		}
	}
}

void redraw_field()
{
	PatBlt(hdc, 0, 0, 640, 480, BLACKNESS);
	for (int i = 0; i <= WIDTH; i++)
	{
		for (int j = 0; j <= HEIGHT; j++)
		{
			if (field[FIELD(i, j)])
				draw_cell(hdc, i, j);
		}
	}
}

#define abs(x) ((x < 0) ? -x : x)

void my_itoa(unsigned long n, char * buf, short len)
{
	short i;

	for (i = 0; i < len; i++)
		buf[i] = ' ';

	if (n == 0)
	{
		buf[len] = '0';
		return;
	}

	i = 0;
	do
	{
		buf[len - i] = n % 10 + '0';
		i++;
	} while ((n /= 10) > 0 && (i <= len));
}

int main(int argc, char ** argv)
{
	unsigned long prevTick = GetTickCount();
	unsigned long flashTick = GetTickCount();
	unsigned long hwnd = CreateWindowExA(0, "#32771", 0, WS_CAPTION | WS_VISIBLE | WS_SYSMENU, 0, 0, 640, 480, 0, 0, 0, 0);
	hdc = GetDC(hwnd);
	
	new_game();
	redraw_field();

	while(1)
	{
		MSG msg;
		unsigned long currTick = GetTickCount();
		
		draw_cell(hdc, apple[0], apple[1], (currTick - flashTick)%300 < 150);
		if (currTick - flashTick > 300)
		{
			flashTick = currTick;
		}

		if (currTick - prevTick > speed && !gameover)
		{
			draw_cell(hdc, snake[len - 1][0], snake[len - 1][1], true);

			prevTick = currTick;
			char x = snake[0][0], y = snake[0][1];
			if (dir == 0 || dir == 2) y += dir == 0 ? -1 : 1;
			if (dir == 1 || dir == 3) x += dir == 3 ? -1 : 1;
			if (field[FIELD(x, y)])
			{
				gameover = true;
				score_text[32] = ' ';
				SetWindowTextA(hwnd, score_text);
			}
			else
			{
				if (x == apple[0] && y == apple[1])
				{
					char it = 0;
					do
					{
						apple[0] = 1 + random() % (WIDTH - 2);
						apple[1] = 1 + random() % (HEIGHT - 2);
						it++;
					} while (field[FIELD(apple[0], apple[1])] && it < 10);
					speed -= speed / 10;
					if (speed < MIN_SPEED)
						speed = MIN_SPEED;

					if (len < MAX_LENGTH - 1)
						len++;

					score += 30;
					need_walls += speed / MIN_SPEED;
				}
				else if (curr_walls < need_walls)
				{
					char i, j, it = 0;

					do
					{
						i = random()%2 == 0 ? (1 + random() % (WIDTH - 2)) : WIDTH - (1 + random() % (WIDTH - 2));
						j = random()%2 == 0 ? (1 + random() % (HEIGHT - 2)) : HEIGHT - (1 + random() % (HEIGHT - 2));
						it++;
					} while ((field[FIELD(i, j)] || (apple[0] == i && apple[1] == j) || (abs(x - i) < 2 && abs(y - j) < 3) || (abs(snake[len - 1][0] - i) < 2 && abs(snake[len - 1][1] - j) < 3)) && (it < 10));
					draw_cell(hdc, i, j);
					field[FIELD(i, j)] = 1;

					curr_walls++;

					redraw_field();
				}

				for (char i = len; i > 0; i--)
				{
					if (snake[i][0] > 0)
						field[FIELD(snake[i][0], snake[i][1])] = 0;

					snake[i][0] = snake[i - 1][0];
					snake[i][1] = snake[i - 1][1];

					field[FIELD(snake[i][0], snake[i][1])] = 1;
				}
				snake[0][0] = x;
				snake[0][1] = y;

				field[FIELD(snake[0][0], snake[0][1])] = 1;

				for(int i = 0; i < len; i++)
				{
					draw_cell(hdc, snake[i][0], snake[i][1]);
				}
			
				if (speed > MIN_SPEED * 5)
					speed--;
				score++;

				my_itoa(score, score_text + 7, 16);
				SetWindowTextA(hwnd, score_text);
			}
		}

		while (PeekMessageA(&msg, 0, 0, 0, 1))
		{
			if (msg.message == 0x100)
			{
				char new_dir = 100;
				if (msg.wParam == 0x26 && dir != 2) // VK_UP
					new_dir = 0;
				if (msg.wParam == 0x28 && dir != 0) // VK_DOWN
					new_dir = 2;
				if (msg.wParam == 0x25 && dir != 1) // VK_LEFT
					new_dir = 3;
				if (msg.wParam == 0x27 && dir != 3) // VK_RIGHT
					new_dir = 1;

				if (new_dir != 100)
				{
					char x = snake[0][0], y = snake[0][1];
					if (new_dir == 0 || new_dir == 2) y += new_dir == 0 ? -1 : 1;
					if (new_dir == 1 || new_dir == 3) x += new_dir == 3 ? -1 : 1;
					if (snake[1][0] != x && snake[1][1] != y)
						dir = new_dir;
				}

				if (msg.wParam == 0x71) // VK_F2
				{
					new_game();
					redraw_field();
				}
			}
			if (msg.message == 0x0f)
				redraw_field();

			TranslateMessage(&msg);
			DispatchMessageA(&msg);
		}

		if (!IsWindow(hwnd))
			break;
	}

	return 0;
}