// 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 "../base/os.h"
#if defined(IS_MACOSX)
	#include <dlfcn.h>
#elif defined(USE_X11)
	#include <X11/Xatom.h>
#endif

struct _MData{
	#if defined(IS_MACOSX)
		CGWindowID		CgID;		// Handle to a CGWindowID
		AXUIElementRef	AxID;		// Handle to a AXUIElementRef
	#elif defined(USE_X11)
		Window		XWin;		// Handle to an X11 window
	#elif defined(IS_WINDOWS)
		HWND			HWnd;		// Handle to a window HWND
		TCHAR 	Title[512];
	#endif
};

typedef struct _MData MData;
MData pub_mData;

struct _Bounds {
	int32_t		X;				// Top left X coordinate
	int32_t		Y;				// Top left Y coordinate
	int32_t		W;				// bounds width
	int32_t		H;				// bounds height
};
typedef struct _Bounds Bounds;

#if defined(IS_MACOSX)
	static Boolean(*gAXIsProcessTrustedWithOptions) (CFDictionaryRef);
	static CFStringRef* gkAXTrustedCheckOptionPrompt;

	AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);
	static AXUIElementRef GetUIElement(CGWindowID win){
		intptr pid = 0;
		// double_t pid = 0;
		// Create array storing window
		CGWindowID window[1] = { win };
		CFArrayRef wlist = CFArrayCreate(NULL, (const void**)window, 1, NULL);

		// Get window info
		CFArrayRef info = CGWindowListCreateDescriptionFromArray(wlist);
		CFRelease(wlist);

		// Check whether the resulting array is populated
		if (info != NULL && CFArrayGetCount(info) > 0) {
			// Retrieve description from info array
			CFDictionaryRef desc = (CFDictionaryRef)CFArrayGetValueAtIndex(info, 0);
			// Get window PID
			CFNumberRef data = (CFNumberRef) CFDictionaryGetValue(desc, kCGWindowOwnerPID);
			if (data != NULL) {
				CFNumberGetValue(data, kCFNumberIntType, &pid);
			}

			// Return result
			CFRelease(info);
		}

		// Check if PID was retrieved
		if (pid <= 0) { return NULL; }

		// Create an accessibility object using retrieved PID
		AXUIElementRef application = AXUIElementCreateApplication(pid);
		if (application == 0) {return NULL;}

		CFArrayRef windows = NULL;
		// Get all windows associated with the app
		AXUIElementCopyAttributeValues(application, kAXWindowsAttribute, 0, 1024, &windows);

		// Reference to resulting value
		AXUIElementRef result = NULL;

		if (windows != NULL) {
			int count = CFArrayGetCount(windows);
			// Loop all windows in the process
			for (CFIndex i = 0; i < count; ++i){
				// Get the element at the index
				AXUIElementRef element = (AXUIElementRef) CFArrayGetValueAtIndex(windows, i);
				CGWindowID temp = 0;
				// Use undocumented API to get WindowID
				_AXUIElementGetWindow(element, &temp);

				if (temp == win) {
					// Retain element
					CFRetain(element);
					result = element;
					break;
				}
			}

			CFRelease(windows);
		}

		CFRelease(application);
		return result;
	}
