#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

#include <d2d1.h>
#include <wincodec.h>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "WindowsCodecs.lib")
#pragma comment(lib, "ole32.lib")

#include <vector>
#include <cmath>
#include <algorithm>
#include <random>
#include <tuple>

using namespace std;

#include "resource.h"
#include "card.h"
#include "spider.h"

// Direct2D / WIC resources
ID2D1Factory* pD2DFactory = NULL;
ID2D1HwndRenderTarget* pRenderTarget = NULL;
IWICImagingFactory* pWICFactory = NULL;
ID2D1Bitmap* cardsSheet = NULL;
bool running = true;

Spider* game = NULL;
DWORD lastTime = 0;
bool mouseDown = false;

int desiredWidth, desiredHeight;
float zoom = 1.0f;

// Helper to safely release COM objects
template<typename T>
inline void SafeRelease(T** ppT)
{
	if (ppT && *ppT)
	{
		(*ppT)->Release();
		*ppT = NULL;
	}
}

// Load PNG via WIC and create ID2D1Bitmap
HRESULT LoadBitmapFromFile(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pWICFactory, PCWSTR uri, ID2D1Bitmap** ppBitmap)
{
	if (!pRenderTarget || !pWICFactory || !ppBitmap) return E_INVALIDARG;

	IWICBitmapDecoder* pDecoder = NULL;
	IWICBitmapFrameDecode* pFrame = NULL;
	IWICFormatConverter* pConverter = NULL;
	HRESULT hr = pWICFactory->CreateDecoderFromFilename(uri, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
	if (SUCCEEDED(hr)) {
		hr = pDecoder->GetFrame(0, &pFrame);
	}
	if (SUCCEEDED(hr)) {
		hr = pWICFactory->CreateFormatConverter(&pConverter);
	}
	if (SUCCEEDED(hr)) {
		hr = pConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom);
	}
	if (SUCCEEDED(hr)) {
		hr = pRenderTarget->CreateBitmapFromWicBitmap(pConverter, NULL, ppBitmap);
	}

	SafeRelease(&pDecoder);
	SafeRelease(&pFrame);
	SafeRelease(&pConverter);
	return hr;
}

// Load PNG via WIC and create ID2D1Bitmap
HRESULT LoadBitmapFromMemory(ID2D1RenderTarget* pRenderTarget, IWICImagingFactory* pWICFactory, BYTE *pData, DWORD size, ID2D1Bitmap** ppBitmap)
{
	if (!pRenderTarget || !pWICFactory || !ppBitmap) return E_INVALIDARG;

	IWICBitmapDecoder* pDecoder = NULL;
	IWICBitmapFrameDecode* pFrame = NULL;
	IWICFormatConverter* pConverter = NULL;
	IWICStream* pStream = NULL;

	HRESULT hr = pWICFactory->CreateStream(&pStream);
	if (SUCCEEDED(hr)) {
		hr = pStream->InitializeFromMemory((BYTE*)pData, size);
	}
	if (SUCCEEDED(hr)) {
		hr = pWICFactory->CreateDecoderFromStream(
			pStream, nullptr, WICDecodeMetadataCacheOnLoad, &pDecoder
		);
	}
	if (SUCCEEDED(hr)) {
		hr = pDecoder->GetFrame(0, &pFrame);
	}
	if (SUCCEEDED(hr)) {
		hr = pWICFactory->CreateFormatConverter(&pConverter);
	}
	if (SUCCEEDED(hr)) {
		hr = pConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom);
	}
	if (SUCCEEDED(hr)) {
		hr = pRenderTarget->CreateBitmapFromWicBitmap(pConverter, NULL, ppBitmap);
	}

	SafeRelease(&pDecoder);
	SafeRelease(&pFrame);
	SafeRelease(&pConverter);
	return hr;
}

void InitializeResources(HWND hWnd) {
	// Initialize COM for WIC
	CoInitialize(NULL);

	// Create D2D factory
	D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);

	// Create WIC factory
	CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pWICFactory));

	// Create render target
	RECT rect;
	GetClientRect(hWnd, &rect);
	int width = rect.right - rect.left;
	int height = rect.bottom - rect.top;

	D2D1_SIZE_U size = D2D1::SizeU(width, height);
	D2D1_HWND_RENDER_TARGET_PROPERTIES rtProps = D2D1::HwndRenderTargetProperties(hWnd, size);
	pD2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), rtProps, &pRenderTarget);

	HRSRC hrsrc = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_CARDS_PNG), RT_RCDATA);
	if (!hrsrc) {
		return;
	}
	HGLOBAL hRes = LoadResource(NULL, hrsrc);
	if (!hRes) {
		return;
	}
	BYTE *cardsData = (BYTE *)LockResource(hRes);
	DWORD cardsDataSize = SizeofResource(NULL, hrsrc);

	// Load bitmap
	LoadBitmapFromMemory(pRenderTarget, pWICFactory, cardsData, cardsDataSize, &cardsSheet);
}

void ReleaseResources() {
	if (game) {
		delete game;
		game = NULL;
	}

	SafeRelease(&cardsSheet);
	SafeRelease(&pRenderTarget);
	SafeRelease(&pWICFactory);
	SafeRelease(&pD2DFactory);
	CoUninitialize();
}

