// 7 june 2016
#import "uipriv_darwin.h"

// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced)

@interface formChild : NSView
@property uiControl *c;
@property (strong) NSTextField *label;
@property BOOL stretchy;
@property NSLayoutPriority oldHorzHuggingPri;
@property NSLayoutPriority oldVertHuggingPri;
@property (strong) NSLayoutConstraint *baseline;
@property (strong) NSLayoutConstraint *leading;
@property (strong) NSLayoutConstraint *top;
@property (strong) NSLayoutConstraint *trailing;
@property (strong) NSLayoutConstraint *bottom;
- (id)initWithLabel:(NSTextField *)l;
- (void)onDestroy;
- (NSView *)view;
@end

@interface formView : NSView {
	uiForm *f;
	NSMutableArray *children;
	int padded;

	NSLayoutConstraint *first;
	NSMutableArray *inBetweens;
	NSLayoutConstraint *last;
	NSMutableArray *widths;
	NSMutableArray *leadings;
	NSMutableArray *middles;
	NSMutableArray *trailings;
}
- (id)initWithF:(uiForm *)ff;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)syncEnableStates:(int)enabled;
- (CGFloat)paddingAmount;
- (void)establishOurConstraints;
- (void)append:(NSString *)label c:(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 uiForm {
	uiDarwinControl c;
	formView *view;
};

@implementation formChild

- (id)initWithLabel:(NSTextField *)l
{
	self = [super initWithFrame:NSZeroRect];
	if (self) {
		self.label = l;
		[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
		[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
		[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
		[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
		[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
		[self addSubview:self.label];

		self.leading = uiprivMkConstraint(self.label, NSLayoutAttributeLeading,
			NSLayoutRelationGreaterThanOrEqual,
			self, NSLayoutAttributeLeading,
			1, 0,
			@"uiForm label leading");
		[self addConstraint:self.leading];
		self.top = uiprivMkConstraint(self.label, NSLayoutAttributeTop,
			NSLayoutRelationEqual,
			self, NSLayoutAttributeTop,
			1, 0,
			@"uiForm label top");
		[self addConstraint:self.top];
		self.trailing = uiprivMkConstraint(self.label, NSLayoutAttributeTrailing,
			NSLayoutRelationEqual,
			self, NSLayoutAttributeTrailing,
			1, 0,
			@"uiForm label trailing");
		[self addConstraint:self.trailing];
		self.bottom = uiprivMkConstraint(self.label, NSLayoutAttributeBottom,
			NSLayoutRelationEqual,
			self, NSLayoutAttributeBottom,
			1, 0,
			@"uiForm label bottom");
		[self addConstraint:self.bottom];
	}
	return self;
}

- (void)onDestroy
{
	[self removeConstraint:self.trailing];
	self.trailing = nil;
	[self removeConstraint:self.top];
	self.top = nil;
	[self removeConstraint:self.bottom];
	self.bottom = nil;

	[self.label removeFromSuperview];
	self.label = nil;
}

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

@end

@implementation formView

- (id)initWithF:(uiForm *)ff
{
	self = [super initWithFrame:NSZeroRect];
	if (self != nil) {
		self->f = ff;
		self->padded = 0;
		self->children = [NSMutableArray new];

		self->inBetweens = [NSMutableArray new];
		self->widths = [NSMutableArray new];
		self->leadings = [NSMutableArray new];
		self->middles = [NSMutableArray new];
		self->trailings = [NSMutableArray new];
	}
	return self;
}

- (void)onDestroy
{
	formChild *fc;

	[self removeOurConstraints];
	[self->inBetweens release];
	[self->widths release];
	[self->leadings release];
	[self->middles release];
	[self->trailings release];

	for (fc in self->children) {
		[self removeConstraint:fc.baseline];
		fc.baseline = nil;
		uiControlSetParent(fc.c, NULL);
		uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
		uiControlDestroy(fc.c);
		[fc onDestroy];
		[fc removeFromSuperview];
	}
	[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->widths count] != 0) {
		[self removeConstraints:self->widths];
		[self->widths removeAllObjects];
	}
	if ([self->leadings count] != 0) {
		[self removeConstraints:self->leadings];
		[self->leadings removeAllObjects];
	}
	if ([self->middles count] != 0) {
		[self removeConstraints:self->middles];
		[self->middles removeAllObjects];
	}
	if ([self->trailings count] != 0) {
		[self removeConstraints:self->trailings];
		[self->trailings removeAllObjects];
	}
}

- (void)syncEnableStates:(int)enabled
{
	formChild *fc;

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

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

- (void)establishOurConstraints
{
	formChild *fc;
	CGFloat padding;
	NSView *prev, *prevlabel;
	NSLayoutConstraint *c;

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

	// first arrange the children vertically and make them the same width
	prev = nil;
	for (fc in self->children) {
		[fc setHidden:!uiControlVisible(fc.c)];
		if (!uiControlVisible(fc.c))
			continue;
		if (prev == nil) {			// first view
			self->first = uiprivMkConstraint(self, NSLayoutAttributeTop,
				NSLayoutRelationEqual,
				[fc view], NSLayoutAttributeTop,
				1, 0,
				@"uiForm first vertical constraint");
			[self addConstraint:self->first];
			[self->first retain];
			prev = [fc view];
			prevlabel = fc;
			continue;
		}
		// not the first; link it
		c = uiprivMkConstraint(prev, NSLayoutAttributeBottom,
			NSLayoutRelationEqual,
			[fc view], NSLayoutAttributeTop,
			1, -padding,
			@"uiForm in-between vertical constraint");
		[self addConstraint:c];
		[self->inBetweens addObject:c];
		// and make the same width
		c = uiprivMkConstraint(prev, NSLayoutAttributeWidth,
			NSLayoutRelationEqual,
			[fc view], NSLayoutAttributeWidth,
			1, 0,
			@"uiForm control width constraint");
		[self addConstraint:c];
		[self->widths addObject:c];
		c = uiprivMkConstraint(prevlabel, NSLayoutAttributeWidth,
			NSLayoutRelationEqual,
			fc, NSLayoutAttributeWidth,
			1, 0,
			@"uiForm label lwidth constraint");
		[self addConstraint:c];
		[self->widths addObject:c];
		prev = [fc view];
		prevlabel = fc;
	}
	if (prev == nil)		// all hidden; act as if nothing there
		return;
	self->last = uiprivMkConstraint(prev, NSLayoutAttributeBottom,
		NSLayoutRelationEqual,
		self, NSLayoutAttributeBottom,
		1, 0,
		@"uiForm last vertical constraint");
	[self addConstraint:self->last];
	[self->last retain];

	// now arrange the controls horizontally
	for (fc in self->children) {
		if (!uiControlVisible(fc.c))
			continue;
		c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
			NSLayoutRelationEqual,
			fc, NSLayoutAttributeLeading,
			1, 0,
			@"uiForm leading constraint");
		[self addConstraint:c];
		[self->leadings addObject:c];
		// coerce the control to be as wide as possible
		// see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th
		c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
			NSLayoutRelationEqual,
			[fc view], NSLayoutAttributeLeading,
			1, 0,
			@"uiForm leading constraint");
		[c setPriority:NSLayoutPriorityDefaultHigh];
		[self addConstraint:c];
		[self->leadings addObject:c];
		c = uiprivMkConstraint(fc, NSLayoutAttributeTrailing,
			NSLayoutRelationEqual,
			[fc view], NSLayoutAttributeLeading,
			1, -padding,
			@"uiForm middle constraint");
		[self addConstraint:c];
		[self->middles addObject:c];
		c = uiprivMkConstraint([fc view], NSLayoutAttributeTrailing,
			NSLayoutRelationEqual,
			self, NSLayoutAttributeTrailing,
			1, 0,
			@"uiForm trailing constraint");
		[self addConstraint:c];
		[self->trailings addObject:c];
		// TODO
		c = uiprivMkConstraint(fc, NSLayoutAttributeBottom,
			NSLayoutRelationLessThanOrEqual,
			self, NSLayoutAttributeBottom,
			1, 0,
			@"TODO");
		[self addConstraint:c];
		[self->trailings addObject:c];
	}

	// and make all stretchy controls have the same height
	prev = nil;
	for (fc in self->children) {
		if (!uiControlVisible(fc.c))
			continue;
		if (!fc.stretchy)
			continue;
		if (prev == nil) {
			prev = [fc view];
			continue;
		}
		c = uiprivMkConstraint([fc view], NSLayoutAttributeHeight,
			NSLayoutRelationEqual,
			prev, NSLayoutAttributeHeight,
			1, 0,
			@"uiForm stretchy constraint");
		[self addConstraint:c];
		// TODO make a dedicated array for this
		[self->leadings addObject:c];
	}

	// we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline)
}

- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy
{
	formChild *fc;
	NSLayoutPriority priority;
	NSLayoutAttribute attribute;
	int oldnStretchy;

	fc = [[formChild alloc] initWithLabel:uiprivNewLabel(label)];
	fc.c = c;
	fc.stretchy = stretchy;
	fc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal);
	fc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical);
	[fc setTranslatesAutoresizingMaskIntoConstraints:NO];
	[self addSubview:fc];

	uiControlSetParent(fc.c, uiControl(self->f));
	uiDarwinControlSetSuperview(uiDarwinControl(fc.c), self);
	uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f)));

	// if a control is stretchy, it should not hug vertically
	// otherwise, it should *forcibly* hug
	if (fc.stretchy)
		priority = NSLayoutPriorityDefaultLow;
	else
		// LONGTERM will default high work?
		priority = NSLayoutPriorityRequired;
	uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical);
	// make sure controls don't hug their horizontal direction so they fill the width of the view
	uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);

	// and constrain the baselines to position the label vertically
	// if the view is a scroll view, align tops, not baselines
	// this is what Interface Builder does
	attribute = NSLayoutAttributeBaseline;
	if ([[fc view] isKindOfClass:[NSScrollView class]])
		attribute = NSLayoutAttributeTop;
	fc.baseline = uiprivMkConstraint(fc.label, attribute,
		NSLayoutRelationEqual,
		[fc view], attribute,
		1, 0,
		@"uiForm baseline constraint");
	[self addConstraint:fc.baseline];

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

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

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

