
#include "WINGsP.h"

typedef struct {
	WMView *view;
	int minSize;
	int maxSize;
	int space;
	unsigned expand:1;
	unsigned fill:1;
	unsigned end:1;
} SubviewItem;

typedef struct W_Box {
	W_Class widgetClass;
	W_View *view;

	WMArray *subviews;

	short borderWidth;

	unsigned horizontal:1;
} Box;

#define DEFAULT_WIDTH		40
#define DEFAULT_HEIGHT		40

static void destroyBox(Box * bPtr);

static void handleEvents(XEvent * event, void *data);

static void didResize(struct W_ViewDelegate *, WMView *);

static W_ViewDelegate delegate = {
	NULL,
	NULL,
	didResize,
	NULL,
	NULL
};

WMBox *WMCreateBox(WMWidget * parent)
{
	Box *bPtr;

	bPtr = wmalloc(sizeof(Box));
	memset(bPtr, 0, sizeof(Box));

	bPtr->widgetClass = WC_Box;

	bPtr->view = W_CreateView(W_VIEW(parent));
	if (!bPtr->view) {
		wfree(bPtr);
		return NULL;
	}
	bPtr->view->self = bPtr;

	bPtr->view->delegate = &delegate;

	bPtr->subviews = WMCreateArrayWithDestructor(2, wfree);

	WMCreateEventHandler(bPtr->view, StructureNotifyMask, handleEvents, bPtr);

	WMResizeWidget(bPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);

	return bPtr;
}

typedef struct {
	WMBox *box;
	int total;
	int expands;
	int x, y;
	int xe, ye;
	int w, h;
} BoxData;

static void computeExpansion(void *object, void *cdata)
{
	SubviewItem *item = (SubviewItem *) object;
	BoxData *eData = (BoxData *) cdata;

	eData->total -= item->minSize;
	eData->total -= item->space;
	if (item->expand) {
		eData->expands++;
	}
}

static void doRearrange(void *object, void *cdata)
{
	SubviewItem *item = (SubviewItem *) object;
	BoxData *eData = (BoxData *) cdata;

	if (eData->box->horizontal) {
		eData->w = item->minSize;
		if (item->expand)
			eData->w += eData->total / eData->expands;
	} else {
		eData->h = item->minSize;
		if (item->expand)
			eData->h += eData->total / eData->expands;
	}
	if (!item->end) {
		W_MoveView(item->view, eData->x, eData->y);
	}
	W_ResizeView(item->view, eData->w, eData->h);
	if (eData->box->horizontal) {
		if (item->end)
			eData->xe -= eData->w + item->space;
		else
			eData->x += eData->w + item->space;
	} else {
		if (item->end)
			eData->ye -= eData->h + item->space;
		else
			eData->y += eData->h + item->space;
	}
	if (item->end) {
		W_MoveView(item->view, eData->xe, eData->ye);
	}
}

static void rearrange(WMBox * box)
{
	BoxData eData;

	eData.box = box;
	eData.x = eData.y = box->borderWidth;
	eData.w = eData.h = 1;
	eData.expands = 0;

	if (box->horizontal) {
		eData.ye = box->borderWidth;
		eData.xe = WMWidgetWidth(box) - box->borderWidth;
		eData.h = WMWidgetHeight(box) - 2 * box->borderWidth;
		eData.total = WMWidgetWidth(box) - 2 * box->borderWidth;
	} else {
		eData.xe = box->borderWidth;
		eData.ye = WMWidgetHeight(box) - box->borderWidth;
		eData.w = WMWidgetWidth(box) - 2 * box->borderWidth;
		eData.total = WMWidgetHeight(box) - 2 * box->borderWidth;
	}

	if (eData.w <= 0 || eData.h <= 0 || eData.total <= 0) {
		return;
	}

	WMMapArray(box->subviews, computeExpansion, &eData);
	WMMapArray(box->subviews, doRearrange, &eData);
}

void WMSetBoxBorderWidth(WMBox * box, unsigned width)
{
	if (box->borderWidth != width) {
		box->borderWidth = width;
		rearrange(box);
	}
}

void WMAddBoxSubview(WMBox * bPtr, WMView * view, Bool expand, Bool fill, int minSize, int maxSize, int space)
{
	SubviewItem *subView;

	subView = wmalloc(sizeof(SubviewItem));
	subView->view = view;
	subView->minSize = minSize;
	subView->maxSize = maxSize;
	subView->expand = expand;
	subView->fill = fill;
	subView->space = space;
	subView->end = 0;

	WMAddToArray(bPtr->subviews, subView);

	rearrange(bPtr);
}

void WMAddBoxSubviewAtEnd(WMBox * bPtr, WMView * view, Bool expand, Bool fill, int minSize, int maxSize, int space)
{
	SubviewItem *subView;

	subView = wmalloc(sizeof(SubviewItem));
	subView->view = view;
	subView->minSize = minSize;
	subView->maxSize = maxSize;
	subView->expand = expand;
	subView->fill = fill;
	subView->space = space;
	subView->end = 1;

	WMAddToArray(bPtr->subviews, subView);

	rearrange(bPtr);
}

static int matchView(void *item, void *cdata)
{
	return (((SubviewItem *) item)->view == (WMView *) cdata);
}

void WMRemoveBoxSubview(WMBox * bPtr, WMView * view)
{
	if (WMRemoveFromArrayMatching(bPtr->subviews, matchView, view)) {
		rearrange(bPtr);
	}
}

void WMSetBoxHorizontal(WMBox * box, Bool flag)
{
	/* make sure flag is either 0 or 1 no matter what true value was passed */
	flag = ((flag == 0) ? 0 : 1);
	if (box->horizontal != flag) {
		box->horizontal = flag;
		rearrange(box);
	}
}

static void destroyBox(Box * bPtr)
{
	WMFreeArray(bPtr->subviews);
	wfree(bPtr);
}

static void didResize(struct W_ViewDelegate *delegate, WMView * view)
{
	rearrange(view->self);
}

static void handleEvents(XEvent * event, void *data)
{
	Box *bPtr = (Box *) data;

	CHECK_CLASS(data, WC_Box);

	switch (event->type) {
	case DestroyNotify:
		destroyBox(bPtr);
		break;

	case ConfigureNotify:
		rearrange(bPtr);
		break;
	}
}
