require 'dl/import'
require 'dl/struct'

require 'singleton'

module X11
	class << self
		include X11 # Do this so we can call imported libraries directly on X11
	end

	# Load the X11 library
	extend DL::Importable
	dlload "libX11.so" 
	
	# Import necessary functions from X11 here.
	import("XOpenDisplay", "unsigned long", ["char*"])
	import("XScreenCount", "int", ["unsigned long"])
	import("XRootWindow", "unsigned long", ["unsigned long","int"])

	import("XFree", "int", ["void*"])

	import("XFetchName", "int", ["unsigned long","unsigned long","char**"])
	import("XGetClassHint", "int", ["unsigned long","unsigned long","void*"])
	import("XQueryTree", "int", ["unsigned long","unsigned long","unsigned long*","unsigned long*","unsigned long**","unsigned int*"])

	import("XSetInputFocus", "int", ["unsigned long","unsigned long","int","long"])
	import("XSendEvent", "int", ["unsigned long","unsigned long","int","long","void*"])
	import("XFlush", "int", ["unsigned long"])

	# Structs we will use in API calls.
	# Pointer structs are necessary when the API uses a pointer parameter for a return value.
	ULPPointer = struct [
		"long* value"
	]
	ULPointer = struct [
		"long value"
	]
	CPPointer = struct [
		"char* value"
	]
	UIPointer = struct [
		"int value"
	]
	# Info about window class
	XClassHint = struct [
		"char* res_name",
		"char* res_class"
	]
	# Event struct for key presses
	XKeyEvent = struct [
		"int type",
		"long serial",
		"int send_event",
		"long display",
		"long window",
		"long root",
		"long subwindow",
		"long time",
		"int x",
		"int y",
		"int x_root",
		"int y_root",
		"int state",
		"int keycode",
		"int same_screen"
	]

	# End of library imports.

	# X11 Display. Singleton -- assumes single display.
	# Assumes the current display is the same as the one running FireFox.
	# Represented by memory pointer (which we treat in-code as an unsigned long).
	class Display
		include Singleton

		def initialize
			@xdisplay = X11.xOpenDisplay("");
		end

		# Array of screens associated with this display.
		def screens
			nScreens = X11.xScreenCount(@xdisplay);
			(0...nScreens).collect{|n| Screen.new(n,@xdisplay)}
		end
	end

	# A display screen, for multi-monitor displays like mine ;-)
	# Represented by display pointer and screen number.
	class Screen
		def initialize(screen_num,xdisplay)
			@screen_num = screen_num
			@xdisplay = xdisplay
		end

		# Root window containing all other windows in this screen.
		def root_window
			Window.new(X11.xRootWindow(@xdisplay,@screen_num),@screen_num,@xdisplay)
		end
	end

	# An X11 Window (toplevel window, widget, applet, etc.)
	# Represented by its XID, an unsigned long.
	class Window
		attr_reader :xid, :name, :class, :hint, :parent

		def initialize(xid,screen_num,xdisplay,parent=nil)
			@xid = xid
			@screen_num = screen_num
			@xdisplay = xdisplay
			@parent = parent
			load_standard
		end

		# Child windows
		def children
			tree[:children].collect{|c| Window.new(c,@screen_num,@xdisplay,self)}
		end

		# XID of parent window
		def parent_xid
			parent ? parent.xid : nil
		end

		# Send a key press to this window
		def send_key(key=:enter,sleep=nil)
			# TODO expand this list out, add support for shift, etc.
			@@keys = {:enter => 36, :tab => 23} unless defined?@@keys
			keycode = @@keys[key]
			X11.xSetInputFocus(@xdisplay, @xid, 1, 0)
			sleep(sleep) if sleep
			e = create_key_event
			e.keycode = keycode
			e.type = 2 # press
			X11.xSendEvent(@xdisplay,@xid,1,1,e)
			e.type = 3 # release
			X11.xSendEvent(@xdisplay,@xid,1,2,e)
			X11.xFlush(@xdisplay)
		end

		private

		# Retrieve this window's portion of the window tree
		# Includes display root, parent, and children
		def tree
			tree = {:children => [], :parent => 0, :root => 0}
			children = ULPPointer.malloc
			root = ULPointer.malloc
			parent = ULPointer.malloc
			n = UIPointer.malloc
			r=X11.xQueryTree(@xdisplay,@xid,root,parent,children,n)
			tree[:parent] = parent.value
			tree[:root] = root.value
			tree[:children] = children.value.to_s(4*n.value).unpack("L*") if children.value
			tree
		end

		# Load some standard attributes (name and class)
		def load_standard
			name = CPPointer.malloc
			if X11.xFetchName(@xdisplay,@xid,name) != 0
				@name = name.value.to_s
				X11.xFree name.value
			end
			classHint = XClassHint.malloc
			res = X11.xGetClassHint(@xdisplay,@xid,classHint)
			if res != 0 then
				@class = classHint.res_name.to_s
				@hint = classHint.res_class.to_s
				X11.xFree classHint.res_name
				X11.xFree classHint.res_class
			end
		end

		# Create an X11 Key Event for this window and set defaults
		def create_key_event
			ke = XKeyEvent.malloc
			ke.serial = 0
			ke.send_event = 1
			ke.display = @xdisplay
			ke.window = @xid
			ke.subwindow = 0
			ke.root = tree[:root]
			ke.time = Time.now.sec
			ke.state = 0
			ke.same_screen = 0
			return ke
		end

	end

end