#include <stdio.h>
#include <Xlib.h>
#include <Xutil.h>
#include <X11/keysym.h>
#include <unordered_map>
#include <vector>
#include <string>


//
// References:
//	http://stackoverflow.com/questions/18246848/get-utf-8-input-with-x11-display
//	
//
class XWindow
{
public:
	XWindow(Display* a_dpy, Window a_win) : m_dpy(a_dpy), m_win(a_win) {}
	~XWindow() { XDestroyWindow(m_dpy, m_win); }
	void Show() {
		// KeymapStateMask ??
		// ButtonReleaseMask ??
		XSelectInput(m_dpy, m_win, StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask);
		XMapWindow(m_dpy, m_win);
		//GC gc = XCreateGC(display, window, valuemask, 0);
		m_im = XOpenIM(m_dpy, NULL, NULL, NULL);
		XIMStyles *styles;	
		XGetIMValues(m_im, XNQueryInputStyle, &styles, NULL);
		m_ic = XCreateIC(m_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, m_win, NULL);
		XSetICFocus(m_ic);
	}
	void HandleKeyEvent(XEvent& a_event) {
		KeySym keysym;
		char buf[128] = {};
		int len = Xutf8LookupString(m_ic, &a_event.xkey, buf, sizeof(buf), &keysym, NULL);
		printf("got keys: %i %i %i %s\n", buf[0], buf[1], buf[2], buf);
		if (keysym == XK_Escape) {
			printf("got ESC event\n");
			XCloseDisplay(m_dpy);
		}
	}
	void HandleExposeEvent(XEvent& a_event) {
		int screen = DefaultScreen(m_dpy);
		XSetForeground(m_dpy, DefaultGC(m_dpy, screen), WhitePixel(m_dpy, screen));
		XFillRectangle(m_dpy, m_win, DefaultGC(m_dpy, screen), 0, 0, m_width, m_height);
		XSetForeground(m_dpy, DefaultGC(m_dpy, screen), BlackPixel(m_dpy, screen));
		int y_off = 20;
		XDrawString(m_dpy, m_win, DefaultGC(m_dpy, screen), 10, y_off, "test", 4);
	}
	void HandleResizeEvent(XEvent& a_event) {
        	if (m_width != a_event.xconfigure.width || m_height != a_event.xconfigure.height) {
                	m_width = a_event.xconfigure.width;
                	m_height = a_event.xconfigure.height;
                	printf("Size changed to: %d by %d\n", m_width, m_height);
		}
        }
private:
	std::vector<std::string> m_testLinesOfText;
	Display*   m_dpy;
	Window     m_win;
	XIM        m_im;
	XIC        m_ic;
	int        m_width;
	int        m_height;
};


class XApplication
{
public:
	XApplication();
	~XApplication();
	XWindow* CreateWindow(int a_x, int a_y, int a_w, int a_h);
	XEvent* NextEvent();
	bool ProcessEvents();
private:
	Display*   m_display;
	int        m_screen;
	Window     m_root;
	Visual*    m_visual;
	XEvent     m_event;
	std::unordered_map<Window,XWindow*>  m_windowMap;
};


XApplication::XApplication()
{
	m_display = XOpenDisplay(0);
	// XXX Should check m_display
	m_screen = DefaultScreen(m_display);
	m_root = RootWindow(m_display, m_screen);
	m_visual = DefaultVisual(m_display, m_screen);
}


XApplication::~XApplication()
{
	XCloseDisplay(m_display);
}


XWindow* XApplication::CreateWindow(int a_x, int a_y, int a_w, int a_h)
{
	unsigned clss = 0;
	unsigned long valuemask = 0;
	int screen = DefaultScreen(m_display);
	XSetWindowAttributes* attribs = 0;
	//Window window = XCreateSimpleWindow(m_display, m_root, 
	Window window = XCreateWindow(m_display, m_root, 
		a_x, a_y, a_w, a_h,
		//1, BlackPixel(m_display, screen), WhitePixel(m_display, screen));
		5, 24, clss, m_visual, valuemask, attribs);
	XWindow* win = new XWindow(m_display, window);
	m_windowMap.insert(std::make_pair(window, win));
	return win;
}


XEvent* XApplication::NextEvent()
{
	XNextEvent(m_display, &m_event);
	Window win = m_event.xany.window;
	while (XFilterEvent(&m_event, win)) {
		XNextEvent(m_display, &m_event);
		win = m_event.xany.window;
	}
	XWindow* w = m_windowMap[win];

	switch (m_event.type) {
		case KeymapNotify:
			// XRefreshKeyboardMapping(&ev.xmapping);
			break;
		case Expose: {
			if (w)
				w->HandleExposeEvent(m_event);
			break;
		}
		case KeyPress: {
			if (w)
				w->HandleKeyEvent(m_event);
			break;
		}
		case ClientMessage: {
			break;
		}
	        case ConfigureNotify:
			if (w)
				w->HandleResizeEvent(m_event);
            		break;
	}
	return &m_event;
}


bool XApplication::ProcessEvents()
{
	while (	NextEvent() )
		/* do nothing */;
	return true;
}


int main(int argc, char *argv[])
{
	XApplication app;
	XWindow* win = app.CreateWindow(0, 0, 640, 480);
	win->Show();
	app.ProcessEvents();
	//getchar();
	delete win;
	return 0;
}


