#include "CtrlCore.h"

#ifdef GUI_WIN

#include <winnls.h>

//#include "imm.h"

namespace Upp {

#define LLOG(x)  // DLOG(x)

dword Ctrl::KEYtoK(dword chr) {
	if(chr == VK_TAB)
		chr = K_TAB;
	else
	if(chr == VK_SPACE)
		chr = K_SPACE;
	else
	if(chr == VK_RETURN)
		chr = K_RETURN;
	else
		chr = chr + K_DELTA;
	if(chr == K_ALT_KEY || chr == K_CTRL_KEY || chr == K_SHIFT_KEY)
		return chr;
	if(GetCtrl()) chr |= K_CTRL;
	if(GetAlt()) chr |= K_ALT;
	if(GetShift()) chr |= K_SHIFT;
	return chr;
}


class NilDrawFull : public NilDraw {
	virtual bool IsPaintingOp(const Rect& r) const { return true; }
};

void AvoidPaintingCheck__()
{
	Ctrl::painting = false;
}

dword GetKeyStateSafe(dword what) {
	bool h = Ctrl::painting;
	Ctrl::painting = false;
	dword r = GetKeyState(what);
	Ctrl::painting = h;
	return r;
}

extern void SetMousePos(const Point &p);
static bool pendown=false;
static bool barreldown=false;

bool GetShift()       { return !!(GetKeyStateSafe(VK_SHIFT) & 0x8000); }
bool GetCtrl()        { return !!(GetKeyStateSafe(VK_CONTROL) & 0x8000); }
bool GetAlt()         { return !!(GetKeyStateSafe(VK_MENU) & 0x8000); }
bool GetCapsLock()    { return !!(GetKeyStateSafe(VK_CAPITAL) & 1); }
bool GetMouseLeft()   { return pendown || !!(GetKeyStateSafe(VK_LBUTTON) & 0x8000); }
bool GetMouseRight()  { return barreldown || !!(GetKeyStateSafe(VK_RBUTTON) & 0x8000); }
//bool GetMouseRight()  { return (pendown && barreldown) || !!(GetKeyStateSafe(VK_RBUTTON) & 0x8000); }
bool GetMouseMiddle() { return !!(GetKeyStateSafe(VK_MBUTTON) & 0x8000); }

bool PassWindowsKey(int wParam);

LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
	GuiLock __;
	eventid++;
//	LLOG("Ctrl::WindowProc(" << message << ") in " << ::Name(this) << ", focus " << (void *)::GetFocus());
	Ptr<Ctrl> _this = this;
	HWND hwnd = GetHWND();

	auto DoMouseMove = [&](POINT p) {
		if(ignoreclick)
			EndIgnore();
		else {
			if(_this) DoMouse(MOUSEMOVE, Point(p));
			DoCursorShape();
		}
	};

	static Point pendownpos=Null;
	
	static bool down=false;
	
