package com.danitheskunk.skunkworks.os.windows;


import com.danitheskunk.skunkworks.Recti;
import com.danitheskunk.skunkworks.Util;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.*;
import com.sun.jna.ptr.IntByReference;

import java.util.ArrayList;
import java.util.List;

public class Window {
	private final WinDef.HWND hwnd;

	public Window(WinDef.HWND hwnd) {
		this.hwnd = hwnd;
	}

	//static functions
	public static List<Window> getAllVisible() {
		var user32 = User32Extra.INSTANCE;
		var windows = new ArrayList<Window>();

		user32.EnumWindows((hwnd, pointer) -> {
			var window = new Window(hwnd);
			if(window.isInAltTabList()) windows.add(window);
			return true;
		}, null);

		return windows;
	}

	public static void messageLoop() {
		var user32 = User32Extra.INSTANCE;
		var msg = new WinUser.MSG();
		while(user32.GetMessage(msg, null, 0, 0) != 0) {
			System.out.println("msg pump");
			user32.TranslateMessage(msg);
			user32.DispatchMessage(msg);
			if(msg.message == User32Extra.WM_TIMER) return;
		}
	}

	public static void messageTick() {
		//todo: check if this even works (switch to getmessage, and add timer
		// function?)
		var user32 = User32Extra.INSTANCE;
		var msg = new WinUser.MSG();
		//while(user32.PeekMessage(msg, null, 0, 0, User32Extra.PM_NOREMOVE)) {
		user32.GetMessage(msg, null, 0, 0);
		System.out.println("msg pump");
		user32.TranslateMessage(msg);
		user32.DispatchMessage(msg);
		//}
	}

	public static void onKey() {
		var user32 = User32Extra.INSTANCE;
		var res = user32.SetWindowsHookEx(User32Extra.WH_KEYBOARD_LL,
			new WinUser.HOOKPROC() {
				public WinDef.LRESULT callback(
					int nCode, WinDef.WPARAM wparam, WinDef.LPARAM lparam
				) {
					new WinUser.KBDLLHOOKSTRUCT();
					var kb =
						Structure.newInstance(WinUser.KBDLLHOOKSTRUCT.class,
						lparam.toPointer()
					);
					System.out.println(kb.vkCode);
					switch(wparam.intValue()) {
						case User32Extra.WM_KEYDOWN -> System.out.println(
							"WM_KEYDOWN");
						case User32Extra.WM_KEYUP -> System.out.println(
							"WM_KEYUP");
						case User32Extra.WM_SYSKEYDOWN -> System.out.println(
							"WM_SYSKEYDOWN");
						case User32Extra.WM_SYSKEYUP -> System.out.println(
							"WM_SYSKEYUP");
					}
					System.out.println("got some message a");
					return user32.CallNextHookEx(null, nCode, wparam, lparam);
				}
			},
			null,
			0
		);
		if(res == null) {
			throw new RuntimeException("Couldn't setwindowhookex: " +
				Native.getLastError());
		}
	}

	public static void onNewWindow() {
		var user32 = User32Extra.INSTANCE;
		var kernel32 = Kernel32.INSTANCE;
		var hmod = kernel32.GetModuleHandle("kernel32.dll");
		//todo: try RegisterShellHookWindow instead
		var res = user32.SetWindowsHookEx(User32Extra.WH_SHELL,
			new WinUser.HOOKPROC() {
				public WinDef.LRESULT callback(
					int nCode, WinDef.WPARAM ignoredWparam,
					WinDef.LPARAM ignoredLparam
				) {
					System.out.println("got some message b");
					if(nCode == User32Extra.HSHELL_WINDOWCREATED) {
						System.out.println("window created");
					}
					return new WinDef.LRESULT(0);
				}
			},
			hmod,
			0
		);
		if(res == null) {
			throw new RuntimeException("Couldn't setwindowhookex: " +
				Native.getLastError());
		}
	}

	public static void onTimer(int ms) {
		WinUserExtra.INSTANCE.SetTimer(null, null, new WinDef.UINT(ms), null);
	}

