// 7 april 2015
#include "uipriv_windows.h"

struct uiBox {
	uiWindowsControl c;
	HWND hwnd;
	struct ptrArray *controls;
	int vertical;
	int padded;
};

#define ctrlStretchy(child) childFlag(child)
#define ctrlSetStretchy(child, stretchy) childSetFlag(child, stretchy)
// both used by resize(); preallocated to save time and reduce risk of failure
#define ctrlWidth(child) childIntmax(child, 0)
#define ctrlSetWidth(child, w) childSetIntmax(child, 0, w)
#define ctrlHeight(child) childIntmax(child, 1)
#define ctrlSetHeight(child, h) childSetIntmax(child, 1, h)

static void onDestroy(uiBox *);

uiWindowsDefineControlWithOnDestroy(
	uiBox,								// type name
	uiBoxType,							// type function
	onDestroy(this);						// on destroy
)

static void onDestroy(uiBox *b)
{
	struct child *bc;

	while (b->controls->len != 0) {
		bc = ptrArrayIndex(b->controls, struct child *, 0);
		ptrArrayDelete(b->controls, 0);
		childDestroy(bc);
	}
	ptrArrayDestroy(b->controls);
}

static void minimumSize(uiWindowsControl *c, uiWindowsSizing *d, intmax_t *width, intmax_t *height)
{
	uiBox *b = uiBox(c);
	struct child *bc;
	int xpadding, ypadding;
	uintmax_t nStretchy;
	// these two contain the largest minimum width and height of all stretchy controls in the box
	// all stretchy controls will use this value to determine the final minimum size
	intmax_t maxStretchyWidth, maxStretchyHeight;
	uintmax_t i;
	intmax_t minimumWidth, minimumHeight;
	uiWindowsSizing *dself;

	*width = 0;
	*height = 0;
	if (b->controls->len == 0)
		return;

	dself = uiWindowsNewSizing(b->hwnd);

	// 0) get this Box's padding
	xpadding = 0;
	ypadding = 0;
	if (b->padded) {
		xpadding = d->XPadding;
		ypadding = d->YPadding;
	}

	// 1) initialize the desired rect with the needed padding
	// TODO this is wrong if any controls are hidden
	if (b->vertical)
		*height = (b->controls->len - 1) * ypadding;
	else
		*width = (b->controls->len - 1) * xpadding;

	// 2) add in the size of non-stretchy controls and get (but not add in) the largest widths and heights of stretchy controls
	// we still add in like direction of stretchy controls
	nStretchy = 0;
	maxStretchyWidth = 0;
	maxStretchyHeight = 0;
	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		if (!childVisible(bc))
			continue;
		childMinimumSize(bc, dself, &minimumWidth, &minimumHeight);
		if (ctrlStretchy(bc)) {
			nStretchy++;
			if (maxStretchyWidth < minimumWidth)
				maxStretchyWidth = minimumWidth;
			if (maxStretchyHeight < minimumHeight)
				maxStretchyHeight = minimumHeight;
		}
		if (b->vertical) {
			if (*width < minimumWidth)
				*width = minimumWidth;
			if (!ctrlStretchy(bc))
				*height += minimumHeight;
		} else {
			if (!ctrlStretchy(bc))
				*width += minimumWidth;
			if (*height < minimumHeight)
				*height = minimumHeight;
		}
	}

	// 3) and now we can add in stretchy controls
	if (b->vertical)
		*height += nStretchy * maxStretchyHeight;
	else
		*width += nStretchy * maxStretchyWidth;

	uiWindowsFreeSizing(dself);
}

