/* Kuroko bindings for Yutani */
#include <assert.h>
#include <toaru/yutani.h>
#include <toaru/decorations.h>
#include <toaru/menu.h>
#include <toaru/text.h>
#include <kuroko/kuroko.h>
#include <kuroko/vm.h>
#include <kuroko/value.h>
#include <kuroko/object.h>

static KrkInstance * module;
static KrkInstance * yctxInstance = NULL;

#define S(c) (krk_copyString(c,sizeof(c)-1))

static KrkClass * Message;
struct MessageClass {
	KrkInstance inst;
	yutani_msg_t * msg;
};

static KrkClass * Yutani;
struct YutaniClass {
	KrkInstance inst;
	yutani_t * yctx;
};

static KrkClass * GraphicsContext;
struct GraphicsContext {
	KrkInstance inst;
	gfx_context_t * ctx;
	int doubleBuffered;
};

static KrkClass * YutaniWindow;
struct WindowClass {
	KrkInstance inst;
	gfx_context_t * ctx;
	int doubleBuffered;
	yutani_window_t * window;
};

static KrkClass * YutaniSprite;
struct YutaniSprite {
	KrkInstance inst;
	gfx_context_t * ctx;
	int doubleBuffered;
	sprite_t sprite;
};

static KrkClass * YutaniColor;
struct YutaniColor {
	KrkInstance inst;
	uint32_t color;
};

static KrkClass * YutaniFont;
struct YutaniFont {
	KrkInstance inst;
	struct TT_Font * fontData;
	int fontSize;
	uint32_t fontColor;
};

static KrkClass * MenuBarClass;
struct MenuBarClass {
	KrkInstance inst;
	struct menu_bar menuBar;
};

static KrkClass * MenuListClass;
struct MenuListClass {
	KrkInstance inst;
	struct MenuList * menuList;
};

static KrkClass * MenuEntryClass;
struct MenuEntryClass {
	KrkInstance inst;
	struct MenuEntry * menuEntry;
};

static KrkClass * MenuEntrySubmenuClass;
static KrkClass * MenuEntrySeparatorClass;

/**
 * Convenience wrapper to make a class and attach it to the module, while
 * handling stack push/pop to keep things from being prematurely GC'd.
 */
KrkClass * krk_createClass(KrkInstance * inModule, const char * name, KrkClass * base) {
	if (!base) base = vm.baseClasses->objectClass;
	KrkString * str_Name = krk_copyString(name, strlen(name));
	krk_push(OBJECT_VAL(str_Name));
	KrkClass * obj_Class = krk_newClass(str_Name, base);
	krk_push(OBJECT_VAL(obj_Class));
	krk_attachNamedObject(&inModule->fields, name, (KrkObj *)obj_Class);
	krk_pop(); /* obj_Class */
	krk_pop(); /* str_Name */

	return obj_Class;
}

#define DO_FIELD(name, body) \
	{ krk_push(OBJECT_VAL(S(name))); \
		if (krk_valuesEqual(argv[1], krk_peek(0))) { \
			krk_pop(); \
			body \
		} krk_pop(); }

#define TO_INT_(name) { return INTEGER_VAL((msg-> name)); }
#define TO_INT(name) { return INTEGER_VAL((me-> name)); }
#define WID() DO_FIELD("wid", TO_INT(wid))
#define STRUCT(type) type * me = (void*)msg->data

static void _message_sweep(KrkInstance * self) {
	free(((struct MessageClass*)self)->msg);
}

static KrkValue _message_getattr(int argc, KrkValue argv[], int hasKw) {
	assert(argc == 2);
	KrkInstance * self = AS_INSTANCE(argv[0]);

	yutani_msg_t * msg = ((struct MessageClass*)self)->msg;
	if (!msg) return NONE_VAL();

	DO_FIELD("magic", TO_INT_(magic));
	DO_FIELD("type",  TO_INT_(type));
	DO_FIELD("size",  TO_INT_(size));

	switch (msg->type) {
		case YUTANI_MSG_WELCOME: {
			STRUCT(struct yutani_msg_welcome);
			DO_FIELD("display_width",  TO_INT(display_width));
			DO_FIELD("display_height", TO_INT(display_height));
		} break;
		case YUTANI_MSG_WINDOW_MOUSE_EVENT: {
			STRUCT(struct yutani_msg_window_mouse_event);
			WID();
			DO_FIELD("new_x", TO_INT(new_x));
			DO_FIELD("new_y", TO_INT(new_y));
			DO_FIELD("old_x", TO_INT(old_x));
			DO_FIELD("old_y", TO_INT(old_y));
			DO_FIELD("buttons", TO_INT(buttons));
			DO_FIELD("command", TO_INT(command));
			DO_FIELD("modifiers", TO_INT(modifiers));
		} break;
		case YUTANI_MSG_WINDOW_FOCUS_CHANGE: {
			STRUCT(struct yutani_msg_window_focus_change);
			WID();
			DO_FIELD("focused", TO_INT(focused));
		} break;
		case YUTANI_MSG_RESIZE_OFFER: {
			STRUCT(struct yutani_msg_window_resize);
			WID();
			DO_FIELD("width", TO_INT(width));
			DO_FIELD("height", TO_INT(height));
			DO_FIELD("bufid", TO_INT(bufid));
		} break;
		case YUTANI_MSG_WINDOW_ADVERTISE: {
			STRUCT(struct yutani_msg_window_advertise);
			WID();
			DO_FIELD("flags", TO_INT(flags));
			DO_FIELD("size", TO_INT(size));
			DO_FIELD("width", TO_INT(width));
			DO_FIELD("height", TO_INT(height));
			DO_FIELD("bufid", TO_INT(bufid));
			DO_FIELD("name", { char * s = me->strings; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); });
			DO_FIELD("icon", { char * s = me->strings + me->icon; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); });
		} break;
		case YUTANI_MSG_WINDOW_MOVE: {
			STRUCT(struct yutani_msg_window_move);
			WID();
			DO_FIELD("x", TO_INT(x));
			DO_FIELD("y", TO_INT(y));
		} break;
		case YUTANI_MSG_KEY_EVENT: {
			STRUCT(struct yutani_msg_key_event);
			WID();

			DO_FIELD("keycode", TO_INT(event.keycode));
			DO_FIELD("modifiers", TO_INT(event.modifiers));
			DO_FIELD("action", TO_INT(event.action));
			DO_FIELD("key", TO_INT(event.key));

			DO_FIELD("kbd_state", TO_INT(state.kbd_state));
			DO_FIELD("kbd_s_state", TO_INT(state.kbd_s_state));
			DO_FIELD("k_ctrl", TO_INT(state.k_ctrl));
			DO_FIELD("k_shift", TO_INT(state.k_shift));
			DO_FIELD("k_alt", TO_INT(state.k_alt));
			DO_FIELD("k_super", TO_INT(state.k_super));
			DO_FIELD("kl_ctrl", TO_INT(state.kl_ctrl));
			DO_FIELD("kl_shift", TO_INT(state.kl_shift));
			DO_FIELD("kl_alt", TO_INT(state.kl_alt));
			DO_FIELD("kl_super", TO_INT(state.kl_super));
			DO_FIELD("kr_ctrl", TO_INT(state.kr_ctrl));
			DO_FIELD("kr_shift", TO_INT(state.kr_shift));
			DO_FIELD("kr_alt", TO_INT(state.kr_alt));
			DO_FIELD("kr_super", TO_INT(state.kr_super));
			DO_FIELD("kbd_esc_buf", TO_INT(state.kbd_esc_buf));
		} break;
	}

	krk_runtimeError(vm.exceptions->attributeError, "no attribute '%s'", AS_CSTRING(argv[1]));
	return NONE_VAL();
}
#undef DO_FIELD
#undef TO_INT_
#undef GET
#undef TO_INT
#undef WID