void NewGame(int maxColors) {
	if (game) {
		delete game;
		game = NULL;
	}

	game = new Spider(maxColors);
}

void Draw(HWND hWnd) {
	if (!pRenderTarget) return;

	RECT rect;
	GetClientRect(hWnd, &rect);
	UINT width = rect.right - rect.left;
	UINT height = rect.bottom - rect.top;

	// Resize if needed
	D2D1_SIZE_U currentSize = pRenderTarget->GetPixelSize();
	if (currentSize.width != width || currentSize.height != height) {
		pRenderTarget->Resize(D2D1::SizeU(width, height));
	}

	zoom = min((float)width / (float)desiredWidth, (float)height / (float)desiredHeight);
	pRenderTarget->SetTransform(
		D2D1::Matrix3x2F::Scale(
			D2D1::Size(zoom, zoom),
			D2D1::Point2F(0.0f, 0.0f)
		)
	);
	pRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);

	// Clear background
	pRenderTarget->BeginDraw();
	pRenderTarget->Clear(D2D1::ColorF(0.0745f, 0.378f, 0.145f));

	if (game) {
		game->draw(pRenderTarget, cardsSheet);
	}

	pRenderTarget->EndDraw();
}

void Update(HWND hWnd) {
	DWORD dwTime = GetTickCount();

	if (lastTime == 0) {
		lastTime = dwTime;
		return;
	}

	float deltaTime = (dwTime - lastTime) / 1000.0f;
	lastTime = dwTime;

	if (game) {
		game->update(deltaTime);
	}

	Draw(hWnd);
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	int x = LOWORD(lParam) / zoom;
	int y = HIWORD(lParam) / zoom;

	switch (uMsg) {
	case WM_CREATE:
        InitializeResources(hWnd);
		NewGame(4);

		//SetTimer(hWnd, 0, 10, (TIMERPROC)Update);
		return 0;

	case WM_DESTROY:
		ReleaseResources();

		PostQuitMessage(0);
		running = false;
		return 0;

	case WM_MOUSEMOVE:
		if (game) {
			game->onMouseMove(x, y);
		}

		// обработка нажатий сделана еще и так, чтобы не было проблем с перетаскиванием за пределы окна и отпусканием кнопки мыши вне окна
		if (!mouseDown && (wParam & MK_LBUTTON) == MK_LBUTTON) {
			if (game) {
				game->onMouseDown(x, y);
			}
			mouseDown = true;
		}
		else if (mouseDown && (wParam & MK_LBUTTON) == 0) {
			if (game) {
				game->onMouseUp(x, y);
			}
			mouseDown = false;
		}

		return 0;

	case WM_LBUTTONDOWN:
		if (!mouseDown) {
			if (game) {
				game->onMouseDown(x, y);
			}
			mouseDown = true;
		}
		return 0;

	case WM_LBUTTONUP:
		if (mouseDown) {
			if (game) {
				game->onMouseUp(x, y);
			}
			mouseDown = false;
		}
		return 0;

	case WM_LBUTTONDBLCLK:
		if (game) {
			game->onMouseDoubleClick(x, y);
		}
		return 0;

	case WM_XBUTTONDOWN:
		if (LOWORD(wParam) == MK_XBUTTON1) { // кнопка назад на мышке
			if (game) {
				game->popHistory();
			}
		}
		return 0;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ID_NEWGAME_1:
			NewGame(1);
			return 0;
		case ID_NEWGAME_2:
			NewGame(2);
			return 0;
		case ID_NEWGAME_4:
			NewGame(4);
			return 0;
		case ID_UNDO:
			if (game) {
				game->popHistory();
			}

			return 0;
		}
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow) {
	// Register the window class.
	const wchar_t CLASS_NAME[] = L"Spider Solitaire";

	WNDCLASS wc = {};
	wc.lpfnWndProc = WindowProc;
	wc.hInstance = hInstance;
	wc.lpszClassName = CLASS_NAME;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
	wc.style = CS_DBLCLKS;

	RegisterClass(&wc);

	desiredWidth = Spider::MARGIN_X * 2 + Spider::STACKS_COUNT * (CARD_WIDTH + Spider::MARGIN_X);
	desiredHeight = Spider::MARGIN_Y * 3 + CARD_HEIGHT * 6;

	// Create the window.
	HWND hWnd = CreateWindowEx(
		0,                      // Optional window styles.
		CLASS_NAME,             // Window class
		L"Spider Solitaire",    // Window text
		WS_OVERLAPPEDWINDOW,    // Window style

		// Size and position
		CW_USEDEFAULT, CW_USEDEFAULT, desiredWidth, desiredHeight,

		NULL,       // Parent window    
		NULL,       // Menu
		hInstance,  // Instance handle
		NULL        // Additional application data
	);
	if (hWnd == NULL) {
		return 0;
	}

	ShowWindow(hWnd, nCmdShow);

	RECT rect;
	GetClientRect(hWnd, &rect);
	desiredWidth = rect.right - rect.left;
	desiredHeight = rect.bottom - rect.top;

	HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));

	// Run the message loop.
	MSG msg = {};
	while (running) {
		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			if (!TranslateAccelerator(hWnd, hAccel, &msg)) {
				if (msg.message == WM_QUIT) {
					running = false;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

        Update(hWnd);
	}

	return 0;
}