#elif defined(USE_X11)
	// Error Handling
	typedef int (*XErrorHandler) (Display*, XErrorEvent*);

	static int XHandleError(Display* dp, XErrorEvent* e) { return 0; }
		XErrorHandler mOld;
		void XDismissErrors (void) {
			Display *rDisplay = XOpenDisplay(NULL);
			// Save old handler and dismiss errors
			mOld = XSetErrorHandler(XHandleError);
			// Flush output buffer
			XSync(rDisplay, False);

			// Reinstate old handler
			XSetErrorHandler(mOld);
			XCloseDisplay(rDisplay);
		}

	// Definitions
	struct Hints{
		unsigned long Flags;
		unsigned long Funcs;
		unsigned long Decorations;
		signed   long Mode;
		unsigned long Stat;
	};

	static Atom WM_STATE	= None;
	static Atom WM_ABOVE	= None;
	static Atom WM_HIDDEN	= None;
	static Atom WM_HMAX		= None;
	static Atom WM_VMAX		= None;

	static Atom WM_DESKTOP	= None;
	static Atom WM_CURDESK	= None;

	static Atom WM_NAME		= None;
	static Atom WM_UTF8		= None;
	static Atom WM_PID		= None;
	static Atom WM_ACTIVE	= None;
	static Atom WM_HINTS	= None;
	static Atom WM_EXTENTS	= None;

	////////////////////////////////////////////////////////////////////////////////

	static void LoadAtoms (void){
		Display *rDisplay = XOpenDisplay(NULL);
		WM_STATE   = XInternAtom(rDisplay, "_NET_WM_STATE",                True);
		WM_ABOVE   = XInternAtom(rDisplay, "_NET_WM_STATE_ABOVE",          True);
		WM_HIDDEN  = XInternAtom(rDisplay, "_NET_WM_STATE_HIDDEN",         True);
		WM_HMAX    = XInternAtom(rDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
		WM_VMAX    = XInternAtom(rDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", True);

		WM_DESKTOP = XInternAtom(rDisplay, "_NET_WM_DESKTOP",              True);
		WM_CURDESK = XInternAtom(rDisplay, "_NET_CURRENT_DESKTOP",         True);

		WM_NAME    = XInternAtom(rDisplay, "_NET_WM_NAME",                 True);
		WM_UTF8    = XInternAtom(rDisplay, "UTF8_STRING",                  True);
		WM_PID     = XInternAtom(rDisplay, "_NET_WM_PID",                  True);
		WM_ACTIVE  = XInternAtom(rDisplay, "_NET_ACTIVE_WINDOW",           True);
		WM_HINTS   = XInternAtom(rDisplay, "_MOTIF_WM_HINTS",              True);
		WM_EXTENTS = XInternAtom(rDisplay, "_NET_FRAME_EXTENTS",           True);
		XCloseDisplay(rDisplay);
	}

	// Functions
	static void* GetWindowProperty(MData win, Atom atom, uint32_t* items) {
		// Property variables
		Atom type; int format;
		unsigned long  nItems;
		unsigned long  bAfter;
		unsigned char* result = NULL;

		Display *rDisplay = XOpenDisplay(NULL);
		// Check the atom
		if (atom != None) {
			// Retrieve and validate the specified property
			if (!XGetWindowProperty(rDisplay, win.XWin, atom, 0, 
				BUFSIZ, False, AnyPropertyType, &type, &format, &nItems, &bAfter, &result) 
				&& result && nItems) {

				// Copy items result
				if (items != NULL) {
					*items = (uint32_t) nItems;
				}
				XCloseDisplay(rDisplay);
				return result;
			}
		}

		// Reset the items result if valid
		if (items != NULL) { *items = 0; }
		if (result != NULL) {
			XFree(result);
		}

		XCloseDisplay(rDisplay);
		return NULL;
	}

	//////
	#define STATE_TOPMOST  0
	#define STATE_MINIMIZE 1
	#define STATE_MAXIMIZE 2

	//////
	static void SetDesktopForWindow(MData win){
		Display *rDisplay = XOpenDisplay(NULL);
		// Validate every atom that we want to use
		if (WM_DESKTOP != None && WM_CURDESK != None) {
			// Get desktop property
			long* desktop = (long*)GetWindowProperty(win, WM_DESKTOP,NULL);
			// Check result value
			if (desktop != NULL) {
				// Retrieve the screen number
				XWindowAttributes attr = { 0 };
				XGetWindowAttributes(rDisplay, win.XWin, &attr);
				int s = XScreenNumberOfScreen(attr.screen);
				Window root = XRootWindow(rDisplay, s);

				// Prepare an event
				XClientMessageEvent e = { 0 };
				e.window = root; e.format = 32;
				e.message_type = WM_CURDESK;
				e.display = rDisplay;
				e.type = ClientMessage;
				e.data.l[0] = *desktop;
				e.data.l[1] = CurrentTime;

				// Send the message
				XSendEvent(rDisplay, root, False, SubstructureNotifyMask | SubstructureRedirectMask, 
					(XEvent*) &e);

				XFree(desktop);
			}
		}
		XCloseDisplay(rDisplay);
	}

	static Bounds GetFrame(MData win){
		Bounds frame;
		// Retrieve frame bounds
		if (WM_EXTENTS != None) {
			long* result; uint32_t nItems = 0;
			// Get the window extents property
			result = (long*) GetWindowProperty(win, WM_EXTENTS, &nItems);
			if (result != NULL) {
				if (nItems == 4) {
					frame.X = (int32_t) result[0];
					frame.Y = (int32_t) result[2];
					frame.W = (int32_t) result[0] + (int32_t) result[1];
					frame.H = (int32_t) result[2] + (int32_t) result[3];
				}

				XFree(result);
			}
		}
		return frame;
	}


#elif defined(IS_WINDOWS)
	HWND getHwnd(uintptr pid, int8_t isPid);
	
	void win_min(HWND hwnd, bool state){
        if (state) {
            ShowWindow(hwnd, SW_MINIMIZE);
        } else {
            ShowWindow(hwnd, SW_RESTORE);
        }
    }

    void win_max(HWND hwnd, bool state){
        if (state) {
            ShowWindow(hwnd, SW_MAXIMIZE);
        } else {
            ShowWindow(hwnd, SW_RESTORE);
        }
    }
#endif