static KrkValue _yutani_repr(int argc, KrkValue argv[], int hasKw) {
	struct YutaniClass * self = (struct YutaniClass*)AS_INSTANCE(argv[0]);
	char out[500];
	size_t len = sprintf(out, "Yutani(fd=%d,server=%s,display_width=%d,display_height=%d)",
		fileno(self->yctx->sock),
		self->yctx->server_ident,
		(int)self->yctx->display_width,
		(int)self->yctx->display_height);
	return OBJECT_VAL(krk_copyString(out,len));
}

static KrkValue _yutani_init(int argc, KrkValue argv[], int hasKw) {
	if (yctxInstance) {
		krk_runtimeError(vm.exceptions->valueError, "class 'Yutani' is a singleton and has already been initialized.");
		return NONE_VAL();
	}

	KrkInstance * self = AS_INSTANCE(argv[0]);

	/* Connect and let's go. */
	yutani_t * yctx = yutani_init();
	if (!yctx) {
		krk_runtimeError(vm.exceptions->ioError, "Failed to connect to compositor.");
		return NONE_VAL();
	}

	init_decorations();

	((struct YutaniClass*)self)->yctx = yctx;
	yctxInstance = self;
	krk_attachNamedObject(&module->fields, "_yutani_t", (KrkObj*)self);

	return argv[0];
}

#define CHECK_YUTANI() \
	if (argc < 1 || !krk_isInstanceOf(argv[0], Yutani)) \
		return krk_runtimeError(vm.exceptions->typeError, "expected Yutani"); \
	struct YutaniClass * self = (struct YutaniClass*)AS_INSTANCE(argv[0])

static KrkValue _yutani_display_width(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	return INTEGER_VAL(self->yctx->display_width);
}

static KrkValue _yutani_display_height(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	return INTEGER_VAL(self->yctx->display_height);
}

static KrkValue _yutani_poll(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();

	int sync = (argc > 1 && IS_BOOLEAN(argv[1])) ? AS_BOOLEAN(argv[1]) : 1;
	yutani_msg_t * result;
	if (sync) {
		result = yutani_poll(self->yctx);
	} else {
		result = yutani_poll_async(self->yctx);
	}

	if (!result) return NONE_VAL();

	KrkInstance * out = krk_newInstance(Message);
	krk_push(OBJECT_VAL(out));
	((struct MessageClass*)out)->msg = result;
	krk_attachNamedValue(&out->fields, "type", INTEGER_VAL(result->type));

	return krk_pop();
}

static KrkValue _yutani_wait_for(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	if (argc != 2 || !IS_INTEGER(argv[1])) { krk_runtimeError(vm.exceptions->argumentError, "expected int for msgtype"); return NONE_VAL(); }
	yutani_msg_t * result = yutani_wait_for(self->yctx, AS_INTEGER(argv[1]));
	KrkInstance * out = krk_newInstance(Message);
	krk_push(OBJECT_VAL(out));
	((struct MessageClass*)out)->msg = result;
	krk_attachNamedValue(&out->fields, "type", INTEGER_VAL(result->type));

	return krk_pop();
}

static KrkValue _yutani_subscribe(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	yutani_subscribe_windows(self->yctx);
	return NONE_VAL();
}

static KrkValue _yutani_unsubscribe(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	yutani_unsubscribe_windows(self->yctx);
	return NONE_VAL();
}

static KrkValue _yutani_query_windows(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	yutani_query_windows(self->yctx);
	return NONE_VAL();
}

static KrkValue _yutani_fileno(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	return INTEGER_VAL(fileno(self->yctx->sock));
}

static KrkValue _yutani_query(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	return INTEGER_VAL(yutani_query(self->yctx));
}

static KrkValue _yutani_menu_process_event(int argc, KrkValue argv[], int hasKw) {
	CHECK_YUTANI();
	if (argc < 2 || !krk_isInstanceOf(argv[1],Message))
		return krk_runtimeError(vm.exceptions->typeError, "expected Message");
	struct MessageClass* msg = (struct MessageClass*)AS_INSTANCE(argv[1]);

	return INTEGER_VAL(menu_process_event(self->yctx, msg->msg));
}

#define GET_ARG(p,name,type) do { \
	if (hasKw && krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S(#name)), &name)) { \
		if (!krk_isInstanceOf(name,type)) \
			return krk_runtimeError(vm.exceptions->typeError, #name " argument should be " #type ", not '%s'", krk_typeName(name)); \
	} else if (argc > p) { \
		name = argv[p]; \
		if (!krk_isInstanceOf(name,type)) \
			return krk_runtimeError(vm.exceptions->typeError, #name " argument should be " #type ", not '%s'", krk_typeName(name)); \
	} \
} while (0)

#define GFX_PROPERTY(name) \
static KrkValue _gfx_ ## name (int argc, KrkValue argv[], int hasKw) { \
	if (argc != 1 || !krk_isInstanceOf(argv[0], GraphicsContext)) \
		return krk_runtimeError(vm.exceptions->typeError, "Expected GraphicsContext"); \
	struct GraphicsContext * self = (struct GraphicsContext *)AS_INSTANCE(argv[0]); \
	return INTEGER_VAL(self->ctx-> name); \
}

GFX_PROPERTY(width);
GFX_PROPERTY(height);

#define CHECK_GFX() \
	if (argc < 1 || !krk_isInstanceOf(argv[0], GraphicsContext)) \
		return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext"); \
	struct GraphicsContext * self = (struct GraphicsContext*)AS_INSTANCE(argv[0])

static KrkValue _gfx_fill(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();
	if (argc < 2 || !krk_isInstanceOf(argv[1], YutaniColor))
		return krk_runtimeError(vm.exceptions->typeError, "fill() takes one color() argument");
	struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[1]);
	draw_fill(self->ctx, color->color);
	return NONE_VAL();
}

static KrkValue _gfx_flip(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();
	if (self->doubleBuffered) {
		flip(self->ctx);
	}
	return NONE_VAL();
}

static KrkValue _gfx_blur(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();
	int radius = 2;
	if (argc > 1 && IS_INTEGER(argv[1])) radius = AS_INTEGER(argv[1]);
	else if (argc > 1) return krk_runtimeError(vm.exceptions->typeError, "expected int");
	blur_context_box(self->ctx, radius);
	return NONE_VAL();
}

static KrkValue _gfx_line(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();
	if (argc < 6 ||
		!IS_INTEGER(argv[1]) ||
		!IS_INTEGER(argv[2]) ||
		!IS_INTEGER(argv[3]) ||
		!IS_INTEGER(argv[4]) ||
		!krk_isInstanceOf(argv[5], YutaniColor)) {
		return krk_runtimeError(vm.exceptions->typeError, "line() expects 4 ints and a color");
	}

	int32_t x0 = AS_INTEGER(argv[1]);
	int32_t x1 = AS_INTEGER(argv[2]);
	int32_t y0 = AS_INTEGER(argv[3]);
	int32_t y1 = AS_INTEGER(argv[4]);
	struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[5]);

	if (argc > 6) {
		if (IS_INTEGER(argv[6])) {
			draw_line_thick(self->ctx,x0,x1,y0,y1,color->color,AS_INTEGER(argv[6]));
		} else if (IS_FLOATING(argv[6])) {
			draw_line_aa(self->ctx,x0,x1,y0,y1,color->color,AS_FLOATING(argv[6]));
		} else {
			return krk_runtimeError(vm.exceptions->typeError, "thickness must be int or float, not '%s'", krk_typeName(argv[6]));
		}
	} else {
		draw_line(self->ctx,x0,x1,y0,y1,color->color);
	}

	return NONE_VAL();
}

