// 15 august 2015
#import "uipriv_darwin.h"

// TODOs:
// - page 2 isn't growable - I think this is because the button in the tab isn't stretchy
// - tab on page 2 is glitched
// - separators on page 4 have variable padding after them
// - 10.8: if we switch to page 4, then switch back to page 1, check Spaced, and go back to page 4, some controls (progress bar, popup button) are clipped on the sides

struct uiBox {
	uiDarwinControl c;
	NSView *view;
	BOOL vertical;
	int padded;
	NSMutableArray *children;		// []NSValue<uiControl *>
	NSMutableArray *stretchy;		// []NSNumber
	// this view is made stretchy if there are no stretchy views
	NSView *noStretchyView;
	NSString *primaryDirPrefix;
	NSString *secondaryDirPrefix;
	NSLayoutConstraintOrientation primaryOrientation;
	NSLayoutConstraintOrientation secondaryOrientation;
};

static void onDestroy(uiBox *);

uiDarwinDefineControlWithOnDestroy(
	uiBox,								// type name
	uiBoxType,							// type function
	view,								// handle
	onDestroy(this);						// on destroy
)

static uiControl *childAt(uiBox *b, uintmax_t n)
{
	NSValue *v;

	v = (NSValue *) [b->children objectAtIndex:n];
	return (uiControl *) [v pointerValue];
}

static void onDestroy(uiBox *b)
{
	uintmax_t i;
	uiControl *child;
	NSView *childView;

	for (i = 0; i < [b->children count]; i++) {
		child = childAt(b, i);
		childView = (NSView *) uiControlHandle(child);
		[childView removeFromSuperview];
		uiControlSetParent(child, NULL);
		uiControlDestroy(child);
	}
	if ([b->noStretchyView superview] != nil)
		[b->noStretchyView removeFromSuperview];
	[b->noStretchyView release];
	[b->children release];
	[b->stretchy release];
}

static void boxContainerUpdateState(uiControl *c)
{
	uiBox *b = uiBox(c);
	NSUInteger i;

	for (i = 0; i < [b->children count]; i++) {
		NSValue *v;
		uiControl *child;

		v = (NSValue *) [b->children objectAtIndex:i];
		// TODO change all these other instances of casts to conversions
		child = uiControl([v pointerValue]);
		controlUpdateState(child);
	}
}

static NSString *viewName(uintmax_t n)
{
	return [NSString stringWithFormat:@"view%ju", n];
}

static NSString *widthMetricName(uintmax_t n)
{
	return [NSString stringWithFormat:@"view%juwidth", n];
}

static NSString *heightMetricName(uintmax_t n)
{
	return [NSString stringWithFormat:@"view%juheight", n];
}

static int isStretchy(uiBox *b, uintmax_t n)
{
	NSNumber *num;

	num = (NSNumber *) [b->stretchy objectAtIndex:n];
	return [num intValue];
}

