// 18 november 2015
#include <map>
#include "uipriv_haiku.hpp"
using namespace std;

// TODOs:
// - Command+Q invariably quits; override that by catching the B_QUIT_REQUESTED in main.cpp
// - other global shortcuts that need to be handled by overriding DispatchMessage() (NOT MessageReceived()) when adding uiArea event handling

class libuiBWindow : public BWindow {
public:
	// C++11! Inherit constructors.
	using BWindow::BWindow;

	virtual void MessageReceived(BMessage *);

	uiWindow *w;
	virtual bool QuitRequested();
};

struct uiWindow {
	uiHaikuControl c;

	libuiBWindow *window;

	BGroupLayout *vbox;

	struct singleChild *child;
	int margined;

	int (*onClosing)(uiWindow *, void *);
	void *onClosingData;
};

uiHaikuDefineControlWithOnDestroy(
	uiWindow,							// type name
	uiWindowType,							// type function
	window,								// handle
	complain("attempt to use default CommitDestroy() code for uiWindow on Haiku");			// on destroy
)

static map<uint32, void (*)(BMessage *)> eventHandlers;
typedef map<uint32, void (*)(BMessage *)>::const_iterator eventHandlerIter;

void uiHaikuRegisterEventHandler(uint32 what, void (*handler)(BMessage *))
{
	eventHandlerIter iter;

	// TODO decide a convention for libui internal messages
	iter = eventHandlers.find(what);
	if (iter == eventHandlers.end()) {		// new entry
		eventHandlers[what] = handler;
		return;
	}
	if (iter->second != handler)			// mismatch
		complain("attempt to clobber BMessage::what 0x%08X in uiHaikuRegisterEventHandler()", what);
}

void libuiBWindow::MessageReceived(BMessage *msg)
{
	eventHandlerIter iter;

	// handle registered events
	iter = eventHandlers.find(msg->what);
	if (iter != eventHandlers.end()) {
		(*(iter->second))(msg);
		return;
	}
	// no event found; defer to BWindow
	this->BWindow::MessageReceived(msg);
}

bool libuiBWindow::QuitRequested()
{
	// manually destroy the window ourselves; don't let the default BWindow code do it directly
	if ((*(this->w->onClosing))(this->w, this->w->onClosingData))
		uiControlDestroy(uiControl(this->w));
	// don't continue to the default BWindow code; we destroyed the window by now
	return false;
}

static int defaultOnClosing(uiWindow *w, void *data)
{
	return 0;
}

static void windowCommitDestroy(uiControl *c)
{
	uiWindow *w = uiWindow(c);

	// first hide ourselves
	w->window->Hide();
	// now destroy the child
	if (w->child != NULL)
		singleChildDestroy(w->child);
	// and finally destroy ourselves
	// this is why we don't use the libui-provided CommitDestroy() implementation
	// TODO check this for errors? or use B_QUIT_REQUESTED?
	w->window->Lock();
	w->window->Quit();
	// w->window is now destroyed for us
}

// The default implementations assume a BView, which a uiWindow is not.
static void windowCommitShow(uiControl *c)
{
	uiWindow *w = uiWindow(c);

	// This is documented as behaving how we want with regards to presentation.
	w->window->Show();
}

static void windowCommitHide(uiControl *c)
{
	uiWindow *w = uiWindow(c);

	w->window->Hide();
}

static void windowContainerUpdateState(uiControl *c)
{
	uiWindow *w = uiWindow(c);

	if (w->child != NULL)
		singleChildUpdateState(w->child);
}

char *uiWindowTitle(uiWindow *w)
{
	return uiHaikuStrdupText(w->window->Title());
}

void uiWindowSetTitle(uiWindow *w, const char *title)
{
	w->window->SetTitle(title);
	// don't queue resize; the caption isn't part of what affects layout and sizing of the client area (it'll be ellipsized if too long)
}

void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
{
	w->onClosing = f;
	w->onClosingData = data;
}

// see singlechild.cpp
static void attach(void *attachTo, BLayoutItem *what)
{
	BGroupLayout *vbox = (BGroupLayout *) attachTo;

	vbox->AddItem(what);
}

void uiWindowSetChild(uiWindow *w, uiControl *child)
{
	if (w->child != NULL) {
		w->vbox->RemoveItem(singleChildLayoutItem(w->child));
		singleChildRemove(w->child);
	}
	w->child = newSingleChild(child, uiControl(w), attach, w->vbox);
	if (w->child != NULL)
		uiWindowSetMargined(w, w->margined);
}

int uiWindowMargined(uiWindow *w)
{
	return w->margined;
}

void uiWindowSetMargined(uiWindow *w, int margined)
{
	w->margined = margined;
	if (w->child != NULL)
		if (w->margined)
			singleChildSetMargined(w->child, B_USE_WINDOW_SPACING);
		else
			singleChildSetMargined(w->child, 0);
}

uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
{
	uiWindow *w;

	w = (uiWindow *) uiNewControl(uiWindowType());

	// TODO find out how to make it ignore position
	// The given rect is the size of the inside of the window, just as we want.
	w->window = new libuiBWindow(
		BRect(100, 100, width, height),
		title,
		B_TITLED_WINDOW,
		// TODO B_AUTO_UPDATE_SIZE_LIMITS?
		// TODO if we do this we need to set the maximum size to arbitrary (TODO always? check GTK+ and OS X)
		B_ASYNCHRONOUS_CONTROLS);
	w->window->w = w;

	w->vbox = new BGroupLayout(B_VERTICAL, 0);
	w->window->SetLayout(w->vbox);
	// Haiku itself does this, with a TODO
	w->vbox->Owner()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));

	uiWindowOnClosing(w, defaultOnClosing, NULL);

	uiHaikuFinishNewControl(w, uiWindow);
	uiControl(w)->CommitDestroy = windowCommitDestroy;
	uiControl(w)->CommitShow = windowCommitShow;
	uiControl(w)->CommitHide = windowCommitHide;
	uiControl(w)->ContainerUpdateState = windowContainerUpdateState;

	return w;
}