static KrkValue _gfx_rect(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();

	if (argc != 6 ||
		!IS_INTEGER(argv[1]) ||
		!IS_INTEGER(argv[2]) ||
		!IS_INTEGER(argv[3]) ||
		!IS_INTEGER(argv[4]) ||
		!krk_isInstanceOf(argv[5], YutaniColor)) {
		return krk_runtimeError(vm.exceptions->typeError, "rect() expects 4 ints and a color");
	}

	int32_t x = AS_INTEGER(argv[1]);
	int32_t y = AS_INTEGER(argv[2]);
	uint16_t width = AS_INTEGER(argv[3]);
	uint16_t height = AS_INTEGER(argv[4]);
	struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[5]);

	KrkValue solid = BOOLEAN_VAL(0), radius = NONE_VAL();
	if (hasKw) {
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("solid")), &solid);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("radius")), &radius);
	}

	if (!IS_BOOLEAN(solid))
		return krk_runtimeError(vm.exceptions->typeError, "solid must be bool");
	if (!IS_NONE(radius) && !IS_INTEGER(radius))
		return krk_runtimeError(vm.exceptions->typeError, "radius must be int");
	if (!IS_NONE(radius) && AS_BOOLEAN(solid))
		return krk_runtimeError(vm.exceptions->typeError, "radius and solid can not be used together");

	if (AS_BOOLEAN(solid)) {
		draw_rectangle_solid(self->ctx, x, y, width, height, color->color);
	} else if (IS_INTEGER(radius)) {
		draw_rounded_rectangle(self->ctx, x, y, width, height, AS_INTEGER(radius), color->color);
	} else {
		draw_rectangle(self->ctx, x, y, width, height, color->color);
	}

	return NONE_VAL();
}

static KrkValue _gfx_draw_sprite(int argc, KrkValue argv[], int hasKw) {
	CHECK_GFX();

	if (argc < 2 || !krk_isInstanceOf(argv[1], YutaniSprite))
		return krk_runtimeError(vm.exceptions->typeError, "expected Sprite");

	if (argc < 4 || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3]))
		return krk_runtimeError(vm.exceptions->typeError, "expected integer coordinate pair");

	/* Potential kwargs: rotation:float, alpha:float, scale:(int,int)... */
	KrkValue rotation = NONE_VAL(), alpha = NONE_VAL(), scale=NONE_VAL(), color=NONE_VAL();
	if (hasKw) {
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("alpha")), &alpha);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("rotation")), &rotation);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("scale")), &scale);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("color")), &color);
	}

	if (!IS_NONE(alpha) && !IS_FLOATING(alpha))
		return krk_runtimeError(vm.exceptions->typeError, "alpha must be float");
	if (!IS_NONE(rotation) && !IS_FLOATING(rotation))
		return krk_runtimeError(vm.exceptions->typeError, "rotation must be float");
	if (!IS_NONE(color) && !krk_isInstanceOf(color,YutaniColor))
		return krk_runtimeError(vm.exceptions->typeError, "color must be color");
	if (!IS_NONE(scale) && (!IS_TUPLE(scale) || AS_TUPLE(scale)->values.count != 2 ||
		!IS_INTEGER(AS_TUPLE(scale)->values.values[0]) ||
		!IS_INTEGER(AS_TUPLE(scale)->values.values[1])))
		return krk_runtimeError(vm.exceptions->typeError, "scale must be 2-tuple of ints");
	if (!IS_NONE(rotation) + !IS_NONE(scale) + !IS_NONE(color) > 1)
		return krk_runtimeError(vm.exceptions->typeError, "can not combine rotation / scale / color");

	if ((!IS_NONE(rotation) || !IS_NONE(color)) && IS_NONE(alpha))
		alpha = FLOATING_VAL(1.0);

	struct YutaniSprite * sprite = (struct YutaniSprite*)AS_INSTANCE(argv[1]);
	int32_t x = AS_INTEGER(argv[2]);
	int32_t y = AS_INTEGER(argv[3]);

	if (!IS_NONE(scale)) {
		int32_t width = AS_INTEGER(AS_TUPLE(scale)->values.values[0]);
		int32_t height = AS_INTEGER(AS_TUPLE(scale)->values.values[1]);
		if (IS_NONE(alpha)) {
			draw_sprite_scaled(self->ctx, &sprite->sprite, x, y, width, height);
		} else {
			draw_sprite_scaled_alpha(self->ctx, &sprite->sprite, x, y, width, height, AS_FLOATING(alpha));
		}
	} else if (IS_NONE(alpha)) {
		draw_sprite(self->ctx, &sprite->sprite, x, y);
	} else if (!IS_NONE(color)) {
		draw_sprite_alpha_paint(self->ctx, &sprite->sprite, x, y, AS_FLOATING(alpha), ((struct YutaniColor*)AS_INSTANCE(color))->color);
	} else if (!IS_NONE(rotation)) {
		draw_sprite_rotate(self->ctx, &sprite->sprite, x, y, AS_FLOATING(rotation), AS_FLOATING(alpha));
	} else {
		draw_sprite_alpha(self->ctx, &sprite->sprite, x, y, AS_FLOATING(alpha));
	}

	return NONE_VAL();
}

static void _sprite_sweep(KrkInstance * self) {
	struct YutaniSprite * sprite = (struct YutaniSprite*)self;

	if (sprite->sprite.masks) free(sprite->sprite.masks);
	if (sprite->sprite.bitmap) free(sprite->sprite.bitmap);
	if (sprite->ctx) free(sprite->ctx);
}

static KrkValue _sprite_repr(int argc, KrkValue argv[], int hasKw) {
	struct YutaniSprite * self = (struct YutaniSprite *)AS_INSTANCE(argv[0]);

	KrkValue file;
	krk_tableGet(&self->inst.fields, OBJECT_VAL(S("file")), &file);

	char out[500];
	size_t len = sprintf(out, "Sprite('%s',width=%d,height=%d)",
		!IS_STRING(file) ? "" : AS_CSTRING(file),
		(int)self->sprite.width,
		(int)self->sprite.height);
	return OBJECT_VAL(krk_copyString(out,len));
}

static KrkValue _sprite_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniSprite))
		return krk_runtimeError(vm.exceptions->typeError, "expected sprite");

	if (argc < 2 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "Sprite() takes one str argument");

	struct YutaniSprite * self = (struct YutaniSprite*)AS_INSTANCE(argv[0]);

	int result = load_sprite(&self->sprite, AS_CSTRING(argv[1]));
	if (result) {
		return krk_runtimeError(vm.exceptions->ioError, "Sprite() could not be initialized");
	}

	self->ctx = init_graphics_sprite(&self->sprite);
	krk_attachNamedValue(&self->inst.fields, "file", argv[1]);

	return argv[0];
}

#define CHECK_WINDOW() \
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) \
		return krk_runtimeError(vm.exceptions->typeError, "expected Window"); \
	struct WindowClass * self = (struct WindowClass*)AS_INSTANCE(argv[0]); \
	if (!self->window) return krk_runtimeError(vm.exceptions->valueError, "Window is closed")

static KrkValue _window_repr(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	KrkValue title;
	krk_tableGet(&self->inst.fields, OBJECT_VAL(S("title")), &title);
	char out[500];
	size_t len = sprintf(out, "Window(wid=%d,title=%s,width=%d,height=%d)",
		self->window->wid,
		IS_NONE(title) ? "" : AS_CSTRING(title),
		(int)self->window->width,
		(int)self->window->height);
	return OBJECT_VAL(krk_copyString(out,len));
}