static void boxRelayout(uiWindowsControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height)
{
	uiBox *b = uiBox(c);
	struct child *bc;
	int xpadding, ypadding;
	uintmax_t nStretchy;
	intmax_t stretchywid, stretchyht;
	uintmax_t i;
	intmax_t minimumWidth, minimumHeight;
	uiWindowsSizing *d;

	uiWindowsEnsureMoveWindow(b->hwnd, x, y, width, height);

	if (b->controls->len == 0)
		return;

	d = uiWindowsNewSizing(b->hwnd);

	// -1) get this Box's padding
	xpadding = 0;
	ypadding = 0;
	if (b->padded) {
		xpadding = d->XPadding;
		ypadding = d->YPadding;
	}

	// 0) inset the available rect by the needed padding
	// TODO this is incorrect if any controls are hidden
	if (b->vertical)
		height -= (b->controls->len - 1) * ypadding;
	else
		width -= (b->controls->len - 1) * xpadding;

	// 1) get width and height of non-stretchy controls
	// this will tell us how much space will be left for stretchy controls
	stretchywid = width;
	stretchyht = height;
	nStretchy = 0;
	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		if (!childVisible(bc))
			continue;
		if (ctrlStretchy(bc)) {
			nStretchy++;
			continue;
		}
		childMinimumSize(bc, d, &minimumWidth, &minimumHeight);
		if (b->vertical) {		// all controls have same width
			ctrlSetWidth(bc, width);
			ctrlSetHeight(bc, minimumHeight);
			stretchyht -= minimumHeight;
		} else {				// all controls have same height
			ctrlSetWidth(bc, minimumWidth);
			ctrlSetHeight(bc, height);
			stretchywid -= minimumWidth;
		}
	}

	// 2) now get the size of stretchy controls
	if (nStretchy != 0)
		if (b->vertical)
			stretchyht /= nStretchy;
		else
			stretchywid /= nStretchy;
	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		if (!childVisible(bc))
			continue;
		if (ctrlStretchy(bc)) {
			ctrlSetWidth(bc, stretchywid);
			ctrlSetHeight(bc, stretchyht);
		}
	}

	// 3) now we can position controls
	// first, make relative to the top-left corner of the container
	x = 0;
	y = 0;
	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		if (!childVisible(bc))
			continue;
		childRelayout(bc, x, y, ctrlWidth(bc), ctrlHeight(bc));
		if (b->vertical)
			y += ctrlHeight(bc) + ypadding;
		else
			x += ctrlWidth(bc) + xpadding;
	}

	uiWindowsFreeSizing(d);
}

static void boxContainerUpdateState(uiControl *c)
{
	uiBox *b = uiBox(c);
	struct child *bc;
	uintmax_t i;

	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		childUpdateState(bc);
	}
}

static void redoControlIDsZOrder(uiBox *b)
{
	struct child *bc;
	LONG_PTR controlID;
	HWND insertAfter;
	uintmax_t i;

	controlID = 100;
	insertAfter = NULL;
	for (i = 0; i < b->controls->len; i++) {
		bc = ptrArrayIndex(b->controls, struct child *, i);
		childAssignControlIDZOrder(bc, &controlID, &insertAfter);
	}
}

static void boxArrangeChildrenControlIDsZOrder(uiWindowsControl *c)
{
	uiBox *b = uiBox(c);

	redoControlIDsZOrder(b);
}

void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
	struct child *bc;

	bc = newChild(c, uiControl(b), b->hwnd);
	ctrlSetStretchy(bc, stretchy);
	ptrArrayAppend(b->controls, bc);
	redoControlIDsZOrder(b);
	uiWindowsControlQueueRelayout(uiWindowsControl(b));
}

void uiBoxDelete(uiBox *b, uintmax_t index)
{
	struct child *bc;

	bc = ptrArrayIndex(b->controls, struct child *, index);
	ptrArrayDelete(b->controls, index);
	childRemove(bc);
	redoControlIDsZOrder(b);
	uiWindowsControlQueueRelayout(uiWindowsControl(b));
}

int uiBoxPadded(uiBox *b)
{
	return b->padded;
}

void uiBoxSetPadded(uiBox *b, int padded)
{
	b->padded = padded;
	uiWindowsControlQueueRelayout(uiWindowsControl(b));
}

static uiBox *finishNewBox(int vertical)
{
	uiBox *b;

	b = (uiBox *) uiNewControl(uiBoxType());

	b->hwnd = newContainer();

	b->vertical = vertical;
	b->controls = newPtrArray();

	uiWindowsFinishNewControl(b, uiBox);
	uiControl(b)->ContainerUpdateState = boxContainerUpdateState;
	uiWindowsControl(b)->Relayout = boxRelayout;
	uiWindowsControl(b)->ArrangeChildrenControlIDsZOrder = boxArrangeChildrenControlIDsZOrder;

	return b;
}

uiBox *uiNewHorizontalBox(void)
{
	return finishNewBox(0);
}

uiBox *uiNewVerticalBox(void)
{
	return finishNewBox(1);
}