// TODO do we still need to set hugging? I think we do for stretchy controls...
// TODO try unsetting spinbox intrinsics and seeing what happens
static void relayout(uiBox *b)
{
	NSMutableDictionary *metrics;
	NSMutableDictionary *views;
	uintmax_t i, n;
	BOOL hasStretchy;
	uintmax_t firstStretchy;
	NSMutableString *constraint;

	if ([b->children count] == 0)
		return;

	[b->view removeConstraints:[b->view constraints]];

	// first lay out all children, collect the views and their fitting sizes (for non-stretchy controls)
	// also figure out which is the first stretchy control, if any
	metrics = [NSMutableDictionary new];
	views = [NSMutableDictionary new];
	hasStretchy = NO;
	n = 0;
	while (n < [b->children count]) {
		uiControl *child;
		uiDarwinControl *cc;
		NSView *childView;
		NSSize fittingSize;

		child = childAt(b, n);
		cc = uiDarwinControl(child);
		childView = (NSView *) uiControlHandle(child);
		[views setObject:childView forKey:viewName(n)];
		(*(cc->Relayout))(cc);
		fittingSize = fittingAlignmentSize(childView);
		[metrics setObject:[NSNumber numberWithDouble:fittingSize.width]
			forKey:widthMetricName(n)];
		[metrics setObject:[NSNumber numberWithDouble:fittingSize.height]
			forKey:heightMetricName(n)];
		if (!hasStretchy && isStretchy(b, n)) {
			hasStretchy = YES;
			firstStretchy = n;
		}
		n++;
	}

	// if there are no stretchy controls, we must add the no-stretchy view
	// if there are, we must remove it
	if (!hasStretchy) {
		if ([b->noStretchyView superview] == nil)
			[b->view addSubview:b->noStretchyView];
		[views setObject:b->noStretchyView forKey:@"noStretchyView"];
	} else {
		if ([b->noStretchyView superview] != nil)
			[b->noStretchyView removeFromSuperview];
	}

	// next, assemble the views in the primary direction
	// they all go in a straight line
	constraint = [NSMutableString new];
	[constraint appendString:b->primaryDirPrefix];
	[constraint appendString:@"|"];
	for (i = 0; i < n; i++) {
		if (b->padded && i != 0)
			[constraint appendString:@"-"];
		[constraint appendString:@"["];
		[constraint appendString:viewName(i)];
		// implement multiple stretchiness properly
		if (isStretchy(b, i) && i != firstStretchy) {
			[constraint appendString:@"(=="];
			[constraint appendString:viewName(firstStretchy)];
			[constraint appendString:@")"];
		}
		// if the control is not stretchy, restrict it to the fitting size
		if (!isStretchy(b, i)) {
			[constraint appendString:@"(=="];
			if (b->vertical)
				[constraint appendString:heightMetricName(i)];
			else
				[constraint appendString:widthMetricName(i)];
			[constraint appendString:@")"];
		}
		[constraint appendString:@"]"];
	}
	if (!hasStretchy)
		// don't space between the last control and the no-stretchy view
		[constraint appendString:@"[noStretchyView]"];
	[constraint appendString:@"|"];
	addConstraint(b->view, constraint, metrics, views);
	[constraint release];

	// next: assemble the views in the secondary direction
	// each of them will span the secondary direction
	for (i = 0; i < n; i++) {
		constraint = [NSMutableString new];
		[constraint appendString:b->secondaryDirPrefix];
		[constraint appendString:@"|["];
		[constraint appendString:viewName(i)];
		[constraint appendString:@"]|"];
		addConstraint(b->view, constraint, nil, views);
		[constraint release];
	}
	if (!hasStretchy) {			// and again to the no-stretchy view
		constraint = [NSMutableString new];
		[constraint appendString:b->secondaryDirPrefix];
		[constraint appendString:@"|[noStretchyView]|"];
		addConstraint(b->view, constraint, nil, views);
		[constraint release];
	}

	[metrics release];
	[views release];
}

static void boxRelayout(uiDarwinControl *c)
{
	relayout(uiBox(c));
}

void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
	NSView *childView;

	childView = (NSView *) uiControlHandle(c);
	[b->children addObject:[NSValue valueWithPointer:c]];
	[b->stretchy addObject:[NSNumber numberWithInt:stretchy]];

	uiControlSetParent(c, uiControl(b));
	[b->view addSubview:childView];

	// TODO save the old hugging priorities
	// if a control is stretchy, it should not hug in the primary direction
	// otherwise, it should *forcibly* hug
	if (stretchy)
		setHuggingPri(childView, NSLayoutPriorityDefaultLow, b->primaryOrientation);
	else
		// TODO will default high work?
		setHuggingPri(childView, NSLayoutPriorityRequired, b->primaryOrientation);
	// make sure controls don't hug their secondary direction so they fill the width of the view
	setHuggingPri(childView, NSLayoutPriorityDefaultLow, b->secondaryOrientation);

	relayout(b);
}

void uiBoxDelete(uiBox *b, uintmax_t n)
{
	NSValue *v;
	uiControl *removed;
	NSView *removedView;

	v = (NSValue *) [b->children objectAtIndex:n];
	removed = (uiControl *) [v pointerValue];
	removedView = (NSView *) uiControlHandle(removed);
	[removedView removeFromSuperview];
	uiControlSetParent(removed, NULL);
	[b->children removeObjectAtIndex:n];
	[b->stretchy removeObjectAtIndex:n];
	relayout(b);
}

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

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

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

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

	b->view = [[NSView alloc] initWithFrame:NSZeroRect];

	b->children = [NSMutableArray new];
	b->stretchy = [NSMutableArray new];

	b->vertical = vertical;
	if (b->vertical) {
		b->primaryDirPrefix = @"V:";
		b->secondaryDirPrefix = @"H:";
		b->primaryOrientation = NSLayoutConstraintOrientationVertical;
		b->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
	} else {
		b->primaryDirPrefix = @"H:";
		b->secondaryDirPrefix = @"V:";
		b->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
		b->secondaryOrientation = NSLayoutConstraintOrientationVertical;
	}

	b->noStretchyView = [[NSView alloc] initWithFrame:NSZeroRect];
	[b->noStretchyView setTranslatesAutoresizingMaskIntoConstraints:NO];
	setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
	setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);

	uiDarwinFinishNewControl(b, uiBox);
	uiControl(b)->ContainerUpdateState = boxContainerUpdateState;
	uiDarwinControl(b)->Relayout = boxRelayout;

	return b;
}

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

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