	public void debugPrintStyles() {
		var str = new StringBuilder();
		var style = getStyle();
		str.append("Window Styles: ");
		if((style & User32.WS_BORDER) != 0) str.append("ws_border, ");
		if((style & User32.WS_CAPTION) != 0) str.append("ws_caption, ");
		if((style & User32.WS_CHILD) != 0) str.append("ws_child, ");
		if((style & User32.WS_CHILDWINDOW) != 0) str.append("ws_childwindow," +
			" ");
		if((style & User32.WS_CLIPCHILDREN) != 0)
			str.append("ws_clipchildren, ");
		if((style & User32.WS_CLIPSIBLINGS) != 0)
			str.append("ws_clipsiblings, ");
		if((style & User32.WS_DISABLED) != 0) str.append("ws_disabled, ");
		if((style & User32.WS_DLGFRAME) != 0) str.append("ws_dlgframe, ");
		if((style & User32.WS_GROUP) != 0) str.append("ws_group, ");
		if((style & User32.WS_HSCROLL) != 0) str.append("ws_hscroll, ");
		if((style & User32.WS_ICONIC) != 0) str.append("ws_iconic, ");
		if((style & User32.WS_MAXIMIZE) != 0) str.append("ws_maximize, ");
		if((style & User32.WS_MAXIMIZEBOX) != 0) str.append("ws_maximizebox," +
			" ");
		if((style & User32.WS_MINIMIZE) != 0) str.append("ws_minimize, ");
		if((style & User32.WS_MINIMIZEBOX) != 0) str.append("ws_minimizebox," +
			" ");
		if((style & User32.WS_OVERLAPPED) != 0) str.append("ws_overlapped, ");
		if((style & User32.WS_POPUP) != 0) str.append("ws_popup, ");
		if((style & User32.WS_SIZEBOX) != 0) str.append("ws_sizebox, ");
		if((style & User32.WS_SYSMENU) != 0) str.append("ws_sysmenu, ");
		if((style & User32.WS_TABSTOP) != 0) str.append("ws_tabstop, ");
		if((style & User32.WS_THICKFRAME) != 0) str.append("ws_thickframe, ");
		if((style & User32.WS_TILED) != 0) str.append("ws_tiled, ");
		if((style & User32.WS_VISIBLE) != 0) str.append("ws_visible, ");
		if((style & User32.WS_VSCROLL) != 0) str.append("ws_vscroll, ");
		System.out.println(str);
	}

	public void debugPrintStylesEx() {
		var str = new StringBuilder();
		var style = getStyleEx();
		str.append("Window Styles EX: ");
		if((style & User32Extra.WS_EX_ACCEPTFILES) != 0) str.append(
			"ws_ex_acceptfiles, ");
		if((style & User32Extra.WS_EX_APPWINDOW) != 0) str.append(
			"ws_ex_appwindow, ");
		if((style & User32Extra.WS_EX_CLIENTEDGE) != 0) str.append(
			"ws_ex_clientedge, ");
		if((style & User32Extra.WS_EX_COMPOSITED) != 0) str.append(
			"ws_ex_composited, ");
		if((style & User32Extra.WS_EX_CONTEXTHELP) != 0) str.append(
			"ws_ex_contexthelp, ");
		if((style & User32Extra.WS_EX_CONTROLPARENT) != 0) str.append(
			"ws_ex_controlparent, ");
		if((style & User32Extra.WS_EX_DLGMODALFRAME) != 0) str.append(
			"ws_ex_dlgmodalframe, ");
		if((style & User32Extra.WS_EX_LAYERED) != 0) str.append(
			"ws_ex_layered, ");
		if((style & User32Extra.WS_EX_LAYOUTRTL) != 0) str.append(
			"ws_ex_layoutrtl, ");
		if((style & User32Extra.WS_EX_LEFT) != 0) str.append("ws_ex_left, ");
		if((style & User32Extra.WS_EX_LEFTSCROLLBAR) != 0) str.append(
			"ws_ex_leftscrollbar, ");
		if((style & User32Extra.WS_EX_LTRREADING) != 0) str.append(
			"ws_ex_ltrreading, ");
		if((style & User32Extra.WS_EX_MDICHILD) != 0) str.append(
			"ws_ex_mdichild, ");
		if((style & User32Extra.WS_EX_NOACTIVATE) != 0) str.append(
			"ws_ex_noactivate, ");
		if((style & User32Extra.WS_EX_NOINHERITLAYOUT) != 0) str.append(
			"ws_ex_noinheritedlayout, ");
		if((style & User32Extra.WS_EX_NOPARENTNOTIFY) != 0) str.append(
			"ws_ex_noparentnotify, ");
		if((style & User32Extra.WS_EX_NOREDIRECTIONBITMAP) != 0) str.append(
			"ws_ex_noredirectionbitmap, ");
		if((style & User32Extra.WS_EX_RIGHT) != 0) str.append("ws_ex_right, ");
		if((style & User32Extra.WS_EX_RIGHTSCROLLBAR) != 0) str.append(
			"ws_ex_rightscrollbar, ");
		if((style & User32Extra.WS_EX_RTLREADING) != 0) str.append(
			"ws_ex_rtlreading, ");
		if((style & User32Extra.WS_EX_STATICEDGE) != 0) str.append(
			"ws_ex_staticedge, ");
		if((style & User32Extra.WS_EX_TOOLWINDOW) != 0) str.append(
			"ws_ex_toolwindow, ");
		if((style & User32Extra.WS_EX_TOPMOST) != 0) str.append(
			"ws_ex_topmost, ");
		if((style & User32Extra.WS_EX_TRANSPARENT) != 0) str.append(
			"ws_ex_transparent, ");
		if((style & User32Extra.WS_EX_WINDOWEDGE) != 0) str.append(
			"ws_ex_windowedge, ");
		System.out.println(str);
	}