- (void)delete:(int)n
{
	formChild *fc;
	int stretchy;

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

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

	uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
	uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);

	[fc onDestroy];
	[self->children removeObjectAtIndex:n];

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

- (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];
	for (c in self->middles)
		[c setConstant:-padding];
}

- (BOOL)hugsTrailing
{
	return YES;			// always hug trailing
}

- (BOOL)hugsBottom
{
	// only hug if we have stretchy
	return [self nStretchy] != 0;
}

- (int)nStretchy
{
	formChild *fc;
	int n;

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

@end

static void uiFormDestroy(uiControl *c)
{
	uiForm *f = uiForm(c);

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

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

static void uiFormSyncEnableState(uiDarwinControl *c, int enabled)
{
	uiForm *f = uiForm(c);

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

uiDarwinControlDefaultSetSuperview(uiForm, view)

static BOOL uiFormHugsTrailingEdge(uiDarwinControl *c)
{
	uiForm *f = uiForm(c);

	return [f->view hugsTrailing];
}

static BOOL uiFormHugsBottom(uiDarwinControl *c)
{
	uiForm *f = uiForm(c);

	return [f->view hugsBottom];
}

static void uiFormChildEdgeHuggingChanged(uiDarwinControl *c)
{
	uiForm *f = uiForm(c);

	[f->view establishOurConstraints];
}

uiDarwinControlDefaultHuggingPriority(uiForm, view)
uiDarwinControlDefaultSetHuggingPriority(uiForm, view)

static void uiFormChildVisibilityChanged(uiDarwinControl *c)
{
	uiForm *f = uiForm(c);

	[f->view establishOurConstraints];
}

void uiFormAppend(uiForm *f, const char *label, 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 uiForm.");
	[f->view append:uiprivToNSString(label) c:c stretchy:stretchy];
}

void uiFormDelete(uiForm *f, int n)
{
	[f->view delete:n];
}

int uiFormNumChildren(uiForm *f)
{
	return [f->view numChildren];
}

int uiFormPadded(uiForm *f)
{
	return [f->view isPadded];
}

void uiFormSetPadded(uiForm *f, int padded)
{
	[f->view setPadded:padded];
}

uiForm *uiNewForm(void)
{
	uiForm *f;

	uiDarwinNewControl(uiForm, f);

	f->view = [[formView alloc] initWithF:f];

	return f;
}