static KrkValue _window_init(int argc, KrkValue argv[], int hasKw) {
	if (!yctxInstance) return krk_runtimeError(vm.exceptions->valueError, "Compositor is not initialized");
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow))
		return krk_runtimeError(vm.exceptions->typeError, "Failed to initialize window");

	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->argumentError, "Expected at least two (integer) arguments (width, height)");

	KrkInstance * _self = AS_INSTANCE(argv[0]);
	struct WindowClass * self = (struct WindowClass*)_self;
	krk_integer_type width = AS_INTEGER(argv[1]);
	krk_integer_type height = AS_INTEGER(argv[2]);

	KrkValue flags = INTEGER_VAL(0), title = NONE_VAL(), icon = NONE_VAL(), doublebuffer = BOOLEAN_VAL(0);
	GET_ARG(3, flags, vm.baseClasses->intClass);
	GET_ARG(4, title, vm.baseClasses->strClass);
	GET_ARG(5, icon,  vm.baseClasses->strClass);
	GET_ARG(6, doublebuffer, vm.baseClasses->boolClass);

	self->window = yutani_window_create_flags(((struct YutaniClass*)yctxInstance)->yctx,
		width, height, AS_INTEGER(flags));

	self->doubleBuffered = AS_BOOLEAN(doublebuffer);

	if (self->doubleBuffered) {
		self->ctx = init_graphics_yutani_double_buffer(self->window);
	} else {
		self->ctx = init_graphics_yutani(self->window);
	}

	if (!IS_NONE(title)) {
		if (!IS_NONE(icon)) {
			yutani_window_advertise_icon(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(title), AS_CSTRING(icon));
		} else {
			yutani_window_advertise(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(title));
		}
	}

	krk_attachNamedValue(&_self->fields, "title", title);
	krk_attachNamedValue(&_self->fields, "icon", icon);
	krk_attachNamedValue(&_self->fields, "closed", BOOLEAN_VAL(0));

	return argv[0];
}

static KrkValue _window_flip(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (self->doubleBuffered) {
		flip(self->ctx);
	}
	yutani_flip(((struct YutaniClass*)yctxInstance)->yctx, self->window);
	return NONE_VAL();
}

static KrkValue _window_move(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected two integer arguments");
	yutani_window_move(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_INTEGER(argv[1]), AS_INTEGER(argv[2]));
	return NONE_VAL();
}

static KrkValue _window_set_focused(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected integer argument");
	self->window->focused = AS_INTEGER(argv[1]);
	return NONE_VAL();
}

static KrkValue _window_close(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	yutani_close(((struct YutaniClass*)yctxInstance)->yctx, self->window);
	self->window = NULL;
	release_graphics_yutani(self->ctx);
	self->ctx = NULL;
	return NONE_VAL();
}

static KrkValue _window_set_stack(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for z-order");
	int z = AS_INTEGER(argv[1]);
	yutani_set_stack(((struct YutaniClass*)yctxInstance)->yctx, self->window, z);
	return NONE_VAL();
}

static KrkValue _window_update_shape(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for shape specifier");
	int set_shape = AS_INTEGER(argv[1]);
	yutani_window_update_shape(((struct YutaniClass*)yctxInstance)->yctx, self->window, set_shape);
	return NONE_VAL();
}

static KrkValue _window_warp_mouse(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected two int values for x, y");
	int32_t x = AS_INTEGER(argv[1]);
	int32_t y = AS_INTEGER(argv[2]);
	yutani_window_warp_mouse(((struct YutaniClass*)yctxInstance)->yctx, self->window, x, y);
	return NONE_VAL();
}

static KrkValue _window_show_mouse(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for show_mouse");
	int show_mouse = AS_INTEGER(argv[1]);
	yutani_window_show_mouse(((struct YutaniClass*)yctxInstance)->yctx, self->window, show_mouse);
	return NONE_VAL();
}

static KrkValue _window_resize_start(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for direction");
	yutani_scale_direction_t direction = AS_INTEGER(argv[1]);
	yutani_window_resize_start(((struct YutaniClass*)yctxInstance)->yctx, self->window, direction);
	return NONE_VAL();
}

static KrkValue _window_resize(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height");
	uint32_t width = AS_INTEGER(argv[1]);
	uint32_t height = AS_INTEGER(argv[2]);
	yutani_window_resize(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height);
	return NONE_VAL();
}

static KrkValue _window_resize_offer(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height");
	uint32_t width = AS_INTEGER(argv[1]);
	uint32_t height = AS_INTEGER(argv[2]);
	yutani_window_resize_offer(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height);
	return NONE_VAL();
}

static KrkValue _window_resize_accept(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height");
	uint32_t width = AS_INTEGER(argv[1]);
	uint32_t height = AS_INTEGER(argv[2]);
	yutani_window_resize_accept(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height);
	return NONE_VAL();
}

static KrkValue _window_resize_done(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	yutani_window_resize_done(((struct YutaniClass*)yctxInstance)->yctx, self->window);
	return NONE_VAL();
}

static KrkValue _window_advertise(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected string for title");
	if (argc > 2 && !IS_STRING(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected string for icon");

	if (argc > 2) {
		yutani_window_advertise_icon(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(argv[1]), AS_CSTRING(argv[2]));
	} else {
		yutani_window_advertise(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(argv[1]));
	}
	return NONE_VAL();
}

static KrkValue _window_special_request(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for request");
	uint32_t request = AS_INTEGER(argv[1]);
	yutani_special_request(((struct YutaniClass*)yctxInstance)->yctx, self->window, request);
	return NONE_VAL();
}

static KrkValue _window_reinit(int argc, KrkValue argv[], int hasKw) {
	CHECK_WINDOW();
	reinit_graphics_yutani(self->ctx, self->window);
	return NONE_VAL();
}

#define WINDOW_PROPERTY(name) \
static KrkValue _window_ ## name (int argc, KrkValue argv[], int hasKw) { \
	if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) \
		return krk_runtimeError(vm.exceptions->typeError, "Expected Window"); \
	struct WindowClass * self = (struct WindowClass*)AS_INSTANCE(argv[0]); \
	return INTEGER_VAL(self->window-> name); \
}

WINDOW_PROPERTY(wid);
WINDOW_PROPERTY(x);
WINDOW_PROPERTY(y);
WINDOW_PROPERTY(focused);

static KrkValue _decor_get_bounds(int argc, KrkValue argv[], int hasKw) {
	if (argc > 0 && !krk_isInstanceOf(argv[0], YutaniWindow))
		return krk_runtimeError(vm.exceptions->typeError, "expected window");
	struct decor_bounds bounds;

	decor_get_bounds((argc > 0) ? ((struct WindowClass*)AS_INSTANCE(argv[0]))->window : NULL,
		&bounds);

	KrkValue result = krk_dict_of(0, NULL, 0);
	krk_push(result);

#define SET(val) krk_attachNamedValue(AS_DICT(result), #val, INTEGER_VAL(bounds. val));

	SET(top_height);
	SET(bottom_height);
	SET(left_width);
	SET(right_width);
	SET(width);
	SET(height);

	return krk_pop();
}

static KrkValue _decor_handle_event(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], Message))
		return krk_runtimeError(vm.exceptions->typeError, "expected message");
	return INTEGER_VAL(decor_handle_event(((struct YutaniClass*)yctxInstance)->yctx, ((struct MessageClass*)AS_INSTANCE(argv[0]))->msg));
}