	@Override
	public boolean equals(Object o) {
		if(!(o instanceof Window)) {
			return false;
		}
		return ((Window) o).hwnd.equals(hwnd);
	}

	public String getClassName() {
		var user32 = User32Extra.INSTANCE;
		char[] chars = new char[1024];

		user32.GetClassName(hwnd, chars, 1024);
		return Util.nullTerminatedCharArrayToString(chars);
	}

	public String getExecutableName() {
		var kernel32 = Kernel32.INSTANCE;
		var handle =
			kernel32.OpenProcess(WinNT.PROCESS_QUERY_LIMITED_INFORMATION,
			false,
			getProcessId()
		);
		char[] chars = new char[1024];
		var len = new IntByReference();
		len.setValue(1024);

		kernel32.QueryFullProcessImageName(handle, 0, chars, len);
		return Util.nullTerminatedCharArrayToString(chars);
	}

	public String getKey() {
		return getExecutableName() + " $ " + getClassName();
	}

	private int getProcessId() {
		var user32 = User32Extra.INSTANCE;
		var ref = new IntByReference();
		user32.GetWindowThreadProcessId(hwnd, ref);
		return ref.getValue();
	}

	private int getStyle() {
		var user32 = User32Extra.INSTANCE;
		return user32.GetWindowLong(hwnd, WinUser.GWL_STYLE);
	}

