//	Altirra - Atari 800/800XL/5200 emulator
//	Copyright (C) 2008-2014 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"
#include "uinativewindow.h"

#ifndef TOUCH_HIT_TESTING_PROXIMITY_FARTHEST
	#define TOUCH_HIT_TESTING_PROXIMITY_FARTHEST 0xFFF

	#define TOUCH_HIT_TESTING_DEFAULT 0
	#define TOUCH_HIT_TESTING_CLIENT 1
	#define TOUCH_HIT_TESTING_NONE 2

	#ifndef WM_TOUCHHITTESTING
	#define WM_TOUCHHITTESTING 0x024D
	#endif

	typedef struct tagTOUCH_HIT_TESTING_PROXIMITY_EVALUATION {
		UINT16 score;
		POINT adjustedPoint;
	} TOUCH_HIT_TESTING_PROXIMITY_EVALUATION, *PTOUCH_HIT_TESTING_PROXIMITY_EVALUATION;

	typedef struct tagTOUCH_HIT_TESTING_INPUT {
		UINT32 pointerId;
		POINT point;
		RECT boundingBox;
		RECT nonOccludedBoundingBox;
		UINT32 orientation;
	} TOUCH_HIT_TESTING_INPUT, *PTOUCH_HIT_TESTING_INPUT;
#endif

LRESULT ATPackTouchHitTestingProximityEvaluationW32(const TOUCH_HIT_TESTING_INPUT *input, const TOUCH_HIT_TESTING_PROXIMITY_EVALUATION *eval) {
	typedef LRESULT (WINAPI *tpPackTouchHitTestingProximityEvaluation)(const TOUCH_HIT_TESTING_INPUT *input, const TOUCH_HIT_TESTING_PROXIMITY_EVALUATION *eval);
	static tpPackTouchHitTestingProximityEvaluation spfn = (tpPackTouchHitTestingProximityEvaluation)GetProcAddress(GetModuleHandle(_T("user32")), "PackTouchHitTestingProximityEvaluation");

	return spfn ? spfn(input, eval) : 0;
}

BOOL ATRegisterTouchHitTestingWindowW32(HWND hwnd, ULONG value) {
	typedef LRESULT (WINAPI *tpRegisterTouchHitTestingWindow)(HWND hwnd, ULONG value);
	static tpRegisterTouchHitTestingWindow spfn = (tpRegisterTouchHitTestingWindow)GetProcAddress(GetModuleHandle(_T("user32")), "RegisterTouchHitTestingWindow");

	return spfn && spfn(hwnd, value);
}

BOOL WINAPI ATEvaluateProximityToRectW32(const RECT *controlBoundingBox, const TOUCH_HIT_TESTING_INPUT *pHitTestingInput, TOUCH_HIT_TESTING_PROXIMITY_EVALUATION *pProximityEval) {
	typedef BOOL (WINAPI *tpEvaluateProximityToRect)(const RECT *controlBoundingBox, const TOUCH_HIT_TESTING_INPUT *pHitTestingInput, TOUCH_HIT_TESTING_PROXIMITY_EVALUATION *pProximityEval);
	static tpEvaluateProximityToRect spfn = (tpEvaluateProximityToRect)GetProcAddress(GetModuleHandle(_T("user32")), "EvaluateProximityToRect");

	return spfn && spfn(controlBoundingBox, pHitTestingInput, pProximityEval);
};

#ifndef TWF_WANTPALM
#define TWF_WANTPALM 0x00000002
#endif

BOOL WINAPI ATRegisterTouchWindowW32(HWND hwnd, ULONG flags) {
	static void *pfn = GetProcAddress(GetModuleHandle(_T("user32")), "RegisterTouchWindow");

	return pfn && ((BOOL (WINAPI *)(HWND, ULONG))pfn)(hwnd, flags);
}

BOOL WINAPI ATUnregisterTouchWindowW32(HWND hwnd) {
	static void *pfn = GetProcAddress(GetModuleHandle(_T("user32")), "UnregisterTouchWindow");

	return pfn && ((BOOL (WINAPI *)(HWND))pfn)(hwnd);
}