static KrkValue _decor_render(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow))
		return krk_runtimeError(vm.exceptions->typeError, "expected window");
	char * title = (argc > 1 && IS_STRING(argv[1])) ? AS_CSTRING(argv[1]) : NULL;
	if (title == NULL) {
		KrkValue winTitle;
		if (!krk_tableGet(&AS_INSTANCE(argv[0])->fields, OBJECT_VAL(S("title")), &winTitle) || !IS_STRING(winTitle)) {
			title = "";
		} else {
			title = AS_CSTRING(winTitle);
		}
	}
	render_decorations(((struct WindowClass*)AS_INSTANCE(argv[0]))->window,
		((struct WindowClass*)AS_INSTANCE(argv[0]))->ctx, title);
	return NONE_VAL();
}

static KrkValue _decor_show_default_menu(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow))
		return krk_runtimeError(vm.exceptions->typeError, "show_default_menu() expects Window");
	if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "show_default_menu() expects int coordinate pair");

	struct WindowClass * window = (void*)AS_INSTANCE(argv[0]);
	int32_t x = AS_INTEGER(argv[1]);
	int32_t y = AS_INTEGER(argv[2]);

	decor_show_default_menu(window->window, x, y);
	return NONE_VAL();
}

static KrkValue _yutani_color_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 4 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3]) ||
	   (argc > 5) || (argc == 5 && !IS_INTEGER(argv[4]))) return krk_runtimeError(vm.exceptions->typeError, "color() expects three or four integer arguments");
	if (!krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__init__], not '%s'", krk_typeName(argv[0]));
	struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]);
	if (argc == 5) {
		self->color = rgba(AS_INTEGER(argv[1]),AS_INTEGER(argv[2]),AS_INTEGER(argv[3]),AS_INTEGER(argv[4]));
	} else {
		self->color = rgb(AS_INTEGER(argv[1]),AS_INTEGER(argv[2]),AS_INTEGER(argv[3]));
	}
	return argv[0];
}

static KrkValue _yutani_color_repr(int argc, KrkValue argv[], int hasKw) {
	if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__repr__], not '%s'", krk_typeName(argv[0]));
	struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]);
	char tmp[30];
	if (_ALP(self->color) != 255) {
		sprintf(tmp, "color<#%02x%02x%02x%02x>", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color), (int)_ALP(self->color));
	} else {
		sprintf(tmp, "color<#%02x%02x%02x>", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color));
	}
	return OBJECT_VAL(krk_copyString(tmp,strlen(tmp)));
}

static KrkValue _yutani_color_str(int argc, KrkValue argv[], int hasKw) {
	if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__str__], not '%s'", krk_typeName(argv[0]));
	struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]);
	char tmp[30];
	if (_ALP(self->color) != 255) {
		sprintf(tmp, "#%02x%02x%02x%02x", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color), (int)_ALP(self->color));
	} else {
		sprintf(tmp, "#%02x%02x%02x", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color));
	}
	return OBJECT_VAL(krk_copyString(tmp,strlen(tmp)));
}

#define CHECK_FONT() \
	if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniFont)) \
		return krk_runtimeError(vm.exceptions->typeError, "expected Font"); \
	struct YutaniFont * self = (struct YutaniFont*)AS_INSTANCE(argv[0])

static KrkValue _font_init(int argc, KrkValue argv[], int hasKw) {
	CHECK_FONT();

	if (argc < 2 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected string for font name");
	if (argc < 3 || !IS_INTEGER(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected int for font size");

	KrkValue fontColor = NONE_VAL();
	if (hasKw) {
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("color")), &fontColor);
		if (!krk_isInstanceOf(fontColor, YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color");
	}

	/* Try to be smart about this */
	if (strstr(AS_CSTRING(argv[1]), "sans-serif") == AS_CSTRING(argv[1]) || strstr(AS_CSTRING(argv[1]), "monospace") == AS_CSTRING(argv[1])) {
		self->fontData = tt_font_from_shm(AS_CSTRING(argv[1]));
	} else {
		self->fontData = tt_font_from_file(AS_CSTRING(argv[1]));
	}

	if (!self->fontData)
		return krk_runtimeError(vm.exceptions->typeError, "failed to load font");

	self->fontSize = AS_INTEGER(argv[2]);
	tt_set_size(self->fontData, self->fontSize);

	self->fontColor = IS_NONE(fontColor) ? rgb(0,0,0) : ((struct YutaniColor*)AS_INSTANCE(fontColor))->color;

	return argv[0];
}

static KrkValue _font_size(int argc, KrkValue argv[], int hasKw) {
	CHECK_FONT();
	return INTEGER_VAL(self->fontSize);
}

static KrkValue _font_set_size(int argc, KrkValue argv[], int hasKw) {
	CHECK_FONT();
	if (argc < 2 || !IS_INTEGER(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected int for font size");
	self->fontSize = AS_INTEGER(argv[1]);
	tt_set_size(self->fontData, self->fontSize);
	return INTEGER_VAL(self->fontSize);
}

static KrkValue _font_draw_string(int argc, KrkValue argv[], int hasKw) {
	CHECK_FONT();
	if (argc < 2 || !krk_isInstanceOf(argv[1], GraphicsContext))
		return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext");
	if (argc < 3 || !IS_STRING(argv[2]))
		return krk_runtimeError(vm.exceptions->typeError, "expected str");
	if (argc < 5 || !IS_INTEGER(argv[3]) || !IS_INTEGER(argv[4]))
		return krk_runtimeError(vm.exceptions->typeError, "expected int coordinate pair");

	gfx_context_t * ctx = ((struct GraphicsContext*)AS_INSTANCE(argv[1]))->ctx;
	const char * str = AS_CSTRING(argv[2]);
	int32_t x = AS_INTEGER(argv[3]);
	int32_t y = AS_INTEGER(argv[4]);

	return INTEGER_VAL(tt_draw_string(ctx, self->fontData, x, y, str, self->fontColor));
}

static KrkValue _font_width(int argc, KrkValue argv[], int hasKw) {
	CHECK_FONT();
	if (argc < 2 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected str");

	const char * str = AS_CSTRING(argv[1]);
	return INTEGER_VAL(tt_string_width(self->fontData, str));
}

static void _MenuBar_gcsweep(KrkInstance * _self) {
	struct MenuBarClass * self = (struct MenuBarClass*)_self;
	if (self->menuBar.entries) {
		for (size_t i = 0; self->menuBar.entries[i].title; ++i) {
			free(self->menuBar.entries[i].title);
			free(self->menuBar.entries[i].action);
		}
		free(self->menuBar.entries);
	}
}

static void _menubar_callback(struct menu_bar * _self) {
	struct MenuBarClass * self = _self->_private;
	KrkValue callback;
	if (krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback)) {
		krk_push(callback);
		krk_push(OBJECT_VAL(self));
		krk_callStack(1);
	}
}

static KrkValue _MenuBar_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar");
	if (argc < 2 || !IS_TUPLE(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected tuple of tuples");

	struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]);
	self->menuBar.entries = malloc(sizeof(struct menu_bar_entries) * (AS_TUPLE(argv[1])->values.count + 1));
	for (size_t i = 0; i < AS_TUPLE(argv[1])->values.count; ++i) {
		if (!IS_TUPLE(AS_TUPLE(argv[1])->values.values[i]) ||
			AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.count != 2 ||
			!IS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[0]) || 
			!IS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[1])) {
			return krk_runtimeError(vm.exceptions->typeError, "invalid menu bar entry: expected (str,str) but %d is '%s'",
				(int)i, krk_typeName(AS_TUPLE(argv[1])->values.values[i]));
		}

		KrkString * title = AS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[0]);
		KrkString * action = AS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[1]);

		self->menuBar.entries[i].title = strdup(title->chars);
		self->menuBar.entries[i].action = strdup(action->chars);
	}
	self->menuBar.entries[AS_TUPLE(argv[1])->values.count].title = NULL;
	self->menuBar.entries[AS_TUPLE(argv[1])->values.count].action = NULL;

	self->menuBar.set = menu_set_create();
	self->menuBar._private = self;
	self->menuBar.redraw_callback = _menubar_callback;

	krk_attachNamedValue(&self->inst.fields, "entries", argv[1]);

	/* Give ourselves a dict to track the same information */
	KrkValue dict = krk_dict_of(0,NULL, 0);
	krk_attachNamedValue(&self->inst.fields, "set", dict);

	return argv[0];
}

