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

// TODO hiding all stretchy controls still hugs trailing edge

@interface boxChild : NSObject
@property uiControl *c;
@property BOOL stretchy;
@property NSLayoutPriority oldPrimaryHuggingPri;
@property NSLayoutPriority oldSecondaryHuggingPri;
- (NSView *)view;
@end

@interface boxView : NSView {
	uiBox *b;
	NSMutableArray *children;
	BOOL vertical;
	int padded;

	NSLayoutConstraint *first;
	NSMutableArray *inBetweens;
	NSLayoutConstraint *last;
	NSMutableArray *otherConstraints;

	NSLayoutAttribute primaryStart;
	NSLayoutAttribute primaryEnd;
	NSLayoutAttribute secondaryStart;
	NSLayoutAttribute secondaryEnd;
	NSLayoutAttribute primarySize;
	NSLayoutConstraintOrientation primaryOrientation;
	NSLayoutConstraintOrientation secondaryOrientation;
}
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)syncEnableStates:(int)enabled;
- (CGFloat)paddingAmount;
- (void)establishOurConstraints;
- (void)append:(uiControl *)c stretchy:(int)stretchy;
- (void)delete:(int)n;
- (int)numChildren;
- (int)isPadded;
- (void)setPadded:(int)p;
- (BOOL)hugsTrailing;
- (BOOL)hugsBottom;
- (int)nStretchy;
@end

struct uiBox {
	uiDarwinControl c;
	boxView *view;
};

@implementation boxChild

- (NSView *)view
{
	return (NSView *) uiControlHandle(self.c);
}

@end

@implementation boxView

- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
{
	self = [super initWithFrame:NSZeroRect];
	if (self != nil) {
		// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
		self->b = bb;
		self->vertical = vert;
		self->padded = 0;
		self->children = [NSMutableArray new];

		self->inBetweens = [NSMutableArray new];
		self->otherConstraints = [NSMutableArray new];

		if (self->vertical) {
			self->primaryStart = NSLayoutAttributeTop;
			self->primaryEnd = NSLayoutAttributeBottom;
			self->secondaryStart = NSLayoutAttributeLeading;
			self->secondaryEnd = NSLayoutAttributeTrailing;
			self->primarySize = NSLayoutAttributeHeight;
			self->primaryOrientation = NSLayoutConstraintOrientationVertical;
			self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
		} else {
			self->primaryStart = NSLayoutAttributeLeading;
			self->primaryEnd = NSLayoutAttributeTrailing;
			self->secondaryStart = NSLayoutAttributeTop;
			self->secondaryEnd = NSLayoutAttributeBottom;
			self->primarySize = NSLayoutAttributeWidth;
			self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
			self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
		}
	}
	return self;
}

- (void)onDestroy
{
	boxChild *bc;

	[self removeOurConstraints];
	[self->inBetweens release];
	[self->otherConstraints release];

	for (bc in self->children) {
		uiControlSetParent(bc.c, NULL);
		uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
		uiControlDestroy(bc.c);
	}
	[self->children release];
}

- (void)removeOurConstraints
{
	if (self->first != nil) {
		[self removeConstraint:self->first];
		[self->first release];
		self->first = nil;
	}
	if ([self->inBetweens count] != 0) {
		[self removeConstraints:self->inBetweens];
		[self->inBetweens removeAllObjects];
	}
	if (self->last != nil) {
		[self removeConstraint:self->last];
		[self->last release];
		self->last = nil;
	}
	if ([self->otherConstraints count] != 0) {
		[self removeConstraints:self->otherConstraints];
		[self->otherConstraints removeAllObjects];
	}
}

- (void)syncEnableStates:(int)enabled
{
	boxChild *bc;

	for (bc in self->children)
		uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
}

- (CGFloat)paddingAmount
{
	if (!self->padded)
		return 0.0;
	return uiDarwinPaddingAmount(NULL);
}