#ifndef GC_ALLGESTURES
	#define GC_ALLGESTURES	0x00000001

	#define GID_PAN				4
	#define GID_PRESSANDTAP		7

	#define GC_PAN		0x00000001
	#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY		0x00000002
	#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY		0x00000004
	#define GC_PAN_WITH_GUTTER	0x00000008

	#define	GC_PRESSANDTAP		0x00000001

	#define WM_GESTURENOTIFY	0x011A

	typedef struct _GESTURECONFIG {
		DWORD dwID;
		DWORD dwWant;
		DWORD dwBlock;
	} GESTURECONFIG, *PGESTURECONFIG;

	typedef struct tagGESTURENOTIFYSTRUCT {
		UINT cbSize;
		DWORD dwFlags;
		HWND hwndTarget;
		POINTS ptsLocation;
		DWORD dwInstanceID;
	} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT;
#endif

BOOL WINAPI ATSetGestureConfigW32(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize) {
	static void *pfn = GetProcAddress(GetModuleHandle(_T("user32")), "SetGestureConfig");

	return pfn && ((BOOL (WINAPI *)(HWND, DWORD, UINT, PGESTURECONFIG, UINT))pfn)(hwnd, dwReserved, cIDs, pGestureConfig, cbSize);
}

#ifndef WM_TABLET_DEFBASE
	#define WM_TABLET_DEFBASE						0x02C0
	#define WM_TABLET_QUERYSYSTEMGESTURESTATUS		(WM_TABLET_DEFBASE + 12)

	#define TABLET_DISABLE_PRESSANDHOLD			0x00000001
	#define TABLET_DISABLE_FLICKS				0x00010000
#endif

extern "C" IMAGE_DOS_HEADER __ImageBase;
ATOM ATUINativeWindow::sWndClass;
ATOM ATUINativeWindow::sWndClassMain;

ATUINativeWindow::ATUINativeWindow()
	: mRefCount(0)
	, mhwnd(NULL)
	, mTouchMode(kATUITouchMode_Default)
{
}

ATUINativeWindow::~ATUINativeWindow() {
}

ATOM ATUINativeWindow::Register() {
	if (sWndClass)
		return sWndClass;

	WNDCLASS wc = {};
	wc.lpszClassName	= _T("ATUINativeWindow");
	sWndClass = RegisterCustom(wc);
	return sWndClass;
}

ATOM ATUINativeWindow::RegisterCustom(const WNDCLASS& wc0) {
	WNDCLASS wc(wc0);

	wc.style			|= CS_DBLCLKS;
	wc.lpfnWndProc		= StaticWndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= sizeof(ATUINativeWindow *);
	wc.hInstance		= (HINSTANCE)&__ImageBase;

	if (!wc.hCursor)
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);

	if (!wc.hbrBackground)
		wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

	sWndClassMain = RegisterClass(&wc);
	return sWndClassMain;
}

void ATUINativeWindow::Unregister() {
	if (sWndClass) {
		UnregisterClass((LPCTSTR)sWndClass, (HINSTANCE)&__ImageBase);
		sWndClass = NULL;
	}
}

int ATUINativeWindow::AddRef() {
	return ++mRefCount;
}

int ATUINativeWindow::Release() {
	int rc = --mRefCount;

	if (!rc)
		delete this;

	return 0;
}

void *ATUINativeWindow::AsInterface(uint32 iid) {
	return NULL;
}

void ATUINativeWindow::SetTouchMode(ATUITouchMode touchMode) {
	if (mTouchMode != touchMode) {
		mTouchMode = touchMode;

		UpdateTouchMode(touchMode);
	}
}

LRESULT ATUINativeWindow::StaticWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	ATUINativeWindow *p;

	if (msg == WM_NCCREATE) {
		p = (ATUINativeWindow *)((LPCREATESTRUCT)lParam)->lpCreateParams;
		SetWindowLongPtr(hwnd, 0, (LONG_PTR)p);
		p->AddRef();
		p->mhwnd = hwnd;
	} else
		p = (ATUINativeWindow *)GetWindowLongPtr(hwnd, 0);

	if (!p)
		return DefWindowProc(hwnd, msg, wParam, lParam);

	p->AddRef();
	LRESULT result = p->WndProc(msg, wParam, lParam);

	if (msg == WM_NCDESTROY && p) {
		p->mhwnd = NULL;
		p->Release();
		SetWindowLongPtr(hwnd, 0, NULL);
	}
	p->Release();

	return result;
}