	private int getStyleEx() {
		var user32 = User32Extra.INSTANCE;
		return user32.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE);
	}

	public String getTitle() {
		var user32 = User32Extra.INSTANCE;
		char[] chars = new char[1024];

		user32.GetWindowText(hwnd, chars, 1024);
		return Util.nullTerminatedCharArrayToString(chars);
	}

	private TITLEBARINFO getTitleBarInfo() {
		var user32 = User32Extra.INSTANCE;
		var tb = new TITLEBARINFO();
		user32.GetTitleBarInfo(hwnd, tb);
		return tb;
	}

	public boolean isClass(String str) {
		return str.equals(getClassName());
	}

	public boolean isDwmCloaked() {
		var dwm = Dwm.INSTANCE;
		var out = new IntByReference();
		dwm.DwmGetWindowAttribute(hwnd,
			Dwm.DWMWA_CLOAKED,
			out.getPointer(),
			new WinDef.DWORD(WinDef.DWORD.SIZE)
		);
		var val = out.getValue();
		return val != 0;
	}

	public boolean isExecutableName(String str) {
		return str.equals(getExecutableName());
	}

	public boolean isInAltTabList() {
		var user32 = User32Extra.INSTANCE;
		var walk = user32.GetAncestor(hwnd, WinUser.GA_ROOTOWNER);

		var window = new Window(hwnd);
		if(walk == null) return false;
		return walk.equals(hwnd) &&
			user32.IsWindowVisible(hwnd) &&
			window.getTitle().length() > 0 &&
			!window.isToolWindow() &&
			!window.isDwmCloaked() &&
			!window.getClassName().equals("Progman");
	}

	private boolean isTitleBarVisible() {
		var tb = getTitleBarInfo();
		return (tb.rgstate[0] & User32Extra.STATE_SYSTEM_INVISIBLE) == 0;
	}

	public boolean isToolWindow() {
		return (getStyleEx() & User32Extra.WS_EX_TOOLWINDOW) != 0;
	}

	public void move(Recti rectangle) {
		var user32 = User32Extra.INSTANCE;
		var dwm = Dwm.INSTANCE;
		var pos = rectangle.getPos();
		var size = rectangle.getSize();

		var rect_with_shadow = new WinDef.RECT();
		var rect_without_shadow = new WinDef.RECT();
		var rect = new WinDef.RECT();

		user32.GetWindowRect(hwnd, rect_with_shadow);
		dwm.DwmGetWindowAttribute(hwnd,
			Dwm.DWMWA_EXTENDED_FRAME_BOUNDS,
			rect_without_shadow.getPointer(),
			new WinDef.DWORD(rect_without_shadow.size())
		);
		rect_without_shadow.read();
		var left_shadow = rect_without_shadow.left - rect_with_shadow.left;
		var right_shadow = rect_with_shadow.right - rect_without_shadow.right;
		var top_shadow = rect_without_shadow.top - rect_with_shadow.top;
		var bottom_shaddow = rect_with_shadow.bottom -
			rect_without_shadow.bottom;


		rect.left = pos.getX() - left_shadow;
		rect.top = pos.getY() - top_shadow;
		rect.right = pos.getX() + size.getX() + right_shadow;
		rect.bottom = pos.getY() + size.getY() + bottom_shaddow;

		user32.MoveWindow(hwnd,
			rect.left,
			rect.top,
			rect.right - rect.left,
			rect.bottom - rect.top,
			true
		);

		user32.SetWindowPos(hwnd,
			null,
			0,
			0,
			0,
			0,
			WinUser.SWP_FRAMECHANGED |
				WinUser.SWP_NOZORDER |
				WinUser.SWP_NOOWNERZORDER |
				WinUser.SWP_NOMOVE |
				WinUser.SWP_NOSIZE |
				WinUser.SWP_NOACTIVATE
		);
	}

	public void setBorder(boolean border) {

		var STYLES = User32Extra.WS_CAPTION |
			User32Extra.WS_THICKFRAME |
			User32Extra.WS_MINIMIZEBOX |
			User32Extra.WS_SYSMENU;

		var STYLESEX = User32Extra.WS_EX_DLGMODALFRAME |
			User32Extra.WS_EX_CLIENTEDGE |
			User32Extra.WS_EX_STATICEDGE;

		var style = getStyle();
		var styleex = getStyleEx();
		if(border) {
			style |= STYLES;
			styleex |= STYLESEX;
		} else {
			style &= ~STYLES;
			styleex &= ~STYLESEX;
		}
		setStyle(style);
		setStyleEx(styleex);


		var dwm = Dwm.INSTANCE;
		var margins = new MARGINS();
		margins.cxLeftWidth = 0;
		margins.cxRightWidth = 0;
		margins.cyTopHeight = 0;
		margins.cyBottomHeight = 0;
		dwm.DwmExtendFrameIntoClientArea(hwnd, margins);
	}

	public void setBorderRounded(boolean rounded) {
		var round = rounded ? Dwm.DWMWCP_ROUND : Dwm.DWMWCP_DONOTROUND;
		var roundref = new WinDef.DWORDByReference(round);
		Dwm.INSTANCE.DwmSetWindowAttribute(hwnd,
			Dwm.DWMWA_WINDOW_CORNER_PREFERENCE,
			roundref.getPointer(),
			new WinDef.DWORD(WinDef.DWORD.SIZE)
		);
	}

	private void setStyle(int style) {
		var user32 = User32Extra.INSTANCE;
		user32.SetWindowLong(hwnd, WinUser.GWL_STYLE, style);
	}

	private void setStyleEx(int style) {
		var user32 = User32Extra.INSTANCE;
		user32.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, style);
	}
}