	switch(message) {
	case WM_POINTERDOWN:
	case WM_POINTERUP:
	case WM_POINTERUPDATE:{
			SetMousePos(Point((dword)lParam));

			if(!pen_supported) break; // Go with mouse emulation (default processing) instead

			POINT p = Point((LONG)lParam);
			ScreenToClient(hwnd, &p);
			
			pen = false;
			pen_pressure = pen_rotation = Null;
			pen_tilt = Null;
			pen_eraser = false;
			pen_barrel = false;
			pen_inverted = false;
			pen_history = false;

			static BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType);
			static BOOL (WINAPI *GetPointerInfo)(UINT32 pointerId, POINTER_INFO *pointerInfo);
			static BOOL (WINAPI *GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
			static BOOL (WINAPI *GetPointerPenInfoHistory)(UINT32 pointerId, UINT32 *entriesCount, POINTER_PEN_INFO *penInfo);

			ONCELOCK {
				DllFn(GetPointerType, "User32.dll", "GetPointerType");
				DllFn(GetPointerInfo, "User32.dll", "GetPointerInfo");
				DllFn(GetPointerPenInfo, "User32.dll", "GetPointerPenInfo");
				DllFn(GetPointerPenInfoHistory, "User32.dll", "GetPointerPenInfoHistory");
			};

			if(!(GetPointerType && GetPointerInfo && GetPointerPenInfo && GetPointerPenInfoHistory))
				break;

			POINTER_INPUT_TYPE pointerType;

			auto ProcessPenInfo = [&] (POINTER_PEN_INFO& ppi) {
				pen = true;
				if(ppi.penFlags & PEN_FLAG_BARREL)
					pen_barrel = true;
				if(ppi.penFlags & PEN_FLAG_INVERTED)
					pen_inverted = true;
				if(ppi.penFlags & PEN_FLAG_ERASER)
					pen_eraser = true;
				if(ppi.penMask & PEN_MASK_PRESSURE)
					pen_pressure = ppi.pressure / 1024.0;
				if(ppi.penMask & PEN_MASK_ROTATION)
					pen_rotation = ppi.rotation * M_2PI / 360;
				if(ppi.penMask & PEN_MASK_TILT_X)
					pen_tilt.x = ppi.tiltX * M_2PI / 360;
				if(ppi.penMask & PEN_MASK_TILT_Y)
					pen_tilt.y = ppi.tiltY * M_2PI / 360;
			};

			UINT32 pointerId = GET_POINTERID_WPARAM(wParam);
			if(GetPointerType(pointerId, &pointerType) && pointerType == PT_PEN) {
				UINT32 hc = 256;
				Buffer<POINTER_PEN_INFO> ppit(hc);
				if(message == WM_POINTERUPDATE && pendown && GetPointerPenInfoHistory(pointerId, &hc, ppit)) {
					for(int i = hc - 1; i >= 0; i--) {
						ProcessPenInfo(ppit[i]);
						POINT hp = ppit[i].pointerInfo.ptPixelLocation;
						SetMousePos(hp);
						ScreenToClient(hwnd, &hp);
						pen_history = (bool)i;
						DoMouseMove(hp);
					}
					pen_history = false;
				}
				else{
					POINTER_PEN_INFO ppi;
					if(GetPointerPenInfo(pointerId, &ppi)){
						ProcessPenInfo(ppi);
						switch(message) {
							case WM_POINTERUP:
								pendown=false;
								break;
							case WM_POINTERDOWN:
								ClickActivateWnd();
								if(ignoreclick) return 0L;
								pendown=true;
								if(pendownpos!=Point(p)){
									down=true;
									barreldown=pen_barrel;
									if(barreldown) DoMouse(RIGHTDOWN, pendownpos=Point(p));
									else DoMouse(LEFTDOWN, pendownpos=Point(p), 0);
									if(_this) PostInput();
								}
								break;
						}
					}
				}
			}
		}
		break;
	case WM_POINTERLEAVE:
		pen = false;
		down = false;
		barreldown=false;
		pendown=false;
		pendownpos=Null;
		break;
	case WM_PALETTECHANGED:
		if((HWND)wParam == hwnd)
			break;
	case WM_QUERYNEWPALETTE:
		if(!SystemDraw::AutoPalette()) break;
		{
			HDC hDC = GetDC(hwnd);
			HPALETTE hOldPal = SelectPalette(hDC, GetQlibPalette(), FALSE);
			int i = RealizePalette(hDC);
			SelectPalette(hDC, hOldPal, TRUE);
			RealizePalette(hDC);
			ReleaseDC(hwnd, hDC);
			LLOG("Realized " << i << " colors");
			if(i) InvalidateRect(hwnd, NULL, TRUE);
			return i;
		}
	case WM_PAINT:
		ASSERT_(!painting || IsPanicMode(), "WM_PAINT invoked for " + Name() + " while in Paint routine");
		ASSERT(hwnd);
		if(hwnd) {
			PAINTSTRUCT ps;
			if(IsVisible())
				SyncScroll();
			HDC dc = BeginPaint(hwnd, &ps);
			fullrefresh = false;
			if(IsVisible()) {
				SystemDraw draw(dc);
				HPALETTE hOldPal;
				if(draw.PaletteMode() && SystemDraw::AutoPalette()) {
					hOldPal = SelectPalette(dc, GetQlibPalette(), TRUE);
					int n = RealizePalette(dc);
					LLOG("In paint realized " << n << " colors");
				}
				painting = true;
				UpdateArea(draw, Rect(ps.rcPaint));
				painting = false;
				if(draw.PaletteMode() && SystemDraw::AutoPalette())
					SelectPalette(dc, hOldPal, TRUE);
			}
			EndPaint(hwnd, &ps);

			UpdateDHCtrls(); // so that they are displayed withing the same WM_PAINT - looks better
		}
		return 0L;
	case WM_NCHITTEST:
		CheckMouseCtrl();
		if(ignoremouse) return HTTRANSPARENT;
		break;
	case WM_LBUTTONDOWN:
		ClickActivateWnd();
		if(ignoreclick) return 0L;
		if(down) return 0L;
		DoMouse(LEFTDOWN, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_LBUTTONUP:
		if(ignoreclick)
			EndIgnore();
		else{
			POINT p = GetMousePos();
			ScreenToClient(hwnd, &p);
			if(barreldown) DoMouse(RIGHTUP, p);
			else DoMouse(LEFTUP, p);
			if(_this) PostInput();
		}
		down=false;
		barreldown=false;
		return 0L;
	case WM_LBUTTONDBLCLK:
		ClickActivateWnd();
		pendownpos=Null;
		down=true;
		if(ignoreclick) return 0L;
		DoMouse(LEFTDOUBLE, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_RBUTTONDOWN:
		ClickActivateWnd();
		if(ignoreclick) return 0L;
		if(down) return 0L;
		DoMouse(RIGHTDOWN, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_RBUTTONUP:
		if(ignoreclick)
			EndIgnore();
		else{
			POINT p = GetMousePos();
			ScreenToClient(hwnd, &p);
			DoMouse(RIGHTUP, p);
			if(_this) PostInput();
		}
		down=false;
		barreldown=false;
		return 0L;
	case WM_RBUTTONDBLCLK:
		ClickActivateWnd();
		pendownpos=Null;
		down=true;
		if(ignoreclick) return 0L;
		DoMouse(RIGHTDOUBLE, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_MBUTTONDOWN:
		ClickActivateWnd();
		if(ignoreclick) return 0L;
		DoMouse(MIDDLEDOWN, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_MBUTTONUP:
		if(ignoreclick)
			EndIgnore();
		else
			DoMouse(MIDDLEUP, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_MBUTTONDBLCLK:
		ClickActivateWnd();
		if(ignoreclick) return 0L;
		DoMouse(MIDDLEDOUBLE, Point((dword)lParam));
		if(_this) PostInput();
		return 0L;
	case WM_NCLBUTTONDOWN:
	case WM_NCRBUTTONDOWN:
	case WM_NCMBUTTONDOWN:
		ClickActivateWnd();
		IgnoreMouseUp();
		break;
	case WM_MOUSEMOVE:
		{
			Point p((dword)lParam);
			::ClientToScreen(hwnd,p);
			SetMousePos(p);
			//DLOG("WM_MOUSEMOVE - GetMousePos():" << GetMousePos() << " lParam: " << p);
		}
		if(down) return 0L;
		LLOG("WM_MOUSEMOVE: ignoreclick = " << ignoreclick);
		DoMouseMove(Point((dword)lParam));
		return 0L;
	case 0x20a: // WM_MOUSEWHEEL:
		if(ignoreclick) {
			EndIgnore();
			return 0L;
		}
		if(_this) {
			Point p(0, 0);
			::ClientToScreen(hwnd, p);
			DoMouse(MOUSEWHEEL, Point((dword)lParam) - p, (short)HIWORD(wParam));
		}
		if(_this) PostInput();
		return 0L;
	case WM_SETCURSOR:
		if((HWND)wParam == hwnd && LOWORD((dword)lParam) == HTCLIENT) {
			if(hCursor) SetCursor(hCursor);
			return TRUE;
		}
		break;
//	case WM_MENUCHAR:
//		return MAKELONG(0, MNC_SELECT);
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
	case WM_CHAR:
		ignorekeyup = false;
	case WM_KEYUP:
	case WM_SYSKEYUP:
		{
#if 0
			String msgdump;
			switch(message)
			{
			case WM_KEYDOWN:    msgdump << "WM_KEYDOWN"; break;
			case WM_KEYUP:      msgdump << "WM_KEYUP"; break;
			case WM_SYSKEYDOWN: msgdump << "WM_SYSKEYDOWN"; break;
			case WM_SYSKEYUP:   msgdump << "WM_SYSKEYUP"; break;
			case WM_CHAR:       msgdump << "WM_CHAR"; break;
			}
			msgdump << " wParam = 0x" << FormatIntHex(wParam, 8)
				<< ", lParam = 0x" << FormatIntHex(lParam, 8)
				<< ", ignorekeyup = " << (ignorekeyup ? "true" : "false");
			LLOG(msgdump);
#endif
			dword keycode = 0;
			if(message == WM_KEYDOWN) {
				keycode = KEYtoK((dword)wParam);
				if(keycode == K_SPACE)
					keycode = 0;
			}
			else
			if(message == WM_KEYUP)
				keycode = KEYtoK((dword)wParam) | K_KEYUP;
			else
			if(message == WM_SYSKEYDOWN /*&& ((lParam & 0x20000000) || wParam == VK_F10)*/)
				keycode = KEYtoK((dword)wParam);
			else
			if(message == WM_SYSKEYUP /*&& ((lParam & 0x20000000) || wParam == VK_F10)*/)
				keycode = KEYtoK((dword)wParam) | K_KEYUP;
			else
			if(message == WM_CHAR && wParam != 127 && wParam > 32 || wParam == 32 && KEYtoK(VK_SPACE) == K_SPACE) {
				if(IsWindowUnicode(hwnd)) // TRC 04/10/17: ActiveX Unicode patch
					keycode = (dword)wParam;
				else {
					char b[20];
					::GetLocaleInfo(MAKELCID(LOWORD(GetKeyboardLayout(0)), SORT_DEFAULT),
					                LOCALE_IDEFAULTANSICODEPAGE, b, 20);
					int codepage = atoi(b);
					if(codepage >= 1250 && codepage <= 1258)
						keycode = ToUnicode((dword)wParam, codepage - 1250 + CHARSET_WIN1250);
					else
						keycode = (dword)wParam;
				}
			}
			bool b = false;
			if(keycode) {
				b = DispatchKey(keycode, LOWORD(lParam));
				SyncCaret();
				if(_this) PostInput();
			}
//			LOG("key processed = " << b);
			if(b || (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
			&& wParam != VK_F4 && !PassWindowsKey((dword)wParam)) // 17.11.2003 Mirek -> invoke system menu
				return 0L;
			break;
		}
		break;
//	case WM_GETDLGCODE:
//		return wantfocus ? 0 : DLGC_STATIC;
	case WM_XBUTTONDOWN: {
		UINT button = GET_XBUTTON_WPARAM(wParam);
		if(button == XBUTTON2)
			DispatchKey(K_MOUSE_FORWARD, 1);
		if(button == XBUTTON1)
			DispatchKey(K_MOUSE_BACKWARD, 1);
		return 0L;
	}
	case WM_XBUTTONUP: {
		UINT button = GET_XBUTTON_WPARAM(wParam);
		if(button == XBUTTON2)
			DispatchKey(K_MOUSE_FORWARD|K_KEYUP, 1);
		if(button == XBUTTON1)
			DispatchKey(K_MOUSE_BACKWARD|K_KEYUP, 1);
		return 0L;
	}
	case WM_ERASEBKGND:
		return 1L;
	case WM_DESTROY:
		PreDestroy();
		break;
	case WM_NCDESTROY:
		if(!hwnd) break;
		if(HasChildDeep(mouseCtrl) || this == ~mouseCtrl) mouseCtrl = NULL;
		if(HasChildDeep(focusCtrl) || this == ~focusCtrl) focusCtrl = NULL;
		if(HasChildDeep(focusCtrlWnd) || this == ~focusCtrlWnd) {
			LLOG("WM_NCDESTROY: clearing focusCtrlWnd = " << UPP::Name(focusCtrlWnd));
			focusCtrlWnd = NULL;
			focusCtrl = NULL;
		}
		if(::GetFocus() == NULL) {
			Ctrl *owner = GetOwner();
			if(owner && (owner->IsForeground() || IsForeground()) && !owner->SetWantFocus())
				IterateFocusForward(owner, owner);
		}
		if(IsWindowUnicode(hwnd)) // TRC 04/10/17: ActiveX unicode patch
			DefWindowProcW(hwnd, message, wParam, lParam);
		else
			DefWindowProc(hwnd, message, wParam, lParam);
		hwnd = NULL;
		return 0L;
	case WM_CANCELMODE:
		if(this == ~captureCtrl || HasChildDeep(captureCtrl))
			ReleaseCtrlCapture();
		break;
	case WM_SHOWWINDOW:
		visible = (BOOL) wParam;
		StateH(SHOW);
		break;
	case WM_MOUSEACTIVATE:
		LLOG("WM_MOUSEACTIVATE " << Name() << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		if(!IsEnabled()) {
			if(lastActiveWnd && lastActiveWnd->IsEnabled()) {
				if(focusCtrl) { // this closes popup
					LLOG("WM_MOUSEACTIVATE -> ClickActivateWnd for " << UPP::Name(lastActiveWnd));
					lastActiveWnd->ClickActivateWnd();
				}
				else { // this makes child dialog active when clicked on disabled parent
					LLOG("WM_MOUSEACTIVATE -> ::SetFocus for " << UPP::Name(lastActiveWnd));
					::SetFocus(lastActiveWnd->GetHWND());
				}
			}
			else
				MessageBeep(MB_OK);
			return MA_NOACTIVATEANDEAT;
		}
		if(IsPopUp()) return MA_NOACTIVATE;
		break;
	case WM_SIZE:
	case WM_MOVE:
		if(hwnd) {
			Rect rect;
			if(activex) {
				WINDOWPLACEMENT wp;
				wp.length = sizeof(WINDOWINFO);
				::GetWindowPlacement(hwnd, &wp);
				rect = wp.rcNormalPosition;
			}
			else
				rect = GetScreenClient(hwnd);
			LLOG("WM_MOVE / WM_SIZE: screen client = " << rect);
			if(GetRect() != rect)
				SetWndRect(rect);
		#if WINCARET
			WndDestroyCaret();
			caretCtrl = NULL;
			SyncCaret();
		#endif
		}
		return 0L;
	case WM_HELP:
		return TRUE;
	case WM_ACTIVATE:
		LLOG("WM_ACTIVATE " << Name() << ", wParam = " << (int)wParam << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		ignorekeyup = true;
		break;
	case WM_SETFOCUS:
		LLOG("WM_SETFOCUS " << Name() << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		if(this != focusCtrlWnd || focusCtrl && focusCtrlWnd != focusCtrl->GetTopCtrl()) { // second condition fixes popup issue when clicking dialog parent
			if(IsEnabled()) {
				LLOG("WM_SETFOCUS -> ActivateWnd: this != focusCtrlWnd, this = "
					<< Name() << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd));
				ActivateWnd();
			}
			else {
				if(focusCtrlWnd && focusCtrlWnd->IsEnabled()) {
					if(!IsEnabled())
						MessageBeep(MB_OK);
					LLOG("WM_SETFOCUS -> ::SetFocus for " << UPP::Name(focusCtrlWnd) << ", this: " << UPP::Name(this));
					::SetFocus(focusCtrlWnd->GetHWND());
				}
				else
				if(lastActiveWnd && lastActiveWnd->IsEnabled()) {
					LLOG("WM_SETFOCUS -> ::SetFocus for " << UPP::Name(lastActiveWnd));
					::SetFocus(lastActiveWnd->GetHWND());
				}
				else {
					LLOG("WM_SETFOCUS - ::SetFocus(NULL)");
					::SetFocus(NULL);
				}
			}
		}
		LLOG("//WM_SETFOCUS " << (void *)hwnd << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		return 0L;
	case WM_KILLFOCUS:
		LLOG("WM_KILLFOCUS " << (void *)(HWND)wParam << ", this = " << UPP::Name(this) << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		LLOG("Kill " << UPP::Name(CtrlFromHWND((HWND)wParam)));
		if(!CtrlFromHWND((HWND)wParam)) {
			LLOG("WM_KILLFOCUS -> KillFocusWnd: " << UPP::Name(this));
			KillFocusWnd();
		}
		LLOG("//WM_KILLFOCUS " << (void *)(HWND)wParam << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
		return 0L;
	case WM_ENABLE:
		if(!!wParam != enabled) {
			enabled = !!wParam;
			RefreshFrame();
			StateH(ENABLE);
		}
		return 0L;
#ifndef PLATFORM_WINCE
	case WM_GETMINMAXINFO:
		{
			MINMAXINFO *mmi = (MINMAXINFO *)lParam;
			Rect frmrc = Size(200, 200);
			::AdjustWindowRect(frmrc, WS_OVERLAPPEDWINDOW, FALSE);
//			Size msz = Ctrl::GetWorkArea().Deflated(-frmrc.left, -frmrc.top,
//				           frmrc.right - 200, frmrc.bottom - 200).GetSize();
//			Rect minr(Point(50, 50), min(msz, GetMinSize()));
//			Rect maxr(Point(50, 50), min(msz, GetMaxSize())); // Removed cxl&nixnixnix 2012-6-12
			Rect minr(Point(50, 50), GetMinSize());
			Rect maxr(Point(50, 50), GetMaxSize());
			dword style = ::GetWindowLong(hwnd, GWL_STYLE);
			dword exstyle = ::GetWindowLong(hwnd, GWL_EXSTYLE);
			AdjustWindowRectEx(minr, style, FALSE, exstyle);
			AdjustWindowRectEx(maxr, style, FALSE, exstyle);
			mmi->ptMinTrackSize = Point(minr.Size());
			mmi->ptMaxTrackSize = Point(maxr.Size());
			LLOG("WM_GETMINMAXINFO: MinTrackSize = " << Point(mmi->ptMinTrackSize) << ", MaxTrackSize = " << Point(mmi->ptMaxTrackSize));
			LLOG("ptMaxSize = " << Point(mmi->ptMaxSize) << ", ptMaxPosition = " << Point(mmi->ptMaxPosition));
		}
		return 0L;
#endif
/*	case WM_SETTINGCHANGE:
	case 0x031A: // WM_THEMECHANGED
		ReSkin();
		RefreshLayoutDeep();
		RefreshFrame();
		break;
*/
/*
    case WM_IME_COMPOSITION:
		HIMC himc = ImmGetContext(hwnd);
		if(!himc) break;
		CANDIDATEFORM cf;
		Rect r = GetScreenRect();
		cf.dwIndex = 0;
		cf.dwStyle = CFS_CANDIDATEPOS;
		cf.ptCurrentPos.x = r.left + caretx;
		cf.ptCurrentPos.y = r.top + carety + caretcy;
		ImmSetCandidateWindow (himc, &cf);
		break;
*/
	}
	if(hwnd) {
		if(IsWindowUnicode(hwnd)) // TRC 04/10/17: ActiveX unicode patch
			return DefWindowProcW(hwnd, message, wParam, lParam);
		else
			return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0L;
}

void Ctrl::PreDestroy() {}

}

#endif