LRESULT ATUINativeWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
	switch(msg) {
		case WM_CREATE:
			UpdateTouchMode(mTouchMode);
			break;

		case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
			{
				ATUITouchMode mode = mTouchMode;

				if (mode == kATUITouchMode_Dynamic) {
					POINT pt = { (short)LOWORD(lParam), (short)HIWORD(lParam) };

					ScreenToClient(mhwnd, &pt);

					mode = GetTouchModeAtPoint(vdpoint32(pt.x, pt.y));
				}

				switch(mode) {
					case kATUITouchMode_Default:
					case kATUITouchMode_Direct:
					case kATUITouchMode_VerticalPan:
					case kATUITouchMode_2DPan:
					case kATUITouchMode_2DPanSmooth:
						return TABLET_DISABLE_FLICKS;

					case kATUITouchMode_Immediate:
						return TABLET_DISABLE_PRESSANDHOLD | TABLET_DISABLE_FLICKS;
				}
			}

			break;

		case WM_GESTURENOTIFY:
			if (mTouchMode == kATUITouchMode_Dynamic) {
				const GESTURENOTIFYSTRUCT& gns = *(const GESTURENOTIFYSTRUCT *)lParam;
				POINT pt = { gns.ptsLocation.x, gns.ptsLocation.y };

				ScreenToClient(mhwnd, &pt);

				ATUITouchMode mode = GetTouchModeAtPoint(vdpoint32(pt.x, pt.y));

				UpdateTouchMode(mode);
			} else
				UpdateTouchMode(mTouchMode);
			break;
	}

	return DefWindowProc(mhwnd, msg, wParam, lParam);
}

ATUITouchMode ATUINativeWindow::GetTouchModeAtPoint(const vdpoint32& pt) const {
	return kATUITouchMode_Default;
}

void ATUINativeWindow::UpdateTouchMode(ATUITouchMode touchMode) {
	if (!mhwnd)
		return;

	switch(touchMode) {
		case kATUITouchMode_Default:
			{
				GESTURECONFIG gc[] = {
					{ 0, GC_ALLGESTURES, 0 },
				};

				ATSetGestureConfigW32(mhwnd, 0, 1, &gc[0], sizeof(GESTURECONFIG));
			}
			break;
		case kATUITouchMode_Immediate:
		case kATUITouchMode_Direct:
			{
				GESTURECONFIG gc = {
					0,
					0,
					GC_ALLGESTURES,
				};

				ATSetGestureConfigW32(mhwnd, 0, 1, &gc, sizeof(GESTURECONFIG));
			}
			break;
		case kATUITouchMode_VerticalPan:
			{
				GESTURECONFIG gc[] = {
					{ 0, GC_ALLGESTURES, 0 },
					{ GID_PAN, GC_PAN | GC_PAN_WITH_GUTTER | GC_PAN_WITH_SINGLE_FINGER_VERTICALLY, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY }
				};

				ATSetGestureConfigW32(mhwnd, 0, 1, &gc[0], sizeof(GESTURECONFIG));
				ATSetGestureConfigW32(mhwnd, 0, 1, &gc[1], sizeof(GESTURECONFIG));
			}
			break;
		case kATUITouchMode_2DPan:
			{
				GESTURECONFIG gc[] = {
					{ 0, GC_ALLGESTURES, 0},
				};

				ATSetGestureConfigW32(mhwnd, 0, 1, gc, sizeof(GESTURECONFIG));
			}
			break;
		case kATUITouchMode_2DPanSmooth:
			{
				GESTURECONFIG gc[] = {
					{ 0, GC_ALLGESTURES, 0},
					{ GID_PAN, 0, GC_PAN_WITH_GUTTER},
				};

				ATSetGestureConfigW32(mhwnd, 0, 1, &gc[0], sizeof(GESTURECONFIG));
				ATSetGestureConfigW32(mhwnd, 0, 1, &gc[1], sizeof(GESTURECONFIG));
			}
			break;
		case kATUITouchMode_Dynamic:
			break;
	}
}
