
#include <cstring>
#include <Spectre/System/Log.h>
#include <Spectre/Graphics/Image.h>
#include "X11WindowEventHandler.h"
#include "Xlib.h"
#include "wm_hints.h"
#include "GLXContext.h"
#include "X11Display.h"

// Sometimes not defined in headers.
#ifndef _NET_WM_STATE_TOGGLE
#define _NET_WM_STATE_TOGGLE 2
#endif

namespace sp {

namespace _priv {

	// Pointer to the display that has focus (or NULL if none have).
	X11Display* focused_display = NULL;
}

X11Display* X11Display::getFocused()
{
	return _priv::focused_display;
}

X11Display::
X11Display() :
m_screen 		(0),
m_size   		(200,200),
m_cur_last 		(0),
m_cur_hidden	(0)
{
}

bool X11Display::create(DisplayDescription description)
{
	XSetWindowAttributes attr;
	XVisualInfo* vi;
	Atom protocols;
	Visual* visual;
	Window root_win;
	::Display* disp = Xlib::getDisplay();

	m_screen = DefaultScreen(disp);
	root_win = XRootWindow(disp, m_screen);
	visual = DefaultVisual(disp, m_screen);

	attr.border_pixel = BlackPixel(disp, m_screen);
	attr.background_pixel = WhitePixel(disp, m_screen);
	//attr.override_redirect = True;
	attr.colormap = ::XCreateColormap(disp, root_win, visual, AllocNone);
	// We want InputFocus,Keyboard,Mouse,Resize,Exposure (repaint) events.
	attr.event_mask = FocusChangeMask
		| KeyPressMask | KeyReleaseMask
		| ButtonPressMask | ButtonReleaseMask | PointerMotionMask
		| StructureNotifyMask | ExposureMask;

	m_win = ::XCreateWindow(disp, root_win,
		0, 0, /* Position */
		m_size.x, m_size.y, 0 /* Border width */, DefaultDepth(disp, m_screen),
		InputOutput, visual,
		CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &attr);

	// Register event handler
	X11WindowEventHandler::registerHandler(disp, m_win, this);

	// X11 does not handle pressing the X button on a window.
	// that is the job of the window manager.
	// Here we can request the WM to send us a delete event so we can handle it ourself.
	protocols = Xlib::getAtom("WM_DELETE_WINDOW");
	XSetWMProtocols(disp, m_win, &protocols, 1);

	setVisible(true);

	setSize(description.mode.width, description.mode.height);

	setDecoration(description.decoration);

	// Clear and take focus
	XClearWindow(disp, m_win);
	XMapRaised(disp, m_win);

	Log::info("X11: Created display");

	createHiddenCursor();

	return true;
}

void X11Display::destroy()
{
	if (m_win) {
		::Display* disp = Xlib::getDisplay();
		X11WindowEventHandler::unregisterHandler(disp, m_win);
		::XDestroyWindow(disp, m_win);
	}
}

bool X11Display::isValid()
{
	return m_win;
}

void* X11Display::getHandle() const
{
	return (void*) m_win;
}

void X11Display::setSize(unsigned int width, unsigned int height)
{
	// X11 does not like if width or height is zero.
	if (width == 0) {
		width = 1;
	}

	if (height == 0) {
		height = 1;
	}

	m_size = Vector2u(width, height);
	::XResizeWindow(Xlib::getDisplay(), m_win, m_size.x, m_size.y);
}

Vector2u X11Display::getSize() const
{
	return m_size;
}

void X11Display::setPosition(unsigned int x, unsigned int y)
{
	::XMoveWindow(Xlib::getDisplay(), m_win, x, y);
}

Vector2u X11Display::getPosition() const
{
	int x, y;
	unsigned int w, h, bw, d;
	::Display* disp = Xlib::getDisplay();
	::Window ancestor = m_win;
    	::Window root     = DefaultRootWindow(disp);

	while (Xlib::getParentWindow(ancestor) != root) {
		// Next window up (parent window).
		ancestor = Xlib::getParentWindow(ancestor);
	}

	::XGetGeometry(disp, ancestor, &root, &x, &y, &w, &h, &bw, &d);

	return Vector2u(x, y);
}

void X11Display::setVisible(bool visible)
{
	if (visible) {
		::XMapWindow(Xlib::getDisplay(), m_win);
	} else {
		::XUnmapWindow(Xlib::getDisplay(), m_win);
	}
}

void X11Display::setDecoration(unsigned decoration)
{
	Atom WMHintsAtom = Xlib::getAtom("_MOTIF_WM_HINTS", false);

	Log::info("X11: Decoration");

	if (WMHintsAtom) {
		WMHints hints;
		std::memset(&hints, 0, sizeof(hints));
		hints.flags       = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
		hints.decorations = 0;
		hints.functions   = 0;

		if (decoration & DisplayDecorate::Menu) {
			Log::info("X11: Decoration Menu");
			hints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU;
                	hints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE;
		}

		if (decoration & DisplayDecorate::Resize) {
			Log::info("X11: Decoration Resize");
			hints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH;
			hints.functions |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE;
		}

		if (decoration & DisplayDecorate::Close) {
			Log::info("X11: Decoration Close");
			hints.decorations |= 0;
			hints.functions |= MWM_FUNC_CLOSE;
		}


		::XChangeProperty(Xlib::getDisplay(), m_win, WMHintsAtom, WMHintsAtom,
                            32, PropModeReplace, (const unsigned char*)&hints, WMHINTS_NUM_ELEMENTS);
	}
}

void X11Display::minimize()
{
	::XIconifyWindow(Xlib::getDisplay(), m_win, m_screen);
}

void X11Display::maximize()
{
	::XClientMessageEvent ev = {};
	::Display* disp = Xlib::getDisplay();

	ev.type = ClientMessage;
	ev.window = m_win;
	ev.message_type = Xlib::getAtom("_NET_WM_STATE");
	ev.format = 32;
	ev.data.l[0] = _NET_WM_STATE_TOGGLE;
	ev.data.l[1] = Xlib::getAtom("_NET_WM_STATE_MAXIMIZED_HORZ");
	ev.data.l[2] = Xlib::getAtom("_NET_WM_STATE_MAXIMIZED_VERT");
	ev.data.l[3] = 1;

	::XSendEvent(disp, DefaultRootWindow(disp), False,
		SubstructureNotifyMask, (XEvent*) &ev);
}

void X11Display::enterFullscreen(DisplayMode mode)
{

}

void X11Display::exitFullscreen()
{

}

void X11Display::setCaption(const std::string& caption)
{
	::XStoreName(Xlib::getDisplay(), m_win, caption.c_str());
}

void X11Display::setIcon(unsigned int width, unsigned int height, const uint8_t *pixels)
{
	::Display* disp = Xlib::getDisplay();
	::Atom net_wm_icon = Xlib::getAtom("_NET_WM_ICON", False);
	::Atom cardinal = Xlib::getAtom("CARDINAL", False);
	std::vector<uint64_t> buffer(2 + width * height);
	uint64_t *ptr = &buffer[0];

	*ptr++ = width;
	*ptr++ = height;

	// TODO: Conversion between differnet formats should be defined as functions in Graphics/PixelFormat.h
	for (std::size_t i = 0; i < width * height; i++) {
		*ptr++ =  (pixels[i * 4 + 2] << 0)
				| (pixels[i * 4 + 1] << 8)
				| (pixels[i * 4 + 0] << 16)
				| (pixels[i * 4 + 3] << 24);
	}

	::XChangeProperty(disp, m_win,
		net_wm_icon, cardinal, 32,
		PropModeReplace,
		reinterpret_cast<const unsigned char*>(&buffer[0]),
		2 + width * height);

	XFlush(disp);
}

void X11Display::createHiddenCursor()
{
	::Display* disp = Xlib::getDisplay();
	XColor c;
	Pixmap pix = ::XCreatePixmap(disp, m_win, 1, 1, 1);
	GC gc = ::XCreateGC(disp, pix, 0, NULL);

	// Draw transparent pixel.
	::XDrawPoint(disp, pix, gc, 0, 0);

	c.red = c.green = c.blue = 0;
	c.flags = DoRed | DoGreen | DoBlue;
	m_cur_hidden = XCreatePixmapCursor(disp, pix, pix, &c, &c, 0, 0);

	// Free GC and pixmap.
	::XFreePixmap(disp, pix);
	::XFreeGC(disp, gc);
}

void X11Display::showCursor(bool value)
{
	XDefineCursor(Xlib::getDisplay(), m_win, value ? m_cur_last : m_cur_hidden);
}

void X11Display::grabCursor(bool value)
{
	// TODO (this is abit harder on X11 than windows.)
}

void X11Display::processEvent(const ::XEvent& event)
{
	Vector2u size;

	switch(event.xany.type) {
	case ConfigureNotify:
		size.x = event.xconfigure.width;
		size.y = event.xconfigure.height;

		if (m_size != size) {
			m_size = size;
			Log::info("X11: Resize event");
			onReshape(size.x, size.y);
		}
		break;
	case MotionNotify :
		// Generates to much events to log :)
		break;
	case FocusIn:
		Log::debug("X11: FocusIn");
		_priv::focused_display = this;
		break;
	case FocusOut:
		Log::debug("X11: FocusOut");
		_priv::focused_display = NULL;
		break;
	}
}

} // namespace sp