static KrkValue _MenuBar_place(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar");
	struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]);
	if (argc < 4 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3]))
		return krk_runtimeError(vm.exceptions->typeError, "expected int for x, y, width");
	if (argc < 5 || !krk_isInstanceOf(argv[4],YutaniWindow))
		return krk_runtimeError(vm.exceptions->typeError, "expected Window");

	self->menuBar.x = AS_INTEGER(argv[1]);
	self->menuBar.y = AS_INTEGER(argv[2]);
	self->menuBar.width = AS_INTEGER(argv[3]);
	self->menuBar.window = ((struct WindowClass*)AS_INSTANCE(argv[4]))->window;
	return NONE_VAL();
}

static KrkValue _MenuBar_render(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar");
	struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]);
	if (argc < 2 || !krk_isInstanceOf(argv[1], GraphicsContext))
		return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext");
	menu_bar_render(&self->menuBar, ((struct GraphicsContext*)AS_INSTANCE(argv[1]))->ctx);
	return NONE_VAL();
}

static KrkValue _MenuBar_mouse_event(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar");
	struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]);
	if (argc < 3 || !krk_isInstanceOf(argv[1], YutaniWindow) ||
		!krk_isInstanceOf(argv[2], Message))
		return krk_runtimeError(vm.exceptions->typeError, "expected Window and Message");

	struct MessageClass * msg = ((struct MessageClass*)AS_INSTANCE(argv[2]));
	struct yutani_msg_window_mouse_event * me = (struct yutani_msg_window_mouse_event*)msg->msg->data;

	return INTEGER_VAL(menu_bar_mouse_event(((struct YutaniClass*)yctxInstance)->yctx,
		((struct WindowClass*)AS_INSTANCE(argv[1]))->window,
		&self->menuBar, me, me->new_x, me->new_y));
}

static KrkValue _MenuBar_insert(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar");
	struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]);
	if (argc < 3 || !IS_STRING(argv[1]) || !krk_isInstanceOf(argv[2], MenuListClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected str and MenuList");

	menu_set_insert(self->menuBar.set, AS_CSTRING(argv[1]), ((struct MenuListClass*)AS_INSTANCE(argv[2]))->menuList);

	/* Also assign it to our dict */
	KrkValue dict = NONE_VAL();
	krk_tableGet(&self->inst.fields, OBJECT_VAL(S("set")), &dict);
	if (IS_NONE(dict) || !krk_isInstanceOf(dict,vm.baseClasses->dictClass))
		return krk_runtimeError(vm.exceptions->baseException, "Failed to get set entries?");
	krk_tableSet(AS_DICT(dict), argv[1], argv[2]);

	return NONE_VAL();
}

static KrkValue _MenuList_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuListClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuList");
	struct MenuListClass * self = (struct MenuListClass*)AS_INSTANCE(argv[0]);
	self->menuList = menu_create();

	/* Give us a list to put entries in for GC tracking and retrieval by kuroko code */
	KrkValue list = krk_list_of(0,NULL,0);
	krk_attachNamedValue(&self->inst.fields, "entries", list);

	return argv[0];
}

static KrkValue _MenuList_insert(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuListClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuList");
	struct MenuListClass * self = (struct MenuListClass*)AS_INSTANCE(argv[0]);
	if (argc < 2 || !krk_isInstanceOf(argv[1], MenuEntryClass))
		return krk_runtimeError(vm.exceptions->typeError, "Expected MenuEntry");

	/* Append to menu */
	menu_insert(self->menuList, ((struct MenuEntryClass*)AS_INSTANCE(argv[1]))->menuEntry);

	/* Append to internal list */
	KrkValue list = NONE_VAL();
	krk_tableGet(&self->inst.fields, OBJECT_VAL(S("entries")), &list);
	if (IS_NONE(list) || !krk_isInstanceOf(list,vm.baseClasses->listClass))
		return krk_runtimeError(vm.exceptions->baseException, "Failed to get entries?");
	krk_writeValueArray(AS_LIST(list), argv[1]);

	return NONE_VAL();
}

static void _MenuEntry_callback_internal(struct MenuEntry * _self) {
	struct MenuEntryClass * self = (struct MenuEntryClass *)_self->_private;
	KrkValue callback = NONE_VAL();
	krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback);
	krk_push(callback);
	krk_push(OBJECT_VAL(self));
	krk_callStack(1);
}

static KrkValue _MenuEntry_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntryClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntry");
	struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]);

	if (argc < 3 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected title and callback");

	KrkValue icon = NONE_VAL(), action = NONE_VAL();
	if (hasKw) {
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("icon")), &icon);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("action")), &action);
		if (!IS_NONE(icon) && !IS_STRING(icon))
			return krk_runtimeError(vm.exceptions->typeError, "icon must be str, not '%s'", krk_typeName(icon));
		if (!IS_NONE(action) && !IS_STRING(action))
			return krk_runtimeError(vm.exceptions->typeError, "action must be str, not '%s'", krk_typeName(action));
	}

	self->menuEntry = menu_create_normal(
		IS_STRING(icon) ? AS_CSTRING(icon) : NULL,
		IS_STRING(action) ? AS_CSTRING(action) : NULL,
		AS_CSTRING(argv[1]),
		_MenuEntry_callback_internal);

	self->menuEntry->_private = self;

	krk_attachNamedValue(&self->inst.fields, "callback", argv[2]);

	return argv[0];
}

/* TODO properties: icon, action, title */

static KrkValue _MenuEntrySubmenu_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntrySubmenuClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntrySubmenu");

	struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]);

	if (argc < 2 || !IS_STRING(argv[1]))
		return krk_runtimeError(vm.exceptions->typeError, "expected title to be a str");

	KrkValue icon = NONE_VAL(), action = NONE_VAL();
	if (hasKw) {
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("icon")), &icon);
		krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("action")), &action);
		if (!IS_NONE(icon) && !IS_STRING(icon))
			return krk_runtimeError(vm.exceptions->typeError, "icon must be str, not '%s'", krk_typeName(icon));
		if (!IS_NONE(action) && !IS_STRING(action))
			return krk_runtimeError(vm.exceptions->typeError, "action must be str, not '%s'", krk_typeName(action));
	}

	self->menuEntry = menu_create_submenu(
		IS_STRING(icon) ? AS_CSTRING(icon) : NULL,
		IS_STRING(action) ? AS_CSTRING(action) : NULL,
		AS_CSTRING(argv[1]));

	self->menuEntry->_private = self;

	return argv[0];
}

static KrkValue _MenuEntrySeparator_init(int argc, KrkValue argv[], int hasKw) {
	if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntrySeparatorClass))
		return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntrySeparator");
	struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]);
	self->menuEntry = menu_create_separator();
	self->menuEntry->_private = self;
	return argv[0];
}

