// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://github.com/go-vgo/robotgo/blob/master/LICENSE
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#include "pub.h"

bool setHandle(uintptr handle);
bool is_valid();
bool IsAxEnabled(bool options);

MData get_active(void);
void initWindow();
char* get_title_by_hand(MData m_data);
void close_window_by_Id(MData m_data);

// int findwindow()
uintptr initHandle = 0;

void initWindow(uintptr handle){
#if defined(IS_MACOSX)
	mData.CgID = 0;
	mData.AxID = 0;
#elif defined(USE_X11)
	Display *rDisplay = XOpenDisplay(NULL);
	// If atoms loaded
	if (WM_PID == None) {
		// Load all necessary atom properties
		if (rDisplay != NULL) {LoadAtoms();}
	}

	mData.XWin = 0;
	XCloseDisplay(rDisplay);
#elif defined(IS_WINDOWS)
	mData.HWnd = 0;
#endif
	setHandle(handle);
}

bool Is64Bit() {
	#ifdef RobotGo_64
		return true;
	#endif

	return false;
}

MData set_handle_pid(uintptr pid, uintptr isHwnd){
	MData win;
	#if defined(IS_MACOSX)
		// Handle to a AXUIElementRef
		win.AxID = AXUIElementCreateApplication(pid);
	#elif defined(USE_X11)
		win.XWin = (Window)pid;  // Handle to an X11 window
	#elif defined(IS_WINDOWS)
		// win.HWnd = (HWND)pid;		// Handle to a window HWND
		if (isHwnd == 0) {
			win.HWnd = GetHwndByPId(pid);
		} else {
			win.HWnd = (HWND)pid;
		}
	#endif

	return win;
}

void set_handle_pid_mData(uintptr pid, uintptr isHwnd){
	MData win = set_handle_pid(pid, isHwnd);
	mData = win;
}

bool is_valid() {
	initWindow(initHandle);
	if (!IsAxEnabled(true)) {
		printf("%s\n", "Window: Accessibility API is disabled!\n"
		"Failed to enable access for assistive devices.");
	}
	MData actdata = get_active();

#if defined(IS_MACOSX)
	mData.CgID = actdata.CgID;
	mData.AxID = actdata.AxID;
	if (mData.CgID == 0 || mData.AxID == 0) { return false; }

	CFTypeRef r = NULL;
	// Attempt to get the window role
	if (AXUIElementCopyAttributeValue(mData.AxID, kAXRoleAttribute, &r) == kAXErrorSuccess && r){
			CFRelease(r);
			return true;
	}

	return false;
#elif defined(USE_X11)
	mData.XWin = actdata.XWin;
	if (mData.XWin == 0) { return false; }

	Display *rDisplay = XOpenDisplay(NULL);
	// Check for a valid X-Window display
	if (rDisplay == NULL) { return false; }

	// Ignore X errors
	XDismissErrors();

	// Get the window PID property
	void* result = GetWindowProperty(mData, WM_PID,NULL);
	if (result == NULL) { return false; }

	// Free result and return true
	XFree(result);
	XCloseDisplay(rDisplay);

	return true;
#elif defined(IS_WINDOWS)
	mData.HWnd = actdata.HWnd;
	if (mData.HWnd == 0) {
		return false;
	}

	return IsWindow(mData.HWnd) != 0;
#endif
}

bool IsAxEnabled(bool options){
#if defined(IS_MACOSX)
	// Statically load all required functions one time
	static dispatch_once_t once; dispatch_once (&once,
	^{
		// Open the framework
		void* handle = dlopen("/System/Library/Frameworks/Application" 
			"Services.framework/ApplicationServices", RTLD_LAZY);

		// Validate the handle
		if (handle != NULL) {
			*(void**) (&gAXIsProcessTrustedWithOptions) = dlsym (handle, "AXIsProcessTrustedWithOptions");
			gkAXTrustedCheckOptionPrompt = (CFStringRef*) dlsym (handle, "kAXTrustedCheckOptionPrompt");
		}
	});

	// Check for new OSX 10.9 function
	if (gAXIsProcessTrustedWithOptions) {
		// Check whether to show prompt
		CFBooleanRef displayPrompt = options ? kCFBooleanTrue : kCFBooleanFalse;

		// Convert display prompt value into a dictionary
		const void* k[] = { *gkAXTrustedCheckOptionPrompt };
		const void* v[] = { displayPrompt };
		CFDictionaryRef o = CFDictionaryCreate(NULL, k, v, 1, NULL, NULL);

		// Determine whether the process is actually trusted
		bool result = (*gAXIsProcessTrustedWithOptions)(o);
		// Free memory
		CFRelease(o);
		return result;
	} else {
		// Ignore deprecated warnings
		#pragma clang diagnostic push
		#pragma clang diagnostic ignored "-Wdeprecated-declarations"

		// Check whether we have accessibility access
		return AXAPIEnabled() || AXIsProcessTrusted();
		#pragma clang diagnostic pop
	}
#elif defined(USE_X11)
	return true;
#elif defined(IS_WINDOWS)
	return true;
#endif
}

