LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId;
	PAINTSTRUCT ps;
	HDC windowDC;
	int keys;
	int wheelDelta;
	POINT tempPoint;
	bool copyBeforeErase;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		switch (wmId)
		{
		case IDM_EXIT:
			CloseEnhMetaFile(metafileDC);
			DestroyWindow(hWnd);
			break;
		case IDM_SAVE:
			SaveMetafile(hWnd);
			break;
		case IDM_OPEN:
			OpenMetafile(hWnd); 
			break;
		case IDM_CANCEL:
			/*
				//load from metafile
				copyBeforeErase = TRUE;
			*/
			shape->CancelLastAction();
			break;
		case IDM_PEN:
			if (shape != NULL)
				shape->~Shape();
			shape = new Pen(color, penWidth);
			break;
		case IDM_LINE:
			if (shape != NULL)
				shape->~Shape();
			shape = new Line(color, penWidth);
			break;
		case IDM_POLYLINE:
			if (shape != NULL)
				shape->~Shape();
			shape = new PolylineShape(color, penWidth);
			break;
		case IDM_POLYGON:
			if (shape != NULL)
				shape->~Shape();
			shape = new PolygonShape(color, penWidth);
			break;
		case IDM_ELLIPSE:
			if (shape != NULL)
				shape->~Shape();
			shape = new EllipseShape(color, penWidth);
			break;
		case IDM_RECTANGLE:
			if (shape != NULL)
				shape->~Shape();
			shape = new RectangleShape(color, penWidth);
			break;
		case IDM_TEXT:
			if (shape != NULL)
			{
				shape->~Shape();
				shape = NULL;
			}
			break;
		case IDM_ERASER:
			if (shape != NULL)
				shape->~Shape();
			shape = new EraserShape(color, penWidth);
			break;
		case IDM_COLOR:
			COLORREF chosenColor;
			if (chosenColor = GetColor(hWnd))
				color = chosenColor; 
			shape->SetPenColor(color);
			break;
		case IDM_PENWIDTH_1:
			penWidth = 1;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_2:
			penWidth = 2;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_4:
			penWidth = 4;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_8:
			penWidth = 8;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_12:
			penWidth = 12;
			shape->SetPenWidth(penWidth);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;

	case WM_CREATE:
		DragAcceptFiles(hWnd, true);
		GetClientRect(hWnd, &rect);
		memoryDC = InitialiseMemoryDC(hWnd);
		metafileDC = InitialiseEnhMetafileDC(hWnd);
		break;

	case WM_DROPFILES:
		OpenDropedFile(hWnd, (HDROP) wParam);
		break;

	case WM_LBUTTONDOWN:  
		/*if ((shape->isContinuous) && (copyBeforeErase))
		{
			//copy to metafile
			copyBeforeErase = FALSE;
		}*/
		startPoint.x = LOWORD(lParam); 
		startPoint.y = HIWORD(lParam); 
		if (shape != NULL)
		{
			isDrawing = TRUE;
			shape->SetStartPoint(startPoint);
		}
		else
		{
			memset(textSymbols, 0, MAX_LOADSTRING);
		}
		break; 

	case WM_LBUTTONUP: 
		if (shape != NULL)
		{
			if (isDrawing) 
			{ 
				windowDC = GetDC(hWnd);
				BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
				shape->Draw(memoryDC, startPoint, lParam);
				shape->Draw(metafileDC, startPoint, lParam);
				ReleaseDC(hWnd, windowDC); 
			} 
			if (shape->isFinished)
			{
				windowDC = GetDC(hWnd);
				BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
				isDrawing = FALSE; 
				ReleaseDC(hWnd, windowDC); 
			}
		}
		UpdateWindow(hWnd);
		break; 
	
	case WM_RBUTTONDOWN:
		if (shape != NULL)
		{
			shape->isFinished = TRUE;
			isDrawing = FALSE;  
			if (shape->PolylineFirstPoint.x != -1)
			{
				windowDC = GetDC(hWnd);
				shape->Draw(memoryDC, startPoint, lParam);
				shape->Draw(metafileDC, startPoint, lParam);
				BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
				ReleaseDC(hWnd, windowDC);
			}
			shape->PolylineLastPoint.x = -1;
		}
		break;

	case WM_MOUSEMOVE: 
		if (shape != NULL)
		{
			if (isDrawing) 
			{ 
				windowDC = GetDC(hWnd);
				if (shape->isContinuous)
				{
					tempPoint = shape->GetStartPoint();
					BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
					shape->Draw(memoryDC, startPoint, lParam);
					shape->SetStartPoint(tempPoint);
					shape->Draw(metafileDC, startPoint, lParam);
				}
				ReleaseDC(hWnd, windowDC); 
				UpdateWindow(hWnd);
				PostMessage(hWnd, WM_PAINT, NULL, NULL);
			} 
		}
		break; 

	case WM_CHAR:
		if (shape == NULL)
		{
			UINT charCode = (UINT)wParam;
			TCHAR symbol = (TCHAR)charCode;

			int i = 0;
			while (true)
			{
				if (textSymbols[i] == '\0')
				{
					textSymbols[i] = symbol;
					textSymbols[i + 1] = '\0';
					break;
				}
				i++;
			}
			LPSTR str = (LPSTR)textSymbols;
			RECT textRect;

			textRect.left = startPoint.x;
			textRect.top = startPoint.y;
			textRect.right = startPoint.x + 500;
			textRect.bottom = startPoint.y + 30;

			DrawText(memoryDC,(LPCWSTR)str, -1, &textRect, DT_LEFT);
			DrawText(metafileDC,(LPCWSTR)str, -1, &textRect, DT_LEFT);
			HDC windowDC = GetDC(hWnd);
			BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
			ReleaseDC(hWnd, windowDC);
		}
		break;

	case WM_MOUSEWHEEL:
		if (GET_WHEEL_DELTA_WPARAM(wParam) < 0)
			wheelDelta = 10;
		else 
			wheelDelta = -10;
		keys = GET_KEYSTATE_WPARAM(wParam);
		windowDC = GetDC(hWnd);
			switch(keys)
			{
			case MK_CONTROL:
				zoom += wheelDelta;
				break;
			case MK_SHIFT:
				horizontal_shift += wheelDelta;
				break;
			default:
				vertical_shift += wheelDelta;
				break;
			}
			FillRect(windowDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
			StretchBlt(windowDC, horizontal_shift, vertical_shift,
				rect.right + zoom,
				rect.bottom + rect.bottom*zoom / rect.right ,
				memoryDC, 0, 0, rect.right, rect.bottom, SRCCOPY);
		ReleaseDC(hWnd, windowDC); 
		break;

	case WM_SIZE:
		RefreshWindow(hWnd);
		break;

	case WM_PAINT:
		windowDC = BeginPaint(hWnd, &ps);
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		DeleteDC(metafileDC);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId;
	PAINTSTRUCT ps;
	HDC windowDC;
	int keys;
	int wheelDelta;
	POINT tempPoint;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		switch (wmId)
		{
		case IDM_EXIT:
			CloseEnhMetaFile(metafileDC);
			DestroyWindow(hWnd);
			break;
		case IDM_SAVE:
			SaveMetafile(hWnd);
			break;
		case IDM_OPEN:
			OpenMetafile(hWnd); 
			break;
		case IDM_CANCEL:
			shape->CancelLastAction();
			break;
		case IDM_PEN:
			shape->~Shape();
			shape = new Pen(color, penWidth);
			break;
		case IDM_LINE:
			shape->~Shape();
			shape = new Line(color, penWidth);
			break;
		case IDM_POLYLINE:
			shape->~Shape();
			shape = new PolylineShape(color, penWidth);
			break;
		case IDM_ELLIPSE:
			shape->~Shape();
			shape = new EllipseShape(color, penWidth);
			break;
		case IDM_RECTANGLE:
			shape->~Shape();
			shape = new RectangleShape(color, penWidth);
			break;
		case IDM_COLOR:
			COLORREF chosenColor;
			if (chosenColor = GetColor(hWnd))
				color = chosenColor; 
			shape->SetPenColor(color);
			break;
		case IDM_PENWIDTH_1:
			penWidth = 1;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_2:
			penWidth = 2;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_4:
			penWidth = 4;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_8:
			penWidth = 8;
			shape->SetPenWidth(penWidth);
			break;
		case IDM_PENWIDTH_12:
			penWidth = 12;
			shape->SetPenWidth(penWidth);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;

	case WM_CREATE:
		DragAcceptFiles(hWnd, true);
		GetClientRect(hWnd, &rect);
		memoryDC = InitialiseMemoryDC(hWnd);
		metafileDC = InitialiseEnhMetafileDC(hWnd);
		break;

	case WM_DROPFILES:
		OpenDropedFile(hWnd, (HDROP) wParam);
		break;

	case WM_LBUTTONDOWN: 
		isDrawing = TRUE; 
		startPoint.x = LOWORD(lParam); 
		startPoint.y = HIWORD(lParam); 
		shape->SetStartPoint(startPoint);
		break; 

	case WM_LBUTTONUP: 
		if (isDrawing) 
		{ 
			windowDC = GetDC(hWnd);
			BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
			shape->Draw(memoryDC, startPoint, lParam);
			shape->Draw(metafileDC, startPoint, lParam);
			ReleaseDC(hWnd, windowDC); 
		} 
		if (shape->isFinished)
		{
			windowDC = GetDC(hWnd);
			BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
			isDrawing = FALSE; 
			ReleaseDC(hWnd, windowDC); 
		}
		UpdateWindow(hWnd);
		break; 
	
	case WM_RBUTTONDOWN:
	{
		shape->startPoint.x = -1;
		shape->isFinished = TRUE;
		windowDC = GetDC(hWnd);
		BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
		isDrawing = FALSE; 
		ReleaseDC(hWnd, windowDC); 
		break;
	}

	case WM_MOUSEMOVE: 
		if (isDrawing) 
		{ 
			windowDC = GetDC(hWnd);
			if (shape->isContinuous)
			{
				tempPoint = shape->GetStartPoint();
				BitBlt(windowDC, 0, 0, rect.right, rect.bottom, memoryDC, 0, 0, SRCCOPY);
				shape->Draw(memoryDC, startPoint, lParam);
				shape->SetStartPoint(tempPoint);
				shape->Draw(windowDC, startPoint, lParam);
			}
			ReleaseDC(hWnd, windowDC); 
			UpdateWindow(hWnd);
			PostMessage(hWnd, WM_PAINT, NULL, NULL);
		} 
		break; 

	case WM_MOUSEWHEEL:
		if (GET_WHEEL_DELTA_WPARAM(wParam) < 0)
			wheelDelta = 10;
		else 
			wheelDelta = -10;
		keys = GET_KEYSTATE_WPARAM(wParam);
		windowDC = GetDC(hWnd);
		if (keys == MK_CONTROL || keys == MK_SHIFT || keys == 0)
		{
			switch(keys)
			{
			case MK_CONTROL:
				zoom += wheelDelta;
				break;
			case MK_SHIFT:
				horizontal_shift += wheelDelta;
				// TODO: Shift Left-Right
				break;
			case 0:
				vertical_shift += wheelDelta;
				// TODO: Shift Up-Down
				break;
			}
			FillRect(windowDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
			StretchBlt(windowDC, horizontal_shift, vertical_shift,
				rect.right + zoom,
				rect.bottom + rect.bottom*zoom / rect.right ,
				memoryDC, 0, 0, rect.right, rect.bottom, SRCCOPY);
		}
		ReleaseDC(hWnd, windowDC); 
		break;

	case WM_SIZE:
		RefreshWindow(hWnd);
		break;

	case WM_PAINT:
		windowDC = BeginPaint(hWnd, &ps);
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		DeleteDC(metafileDC);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}