KrkValue krk_module_onload__yutani(void) {
	module = krk_newInstance(vm.baseClasses->moduleClass);
	/* Store it on the stack for now so we can do stuff that may trip GC
	 * and not lose it to garbage colletion... */
	krk_push(OBJECT_VAL(module));

	/**
	 * class Message(object):
	 *     MSG_... = ... # Directly from the library headers.
	 */
	Message = krk_createClass(module, "Message", NULL);
	Message->allocSize = sizeof(struct MessageClass);
	Message->_ongcsweep = _message_sweep;
	/* All the MSG_ constants */
#define TYPE(type) krk_attachNamedValue(&Message->methods, "MSG_" #type, INTEGER_VAL(YUTANI_MSG_ ## type))
	TYPE(HELLO); TYPE(WINDOW_NEW); TYPE(FLIP); TYPE(KEY_EVENT); TYPE(MOUSE_EVENT);
	TYPE(WINDOW_MOVE); TYPE(WINDOW_CLOSE); TYPE(WINDOW_SHOW); TYPE(WINDOW_HIDE);
	TYPE(WINDOW_STACK); TYPE(WINDOW_FOCUS_CHANGE); TYPE(WINDOW_MOUSE_EVENT);
	TYPE(FLIP_REGION); TYPE(WINDOW_NEW_FLAGS); TYPE(RESIZE_REQUEST);
	TYPE(RESIZE_OFFER); TYPE(RESIZE_ACCEPT); TYPE(RESIZE_BUFID); TYPE(RESIZE_DONE);
	TYPE(WINDOW_ADVERTISE); TYPE(SUBSCRIBE); TYPE(UNSUBSCRIBE); TYPE(NOTIFY);
	TYPE(QUERY_WINDOWS); TYPE(WINDOW_FOCUS); TYPE(WINDOW_DRAG_START); TYPE(WINDOW_WARP_MOUSE);
	TYPE(WINDOW_SHOW_MOUSE); TYPE(WINDOW_RESIZE_START); TYPE(SESSION_END);
	TYPE(KEY_BIND); TYPE(WINDOW_UPDATE_SHAPE); TYPE(CLIPBOARD); TYPE(GOODBYE);
	TYPE(SPECIAL_REQUEST); TYPE(WELCOME); TYPE(WINDOW_INIT);
#undef TYPE
	/* Structure bindings */
	krk_defineNative(&Message->methods, "__getattr__", _message_getattr);
	krk_finalizeClass(Message);

	/**
	 * class color():
	 *     rgb(a) value for use with graphics functions.
	 */
	YutaniColor = krk_createClass(module, "color", NULL);
	YutaniColor->allocSize = sizeof(struct YutaniColor);
	YutaniColor->docstring = S("color(r,g,b,a=255)\n  Representation of an RGB(A) color.");
	krk_defineNative(&YutaniColor->methods, "__init__", _yutani_color_init);
	krk_defineNative(&YutaniColor->methods, "__repr__", _yutani_color_repr);
	krk_defineNative(&YutaniColor->methods, "__str__", _yutani_color_str);
	krk_finalizeClass(YutaniColor);

	/**
	 * class Yutani(object):
	 *     yctx = yutani_t *
	 *     display_width  = yctx->display_width
	 *     display_height = yctx->display_height
	 *
	 * def __init__(self): # Call yutani_init()
	 */
	Yutani = krk_createClass(module, "Yutani", NULL);
	Yutani->allocSize = sizeof(struct YutaniClass);
	Yutani->docstring = S("Yutani()\n  Establish a connection to the compositor display server.");
	krk_defineNative(&Yutani->methods, "display_width", _yutani_display_width)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&Yutani->methods, "display_height", _yutani_display_height)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&Yutani->methods, "__repr__", _yutani_repr);
	krk_defineNative(&Yutani->methods, "__init__", _yutani_init);
	krk_defineNative(&Yutani->methods, "poll", _yutani_poll);
	krk_defineNative(&Yutani->methods, "wait_for", _yutani_wait_for);
	krk_defineNative(&Yutani->methods, "subscribe", _yutani_subscribe);
	krk_defineNative(&Yutani->methods, "unsubscribe", _yutani_unsubscribe);
	krk_defineNative(&Yutani->methods, "query_windows", _yutani_query_windows);
	krk_defineNative(&Yutani->methods, "fileno", _yutani_fileno);
	krk_defineNative(&Yutani->methods, "query", _yutani_query);
	krk_defineNative(&Yutani->methods, "menu_process_event", _yutani_menu_process_event);
	#if 0
	krk_defineNative(&Yutani->methods, "focus_window", _yutani_focus_window);
	krk_defineNative(&Yutani->methods, "session_end", _yutani_session_end);
	krk_defineNative(&Yutani->methods, "key_bind", _yutani_key_bind);
	#endif
	krk_finalizeClass(Yutani);

	/**
	 * class GraphicsContext():
	 *     ctx = gfx_context_t *
	 */
	GraphicsContext = krk_createClass(module, "GraphicsContext", NULL);
	GraphicsContext->allocSize = sizeof(struct GraphicsContext);
	krk_defineNative(&GraphicsContext->methods, "width", _gfx_width)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&GraphicsContext->methods, "height", _gfx_height)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&GraphicsContext->methods, "fill", _gfx_fill)->doc =
		"GraphicsContext.fill(color)\n"
		"  Fill the entire context with the given color.";
	krk_defineNative(&GraphicsContext->methods, "flip", _gfx_flip)->doc =
		"GraphicsContext.flip()\n"
		"  If the context is double-buffered, flip its backbuffer.";
	krk_defineNative(&GraphicsContext->methods, "blur", _gfx_blur)->doc =
		"GraphicsContext.blur(radius=2)\n"
		"  Perform an in-place box blur on this graphics context.";
	krk_defineNative(&GraphicsContext->methods, "line", _gfx_line)->doc =
		"GraphicsContext.line(x0,x1,y0,y1,color,thickness=None)\n"
		"  Draw a line between the given points. If thickness is not provided, uses a\n"
		"  a simple Bresenham algorithm. If thickness is an int, draws with a box-shaped pen.\n"
		"  If thickness is a float, draws using a point-distance antialiasing algorithm.";
	krk_defineNative(&GraphicsContext->methods, "rect", _gfx_rect)->doc =
		"GraphicsContext.rect(x,y,width,height,color,solid=False,radius=None)\n"
		"  Draw a filled rectangle. If solid is True, paints the given color directly to\n"
		"  the underlying backbuffer with no alpha calculations. If radius is provided,\n"
		"  draws a rounded rectangle.";
	krk_defineNative(&GraphicsContext->methods, "draw_sprite", _gfx_draw_sprite)->doc =
		"GraphicsContext.draw_sprite(sprite,x,y,alpha=None,rotation=None,scale=None,color=None)\n"
		"  Blit a sprite to this graphics context at the given coordinates.\n"
		"  alpha:    float of opacity; 1.0 = fully opaque (default)\n"
		"  rotation: float of radians; when a rotation is given, the coordinates provided are\n"
		"            the center of the rendered sprite, rather than the upper left corner.\n"
		"  scale:    (int,int) of final resolution of sprite; can not be used with rotation.\n"
		"  color:    color to paint the sprite as, can not be used with rotation or scale;\n"
		"            used to paint a given color with this sprite as a 'brush'. Useful for\n"
		"            colored icons, such as those found in the panel.";
	krk_finalizeClass(GraphicsContext);

	/**
	 * class Window(GraphicsContext):
	 *     ctx = gfx_context_t *
	 *     window = yutani_window_t *
	 */
	YutaniWindow = krk_createClass(module, "Window", GraphicsContext);
	YutaniWindow->allocSize = sizeof(struct WindowClass);
	YutaniWindow->docstring = S("Window(width,height,flags=0,title=None,icon=None,doublebuffer=False)\n"
		"  Create a new window and initializes a graphics rendering context for it.");
	krk_defineNative(&YutaniWindow->methods, "__repr__", _window_repr);
	krk_defineNative(&YutaniWindow->methods, "__init__", _window_init);
	krk_defineNative(&YutaniWindow->methods, "flip", _window_flip);
	krk_defineNative(&YutaniWindow->methods, "move", _window_move);
	krk_defineNative(&YutaniWindow->methods, "set_focused", _window_set_focused);
	krk_defineNative(&YutaniWindow->methods, "close", _window_close);
	krk_defineNative(&YutaniWindow->methods, "set_stack", _window_set_stack);
	krk_defineNative(&YutaniWindow->methods, "special_request", _window_special_request);
	krk_defineNative(&YutaniWindow->methods, "resize", _window_resize);
	krk_defineNative(&YutaniWindow->methods, "resize_start", _window_resize_start);
	krk_defineNative(&YutaniWindow->methods, "resize_done", _window_resize_done);
	krk_defineNative(&YutaniWindow->methods, "resize_offer", _window_resize_offer);
	krk_defineNative(&YutaniWindow->methods, "resize_accept", _window_resize_accept);
	krk_defineNative(&YutaniWindow->methods, "update_shape", _window_update_shape);
	krk_defineNative(&YutaniWindow->methods, "show_mouse", _window_show_mouse);
	krk_defineNative(&YutaniWindow->methods, "warp_mouse", _window_warp_mouse);
	krk_defineNative(&YutaniWindow->methods, "set_stack", _window_set_stack);
	krk_defineNative(&YutaniWindow->methods, "advertise", _window_advertise);
	krk_defineNative(&YutaniWindow->methods, "reinit", _window_reinit);

	krk_defineNative(&YutaniWindow->methods, "wid", _window_wid)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&YutaniWindow->methods, "x", _window_x)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&YutaniWindow->methods, "y", _window_y)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&YutaniWindow->methods, "focused", _window_focused)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_finalizeClass(YutaniWindow);

	/**
	 * class Sprite(GraphicsContext):
	 *     ctx = gfx_context_t *
	 *     sprite = sprite_t
	 */
	YutaniSprite = krk_createClass(module, "Sprite", GraphicsContext);
	YutaniSprite->allocSize = sizeof(struct YutaniSprite);
	YutaniSprite->_ongcsweep = _sprite_sweep;
	YutaniSprite->docstring = S("Sprite(filename)\n  Create a sprite from the requested texture file.");
	krk_defineNative(&YutaniSprite->methods, "__repr__", _sprite_repr);
	krk_defineNative(&YutaniSprite->methods, "__init__", _sprite_init);
	krk_finalizeClass(YutaniSprite);

	/**
	 * class Font():
	 *     fontType, fontSize, fontGamma, fontStroke
	 */
	YutaniFont = krk_createClass(module, "Font", NULL);
	YutaniFont->allocSize = sizeof(struct YutaniFont);
	YutaniFont->docstring = S("Font(type,size,gamma=1.7,stroke=0.75,color=color(0,0,0))\n"
		"  Create a Font specification for rendering text.");
	krk_defineNative(&YutaniFont->methods, "__init__", _font_init);
	krk_defineNative(&YutaniFont->methods, "draw_string", _font_draw_string)->doc =
		"Font.draw_string(gfxContext, string, x, y)\n"
		"  Draw text to a graphics context with this font.";
	krk_defineNative(&YutaniFont->methods, "width", _font_width)->doc =
		"Font.width(string)\n"
		"  Calculate the rendered width of the given string when drawn with this font.";
	krk_defineNative(&YutaniFont->methods, "size", _font_size)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY;
	krk_defineNative(&YutaniFont->methods, "set_size", _font_set_size);
	krk_finalizeClass(YutaniFont);

	MenuBarClass = krk_createClass(module, "MenuBar", NULL);
	MenuBarClass->allocSize = sizeof(struct MenuBarClass);
	MenuBarClass->_ongcsweep = _MenuBar_gcsweep;
	krk_defineNative(&MenuBarClass->methods, "__init__", _MenuBar_init);
	krk_defineNative(&MenuBarClass->methods, "place", _MenuBar_place);
	krk_defineNative(&MenuBarClass->methods, "render", _MenuBar_render);
	krk_defineNative(&MenuBarClass->methods, "mouse_event", _MenuBar_mouse_event);
	krk_defineNative(&MenuBarClass->methods, "insert", _MenuBar_insert);
	krk_finalizeClass(MenuBarClass);

	MenuListClass = krk_createClass(module, "MenuList", NULL);
	MenuListClass->allocSize = sizeof(struct MenuListClass);
	krk_defineNative(&MenuListClass->methods, "__init__", _MenuList_init);
	krk_defineNative(&MenuListClass->methods, "insert", _MenuList_insert);
	krk_finalizeClass(MenuListClass);

	MenuEntryClass = krk_createClass(module, "MenuEntry", NULL);
	MenuEntryClass->allocSize = sizeof(struct MenuEntryClass);
	krk_defineNative(&MenuEntryClass->methods, "__init__", _MenuEntry_init);
	krk_finalizeClass(MenuEntryClass);

	MenuEntrySubmenuClass = krk_createClass(module, "MenuEntrySubmenu", MenuEntryClass);
	krk_defineNative(&MenuEntrySubmenuClass->methods, "__init__", _MenuEntrySubmenu_init);
	krk_finalizeClass(MenuEntrySubmenuClass);
	MenuEntrySeparatorClass = krk_createClass(module, "MenuEntrySeparator", MenuEntryClass);
	krk_defineNative(&MenuEntrySeparatorClass->methods, "__init__", _MenuEntrySeparator_init);
	krk_finalizeClass(MenuEntrySeparatorClass);

	KrkInstance * Decorator = krk_newInstance(vm.baseClasses->objectClass);
	krk_attachNamedObject(&module->fields, "Decorator", (KrkObj*)Decorator);
	krk_defineNative(&Decorator->fields, "get_bounds", _decor_get_bounds);
	krk_defineNative(&Decorator->fields, "render", _decor_render);
	krk_defineNative(&Decorator->fields, "handle_event", _decor_handle_event);
	krk_defineNative(&Decorator->fields, "show_default_menu", _decor_show_default_menu);