- (void)establishOurConstraints
{
	boxChild *bc;
	CGFloat padding;
	NSView *prev;
	NSLayoutConstraint *c;
	BOOL (*hugsSecondary)(uiDarwinControl *);

	[self removeOurConstraints];
	if ([self->children count] == 0)
		return;
	padding = [self paddingAmount];

	// first arrange in the primary direction
	prev = nil;
	for (bc in self->children) {
		if (!uiControlVisible(bc.c))
			continue;
		if (prev == nil) {			// first view
			self->first = uiprivMkConstraint(self, self->primaryStart,
				NSLayoutRelationEqual,
				[bc view], self->primaryStart,
				1, 0,
				@"uiBox first primary constraint");
			[self addConstraint:self->first];
			[self->first retain];
			prev = [bc view];
			continue;
		}
		// not the first; link it
		c = uiprivMkConstraint(prev, self->primaryEnd,
			NSLayoutRelationEqual,
			[bc view], self->primaryStart,
			1, -padding,
			@"uiBox in-between primary constraint");
		[self addConstraint:c];
		[self->inBetweens addObject:c];
		prev = [bc view];
	}
	if (prev == nil)		// no control visible; act as if no controls
		return;
	self->last = uiprivMkConstraint(prev, self->primaryEnd,
		NSLayoutRelationEqual,
		self, self->primaryEnd,
		1, 0,
		@"uiBox last primary constraint");
	[self addConstraint:self->last];
	[self->last retain];

	// then arrange in the secondary direction
	hugsSecondary = uiDarwinControlHugsTrailingEdge;
	if (!self->vertical)
		hugsSecondary = uiDarwinControlHugsBottom;
	for (bc in self->children) {
		if (!uiControlVisible(bc.c))
			continue;
		c = uiprivMkConstraint(self, self->secondaryStart,
			NSLayoutRelationEqual,
			[bc view], self->secondaryStart,
			1, 0,
			@"uiBox secondary start constraint");
		[self addConstraint:c];
		[self->otherConstraints addObject:c];
		c = uiprivMkConstraint([bc view], self->secondaryEnd,
			NSLayoutRelationLessThanOrEqual,
			self, self->secondaryEnd,
			1, 0,
			@"uiBox secondary end <= constraint");
		if ((*hugsSecondary)(uiDarwinControl(bc.c)))
			[c setPriority:NSLayoutPriorityDefaultLow];
		[self addConstraint:c];
		[self->otherConstraints addObject:c];
		c = uiprivMkConstraint([bc view], self->secondaryEnd,
			NSLayoutRelationEqual,
			self, self->secondaryEnd,
			1, 0,
			@"uiBox secondary end == constraint");
		if (!(*hugsSecondary)(uiDarwinControl(bc.c)))
			[c setPriority:NSLayoutPriorityDefaultLow];
		[self addConstraint:c];
		[self->otherConstraints addObject:c];
	}

	// and make all stretchy controls the same size
	if ([self nStretchy] == 0)
		return;
	prev = nil;		// first stretchy view
	for (bc in self->children) {
		if (!uiControlVisible(bc.c))
			continue;
		if (!bc.stretchy)
			continue;
		if (prev == nil) {
			prev = [bc view];
			continue;
		}
		c = uiprivMkConstraint(prev, self->primarySize,
			NSLayoutRelationEqual,
			[bc view], self->primarySize,
			1, 0,
			@"uiBox stretchy size constraint");
		[self addConstraint:c];
		[self->otherConstraints addObject:c];
	}
}

- (void)append:(uiControl *)c stretchy:(int)stretchy
{
	boxChild *bc;
	NSLayoutPriority priority;
	int oldnStretchy;

	bc = [boxChild new];
	bc.c = c;
	bc.stretchy = stretchy;
	bc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);
	bc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);

	uiControlSetParent(bc.c, uiControl(self->b));
	uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
	uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));

	// if a control is stretchy, it should not hug in the primary direction
	// otherwise, it should *forcibly* hug
	if (bc.stretchy)
		priority = NSLayoutPriorityDefaultLow;
	else
		// LONGTERM will default high work?
		priority = NSLayoutPriorityRequired;
	uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);
	// make sure controls don't hug their secondary direction so they fill the width of the view
	uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);

	oldnStretchy = [self nStretchy];
	[self->children addObject:bc];

	[self establishOurConstraints];
	if (bc.stretchy)
		if (oldnStretchy == 0)
			uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));

	[bc release];		// we don't need the initial reference now
}