// int
bool setHandle(uintptr handle){
#if defined(IS_MACOSX)
	// Release the AX element
	if (mData.AxID != NULL) {
		CFRelease(mData.AxID);
	}

	// Reset both values
	mData.CgID = 0;
	mData.AxID = 0;

	if (handle == 0) {
		// return 0;
		return true;
	}

	// Retrieve the window element
	CGWindowID cgID = (CGWindowID)handle;
	AXUIElementRef axID = GetUIElement(cgID);
	if (axID != NULL){
		mData.CgID = cgID;
		mData.AxID = axID;
		// return 0;
		return true;
	}

	// return 1;
	return false;
#elif defined(USE_X11)
	mData.XWin = (Window)handle;
	if (handle == 0) {
		return true;
	}

	if (is_valid()) {
		return true;
	}

	mData.XWin = 0;
	return false;
#elif defined(IS_WINDOWS)
	mData.HWnd = (HWND)handle;
	if (handle == 0) {
		return true;
	}

	if (is_valid()) {
		return true;
	}

	mData.HWnd = 0;
	return false;
#endif
}

bool IsTopMost(void){
	// Check the window validity
	if (!is_valid()) { return false; }
#if defined(IS_MACOSX)
	return false; // WARNING: Unavailable
#elif defined(USE_X11)
	// Ignore X errors
	// XDismissErrors ();
	// return GetState (mData.XWin, STATE_TOPMOST);
#elif defined(IS_WINDOWS)
	return (GetWindowLongPtr(mData.HWnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
#endif
}

bool IsMinimized(void){
	// Check the window validity
	if (!is_valid()) { return false; }
#if defined(IS_MACOSX)
	CFBooleanRef data = NULL;
	// Determine whether the window is minimized
	if (AXUIElementCopyAttributeValue(mData.AxID, kAXMinimizedAttribute, 
	(CFTypeRef*) &data) == kAXErrorSuccess && data != NULL) {
		// Convert resulting data into a bool
		bool result = CFBooleanGetValue(data);
		CFRelease(data);
		return result;
	}

	return false;
#elif defined(USE_X11)
	// Ignore X errors
	// XDismissErrors();
	// return GetState(mData.XWin, STATE_MINIMIZE);
#elif defined(IS_WINDOWS)
	return (GetWindowLongPtr(mData.HWnd, GWL_STYLE) & WS_MINIMIZE) != 0;
#endif
}

//////
bool IsMaximized(void){
	// Check the window validity
	if (!is_valid()) { return false; }
#if defined(IS_MACOSX)
	return false; // WARNING: Unavailable
#elif defined(USE_X11)
	// Ignore X errors
	// XDismissErrors();
	// return GetState(mData.XWin, STATE_MAXIMIZE);
#elif defined(IS_WINDOWS)
	return (GetWindowLongPtr(mData.HWnd, GWL_STYLE) & WS_MAXIMIZE) != 0;
#endif
}

void set_active(const MData win) {
	// Check if the window is valid
	if (!is_valid()) { return; }
#if defined(IS_MACOSX)
	// Attempt to raise the specified window object
	if (AXUIElementPerformAction(win.AxID, kAXRaiseAction) != kAXErrorSuccess) {
		pid_t pid = 0;
		// Attempt to retrieve the PID of the window
		if (AXUIElementGetPid(win.AxID, &pid) != kAXErrorSuccess || !pid) { return; }

		// Ignore deprecated warnings
		#pragma clang diagnostic push
		#pragma clang diagnostic ignored "-Wdeprecated-declarations"

		ProcessSerialNumber psn;
		// Attempt to retrieve the process psn
		if (GetProcessForPID(pid, &psn) == 0) {
			// Gracefully activate process
			SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly);
		}

		#pragma clang diagnostic pop
	}
#elif defined(USE_X11)
	// Ignore X errors
	XDismissErrors();

	// Go to the specified window's desktop
	SetDesktopForWindow(win);
	Display *rDisplay = XOpenDisplay(NULL);
	// Check the atom value
	if (WM_ACTIVE != None) {
		// Retrieve the screen number
		XWindowAttributes attr = { 0 };
		XGetWindowAttributes(rDisplay, win.XWin, &attr);
		int s = XScreenNumberOfScreen(attr.screen);

		// Prepare an event
		XClientMessageEvent e = { 0 };
		e.window = win.XWin;
		e.format = 32;
		e.message_type = WM_ACTIVE;
		e.display = rDisplay;
		e.type = ClientMessage;
		e.data.l[0] = 2;
		e.data.l[1] = CurrentTime;

		// Send the message
		XSendEvent(rDisplay, XRootWindow(rDisplay, s), False,
			SubstructureNotifyMask | SubstructureRedirectMask,
			(XEvent*) &e);
	} else {
		// Attempt to raise the specified window
		XRaiseWindow(rDisplay, win.XWin);
		// Set the specified window's input focus
		XSetInputFocus(rDisplay, win.XWin, RevertToParent, CurrentTime);
	}
	XCloseDisplay(rDisplay);
#elif defined(IS_WINDOWS)
	if (IsMinimized()) {
		ShowWindow(win.HWnd, SW_RESTORE);
	}

	SetForegroundWindow(win.HWnd);
#endif
}

MData get_active(void) {
#if defined(IS_MACOSX)
	MData result;
	// Ignore deprecated warnings
	#pragma clang diagnostic push
	#pragma clang diagnostic ignored "-Wdeprecated-declarations"

	ProcessSerialNumber psn; pid_t pid;
	// Attempt to retrieve the front process
	if (GetFrontProcess(&psn) != 0 || GetProcessPID(&psn, &pid) != 0) {
		return result;
	}

	#pragma clang diagnostic pop

	// Create accessibility object using focused PID
	AXUIElementRef focused = AXUIElementCreateApplication(pid);
	if (focused == NULL) { return result; } // Verify

	AXUIElementRef element;
	// Retrieve the currently focused window
	if (AXUIElementCopyAttributeValue(focused, kAXFocusedWindowAttribute, (CFTypeRef*) &element) 
		== kAXErrorSuccess && element) {

		CGWindowID win = 0;
		// Use undocumented API to get WID
		if (_AXUIElementGetWindow(element, &win) == kAXErrorSuccess && win) {
			// Manually set internals
			result.CgID = win;
			result.AxID = element;
		} else {
			CFRelease(element);
		}
	}
	CFRelease(focused);

	return result;
#elif defined(USE_X11)
	MData result;
	Display *rDisplay = XOpenDisplay(NULL);
	// Check X-Window display
	if (WM_ACTIVE == None || rDisplay == NULL) {
		return result;
	}

	// Ignore X errors
	XDismissErrors();

	// Get the current active window
	result.XWin = XDefaultRootWindow(rDisplay);
	void* active = GetWindowProperty(result, WM_ACTIVE, NULL);

	// Check result value
	if (active != NULL) {
		// Extract window from the result
		long window = *((long*)active);
		XFree(active);

		if (window != 0) {
			// Set and return the foreground window
			result.XWin = (Window)window;
			return result;
		}
	}

	// Use input focus instead
	Window window = None;
	int revert = RevertToNone;
	XGetInputFocus(rDisplay, &window, &revert);
	XCloseDisplay(rDisplay);

	// Return foreground window
	result.XWin = window;
	return result;
#elif defined(IS_WINDOWS)
	// Attempt to get the foreground window multiple times in case
	MData result;

	uint8_t times = 0;
	while (++times < 20) {
		HWND handle;
		handle = GetForegroundWindow();
		if (handle != NULL) {
			// mData.HWnd = (uintptr) handle;
			result.HWnd = (HWND)handle;
			return result;
		}
		Sleep(20);
	}

	return result;
#endif
}

void SetTopMost(bool state){
	// Check window validity
	if (!is_valid()) { return; }
#if defined(IS_MACOSX)
	// WARNING: Unavailable
#elif defined(USE_X11)
	// Ignore X errors
	// XDismissErrors();
	// SetState(mData.XWin, STATE_TOPMOST, state);
#elif defined(IS_WINDOWS)
	SetWindowPos(mData.HWnd, state ? HWND_TOPMOST : HWND_NOTOPMOST,
		0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
#endif
}

void close_main_window () {
   // Check if the window is valid
	if (!is_valid()) { return; }

	close_window_by_Id(mData);
}

void close_window_by_PId(uintptr pid, uintptr isHwnd){
	MData win = set_handle_pid(pid, isHwnd);
	close_window_by_Id(win);
}

// CloseWindow
void close_window_by_Id(MData m_data){
	// Check window validity
	if (!is_valid()) { return; }
#if defined(IS_MACOSX)
	AXUIElementRef b = NULL;
	// Retrieve the close button of this window
	if (AXUIElementCopyAttributeValue(m_data.AxID, kAXCloseButtonAttribute, (CFTypeRef*) &b) 
		== kAXErrorSuccess && b != NULL) {
		// Simulate button press on the close button
		AXUIElementPerformAction(b, kAXPressAction);
		CFRelease(b);
	}
#elif defined(USE_X11)
	Display *rDisplay = XOpenDisplay(NULL);
	// Ignore X errors
	XDismissErrors();

	// Close the window
	XDestroyWindow(rDisplay, m_data.XWin);
	XCloseDisplay(rDisplay);
#elif defined(IS_WINDOWS)
	PostMessage(m_data.HWnd, WM_CLOSE, 0, 0);
#endif
}

char* get_main_title(){
	// Check if the window is valid
	if (!is_valid()) { return "is_valid failed."; }
	
	return get_title_by_hand(mData);
}

char* get_title_by_pid(uintptr pid, uintptr isHwnd){
	MData win = set_handle_pid(pid, isHwnd);
  	return get_title_by_hand(win);
}

char* named(void *result) {
	char *name = (char*)calloc(strlen(result)+1, sizeof(char*));
	char *rptr = (char*)result;
	char *nptr = name;
	while (*rptr) {
		*nptr = *rptr;
		nptr++;
		rptr++;
	}
	*nptr = '\0';

	return name;
}

char* get_title_by_hand(MData m_data){
	// Check if the window is valid
	if (!is_valid()) { return "is_valid failed."; }
#if defined(IS_MACOSX)
	CFStringRef data = NULL;
	// Determine the current title of the window
	if (AXUIElementCopyAttributeValue(m_data.AxID, kAXTitleAttribute, (CFTypeRef*) &data) == 
			kAXErrorSuccess && data != NULL) {
		char conv[512];
		// Convert result to a C-String
		CFStringGetCString(data, conv, 512, kCFStringEncodingUTF8);
		CFRelease(data);

		char* s = (char*)calloc(100, sizeof(char*));
		if (s) { strcpy(s, conv); }
		// return (char *)&conv;
		return s;
	}

	return "";
#elif defined(USE_X11)
	void* result;
	// Ignore X errors
	XDismissErrors();

	// Get window title (UTF-8)
	result = GetWindowProperty(m_data, WM_NAME, NULL);
	// Check result value
	if (result != NULL) {
		// Convert result to a string
		char* name = named(result);
		XFree(result);

		if (name != NULL) { return name; }
	}

	// Get window title (ASCII)
	result = GetWindowProperty(m_data, XA_WM_NAME, NULL);
	// Check result value
	if (result != NULL) {
		// Convert result to a string
		char* name = named(result);
		XFree(result);

		return name;
	}

	return "";
#elif defined(IS_WINDOWS)
	if (GetWindowText(m_data.HWnd, m_data.Title, 512) > 0){
		char* name = m_data.Title;

		char* str = (char*)calloc(100, sizeof(char*));
		if (str) { strcpy(str, name); }
		return str;
	}

	return "";
#endif
}

int32_t get_PID(void) {
	// Check window validity
	if (!is_valid()) { return 0; }
#if defined(IS_MACOSX)
	pid_t pid = 0;
	// Attempt to retrieve the window pid
	if (AXUIElementGetPid(mData.AxID, &pid)== kAXErrorSuccess) {
		return pid;
	}
	return 0;
#elif defined(USE_X11)
	// Ignore X errors
	XDismissErrors();

	// Get the window PID
	long* result = (long*)GetWindowProperty(mData, WM_PID,NULL);
	// Check result and convert it
	if (result == NULL) { return 0; }
	
	int32_t pid = (int32_t) *result;
	XFree(result);
	return pid;
#elif defined(IS_WINDOWS)
	DWORD id = 0;
	GetWindowThreadProcessId(mData.HWnd, &id);
	return id;
#endif
}