#define ATTACH_CONSTANT(name) krk_attachNamedValue(&Decorator->fields, #name, INTEGER_VAL(name))
	ATTACH_CONSTANT(DECOR_OTHER);
	ATTACH_CONSTANT(DECOR_CLOSE);
	ATTACH_CONSTANT(DECOR_RESIZE);
	ATTACH_CONSTANT(DECOR_MAXIMIZE);
	ATTACH_CONSTANT(DECOR_RIGHT);

	ATTACH_CONSTANT(DECOR_ACTIVE);
	ATTACH_CONSTANT(DECOR_INACTIVE);

	ATTACH_CONSTANT(DECOR_FLAG_DECORATED);
	ATTACH_CONSTANT(DECOR_FLAG_NO_MAXIMIZE);
	ATTACH_CONSTANT(DECOR_FLAG_TILED);
	ATTACH_CONSTANT(DECOR_FLAG_TILE_LEFT);
	ATTACH_CONSTANT(DECOR_FLAG_TILE_RIGHT);
	ATTACH_CONSTANT(DECOR_FLAG_TILE_UP);
	ATTACH_CONSTANT(DECOR_FLAG_TILE_DOWN);
#undef ATTACH_CONSTANT

	/* Pop the module object before returning; it'll get pushed again
	 * by the VM before the GC has a chance to run, so it's safe. */
	assert(AS_INSTANCE(krk_pop()) == module);
	return OBJECT_VAL(module);
}