- (void)delete:(int)n
{
	boxChild *bc;
	int stretchy;

	bc = (boxChild *) [self->children objectAtIndex:n];
	stretchy = bc.stretchy;

	uiControlSetParent(bc.c, NULL);
	uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);

	uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);
	uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);

	[self->children removeObjectAtIndex:n];

	[self establishOurConstraints];
	if (stretchy)
		if ([self nStretchy] == 0)
			uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
}

- (int)numChildren
{
	return (int) [self->children count];
}

- (int)isPadded
{
	return self->padded;
}

- (void)setPadded:(int)p
{
	CGFloat padding;
	NSLayoutConstraint *c;

	self->padded = p;
	padding = [self paddingAmount];
	for (c in self->inBetweens)
		[c setConstant:-padding];
}

- (BOOL)hugsTrailing
{
	if (self->vertical)		// always hug if vertical
		return YES;
	return [self nStretchy] != 0;
}

- (BOOL)hugsBottom
{
	if (!self->vertical)		// always hug if horizontal
		return YES;
	return [self nStretchy] != 0;
}

- (int)nStretchy
{
	boxChild *bc;
	int n;

	n = 0;
	for (bc in self->children) {
		if (!uiControlVisible(bc.c))
			continue;
		if (bc.stretchy)
			n++;
	}
	return n;
}

@end

static void uiBoxDestroy(uiControl *c)
{
	uiBox *b = uiBox(c);

	[b->view onDestroy];
	[b->view release];
	uiFreeControl(uiControl(b));
}

uiDarwinControlDefaultHandle(uiBox, view)
uiDarwinControlDefaultParent(uiBox, view)
uiDarwinControlDefaultSetParent(uiBox, view)
uiDarwinControlDefaultToplevel(uiBox, view)
uiDarwinControlDefaultVisible(uiBox, view)
uiDarwinControlDefaultShow(uiBox, view)
uiDarwinControlDefaultHide(uiBox, view)
uiDarwinControlDefaultEnabled(uiBox, view)
uiDarwinControlDefaultEnable(uiBox, view)
uiDarwinControlDefaultDisable(uiBox, view)

static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
{
	uiBox *b = uiBox(c);

	if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
		return;
	[b->view syncEnableStates:enabled];
}

uiDarwinControlDefaultSetSuperview(uiBox, view)

static BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)
{
	uiBox *b = uiBox(c);

	return [b->view hugsTrailing];
}

static BOOL uiBoxHugsBottom(uiDarwinControl *c)
{
	uiBox *b = uiBox(c);

	return [b->view hugsBottom];
}

static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)
{
	uiBox *b = uiBox(c);

	[b->view establishOurConstraints];
}

uiDarwinControlDefaultHuggingPriority(uiBox, view)
uiDarwinControlDefaultSetHuggingPriority(uiBox, view)

static void uiBoxChildVisibilityChanged(uiDarwinControl *c)
{
	uiBox *b = uiBox(c);

	[b->view establishOurConstraints];
}

void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
	// LONGTERM on other platforms
	// or at leat allow this and implicitly turn it into a spacer
	if (c == NULL)
		uiprivUserBug("You cannot add NULL to a uiBox.");
	[b->view append:c stretchy:stretchy];
}

void uiBoxDelete(uiBox *b, int n)
{
	[b->view delete:n];
}

int uiBoxNumChildren(uiBox *b)
{
	return [b->view numChildren];
}

int uiBoxPadded(uiBox *b)
{
	return [b->view isPadded];
}

void uiBoxSetPadded(uiBox *b, int padded)
{
	[b->view setPadded:padded];
}

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

	uiDarwinNewControl(uiBox, b);

	b->view = [[boxView alloc] initWithVertical:vertical b:b];

	return b;
}

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

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