//
//  KeepAttribute.m
//  Keep Layout
//
//  Created by Martin Kiss on 28.1.13.
//  Copyright (c) 2013 Triceratops. All rights reserved.
//

#import "KeepAttribute.h"
#import "UIView+KeepLayout.h"
#import "KeepLayoutConstraint.h"





@implementation KeepAttribute





- (instancetype)init {
    self = [super init];
    if (self) {
        NSAssert(self.class != KeepAttribute.class, @"%@ is abstract class", self.class);
        if (self.class == KeepAttribute.class) {
            return nil;
        }
    }
    return self;
}





#pragma mark Values


- (void)keepAt:(CGFloat)equalHigh min:(CGFloat)minRequired {
    self.equal = KeepHigh(equalHigh);
    self.min = KeepRequired(minRequired);
}


- (void)keepAt:(CGFloat)equalHigh max:(CGFloat)maxRequired {
    self.equal = KeepHigh(equalHigh);
    self.max = KeepRequired(maxRequired);
}


- (void)keepAt:(CGFloat)equalHigh min:(CGFloat)minRequired max:(CGFloat)maxRequired {
    self.equal = KeepHigh(equalHigh);
    self.min = KeepRequired(minRequired);
    self.max = KeepRequired(maxRequired);
}


- (void)keepMin:(CGFloat)minRequired max:(CGFloat)maxRequired {
    self.min = KeepRequired(minRequired);
    self.max = KeepRequired(maxRequired);
}


#pragma mark Remove


- (void)remove {
    NSAssert(NO, @"-[%@ %@] is abstract", KeepAttribute.class, NSStringFromSelector(_cmd));
}





#pragma mark Grouping


+ (KeepGroupAttribute *)group:(KeepAttribute *)first, ... NS_REQUIRES_NIL_TERMINATION {
    va_list list;
    va_start(list, first);
    
    NSMutableArray *attributes = [[NSMutableArray alloc] init];
    KeepAttribute *attribute = first;
    while (attribute) {
        [attributes addObject:attribute];
        attribute = va_arg(list, KeepAttribute *);
    }
    
    va_end(list);
    
    return [[KeepGroupAttribute alloc] initWithAttributes:attributes];
}


+ (KeepRemovableGroup *)removableChanges:(void(^)(void))block {
    KeepRemovableGroup *removableGroup = [[KeepRemovableGroup alloc] init];
    [KeepRemovableGroup setCurrent:removableGroup];
    block();
    [KeepRemovableGroup setCurrent:nil];
    return removableGroup;
}





#pragma mark Naming & Debugging


- (instancetype)name:(NSString *)format, ... {
#ifdef DEBUG
    va_list arguments;
    va_start(arguments, format);
    
    self.name = [[NSString alloc] initWithFormat:format arguments:arguments];
    
    va_end(arguments);
#endif
    return self;
}


- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ %p; %@ [%@ < %@ < %@]>",
            self.class,
            self,
            self.name ?: @"(no name)",
            KeepValueDescription(self.min),
            KeepValueDescription(self.equal),
            KeepValueDescription(self.max)];
}





@end










#pragma mark -


@interface KeepSimpleAttribute ()

@property (nonatomic, readwrite, weak) UIView *view;
@property (nonatomic, readwrite, assign) NSLayoutAttribute layoutAttribute;
@property (nonatomic, readwrite, weak) UIView *relatedView;
@property (nonatomic, readwrite, assign) NSLayoutAttribute relatedLayoutAttribute;
@property (nonatomic, readwrite, weak) UIView *constraintView;

@property (nonatomic, readwrite, assign) CGFloat coefficient;

@property (nonatomic, readwrite, strong) KeepLayoutConstraint *equalConstraint;
@property (nonatomic, readwrite, strong) KeepLayoutConstraint *maxConstraint;
@property (nonatomic, readwrite, strong) KeepLayoutConstraint *minConstraint;

- (instancetype)initWithView:(UIView *)view
             layoutAttribute:(NSLayoutAttribute)layoutAttribute
                 relatedView:(UIView *)relatedView
      relatedLayoutAttribute:(NSLayoutAttribute)superviewLayoutAttribute
                 coefficient:(CGFloat)coefficient;
- (KeepLayoutConstraint *)createConstraintWithRelation:(NSLayoutRelation)relation value:(KeepValue)value;
- (void)addConstraint:(KeepLayoutConstraint *)constraint;
- (void)applyValue:(KeepValue)value forConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation;
- (void)removeConstraint:(KeepLayoutConstraint *)constraint;
- (void)setNameForConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation value:(KeepValue)value;

@end





@implementation KeepSimpleAttribute





#pragma mark Initialization


- (instancetype)init {
    return [self initWithView:nil
              layoutAttribute:NSLayoutAttributeNotAnAttribute
                  relatedView:nil
       relatedLayoutAttribute:NSLayoutAttributeNotAnAttribute
            coefficient:0];
}


- (instancetype)initWithView:(UIView *)view
             layoutAttribute:(NSLayoutAttribute)layoutAttribute
                 relatedView:(UIView *)relatedView
      relatedLayoutAttribute:(NSLayoutAttribute)relatedLayoutAttribute
                 coefficient:(CGFloat)coefficient {
    self = [super init];
    if (self) {
            NSParameterAssert(view);
            NSParameterAssert(layoutAttribute != NSLayoutAttributeNotAnAttribute);
            NSParameterAssert(coefficient);
        
        NSAssert(self.class != KeepSimpleAttribute.class, @"%@ is abstract class", self.class);
        if (self.class == KeepSimpleAttribute.class) {
            return nil;
        }
        
        self.view = view;
        self.layoutAttribute = layoutAttribute;
        self.relatedView = relatedView;
        self.relatedLayoutAttribute = relatedLayoutAttribute;
        self.constraintView = (relatedView? [view commonSuperview:relatedView] : view);
        self.coefficient = coefficient;
    }
    return self;
}





#pragma mark Constraints


- (KeepLayoutConstraint *)createConstraintWithRelation:(NSLayoutRelation)relation value:(KeepValue)value {
    NSAssert(NO, @"-[%@ %@] is abstract", KeepSimpleAttribute.class, NSStringFromSelector(_cmd));
    return nil;
}


- (void)addConstraint:(KeepLayoutConstraint *)constraint {
    [self.constraintView addConstraint:constraint];
}


- (void)applyValue:(KeepValue)value forConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation {
    NSAssert(NO, @"-[%@ %@] is abstract", KeepSimpleAttribute.class, NSStringFromSelector(_cmd));
}


- (void)removeConstraint:(KeepLayoutConstraint *)constraint {
    [self.constraintView removeConstraint:constraint];
}


- (void)remove {
    [self removeConstraint:self.equalConstraint]; self.equalConstraint = nil;
    [self removeConstraint:self.maxConstraint]; self.maxConstraint = nil;
    [self removeConstraint:self.minConstraint]; self.minConstraint = nil;
}





#pragma mark Values


- (void)setEqual:(KeepValue)equal {
    [super setEqual:equal];
    
    if (KeepValueIsNone(equal)) {
        [self removeConstraint:self.equalConstraint]; self.equalConstraint = nil;
        return;
    }
    
    NSLayoutRelation relation = NSLayoutRelationEqual;
    
    if ( ! self.equalConstraint) {
        self.equalConstraint = [self createConstraintWithRelation:relation value:equal];
        [self setNameForConstraint:self.equalConstraint relation:relation value:equal];
        [self addConstraint:self.equalConstraint];
    }
    else {
        [self applyValue:equal forConstraint:self.equalConstraint relation:relation];
        [self setNameForConstraint:self.equalConstraint relation:relation value:equal];
    }
    
    [[KeepRemovableGroup current] addAttribute:self forRelation:relation];
}


- (void)setMax:(KeepValue)max {
    [super setMax:max];
    
    if (KeepValueIsNone(max)) {
        [self removeConstraint:self.maxConstraint]; self.maxConstraint = nil;
        return;
    }
    
    NSLayoutRelation relation = NSLayoutRelationLessThanOrEqual;
    
    if ( ! self.maxConstraint) {
        self.maxConstraint = [self createConstraintWithRelation:relation value:max];
        [self setNameForConstraint:self.maxConstraint relation:relation value:max];
        [self addConstraint:self.maxConstraint];
    }
    else {
        [self applyValue:max forConstraint:self.maxConstraint relation:relation];
        [self setNameForConstraint:self.maxConstraint relation:relation value:max];
    }
    
    [[KeepRemovableGroup current] addAttribute:self forRelation:relation];
}


- (void)setMin:(KeepValue)min {
    [super setMin:min];
    
    if (KeepValueIsNone(min)) {
        [self removeConstraint:self.minConstraint]; self.minConstraint = nil;
        return;
    }
    
    NSLayoutRelation relation = NSLayoutRelationGreaterThanOrEqual;
    
    if ( ! self.minConstraint) {
        self.minConstraint = [self createConstraintWithRelation:relation value:min];
        [self setNameForConstraint:self.minConstraint relation:relation value:min];
        [self addConstraint:self.minConstraint];
    }
    else {
        [self applyValue:min forConstraint:self.minConstraint relation:relation];
        [self setNameForConstraint:self.minConstraint relation:relation value:min];
    }
    
    [[KeepRemovableGroup current] addAttribute:self forRelation:relation];
}



- (void)setNameForConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation value:(KeepValue)value {
#ifdef DEBUG
    NSDictionary *relationNames = @{
                                    @(NSLayoutRelationEqual) : @"equal to",
                                    @(NSLayoutRelationGreaterThanOrEqual) : @"at least",
                                    @(NSLayoutRelationLessThanOrEqual) : @"at most",
                                    };
    [constraint name:@"%@ %@ %@ with %@ priority", self.name, [relationNames objectForKey:@(relation)], @(value.value), KeepPriorityDescription(value.priority)];
#endif
}





@end










#pragma mark -


@implementation KeepConstantAttribute





#pragma mark Constraint Overrides


- (KeepLayoutConstraint *)createConstraintWithRelation:(NSLayoutRelation)relation value:(KeepValue)value {
    if (self.coefficient < 0) {
        if (relation == NSLayoutRelationGreaterThanOrEqual) relation = NSLayoutRelationLessThanOrEqual;
        else if (relation == NSLayoutRelationLessThanOrEqual) relation = NSLayoutRelationGreaterThanOrEqual;
    }
    KeepLayoutConstraint *constraint = [KeepLayoutConstraint constraintWithItem:self.view attribute:self.layoutAttribute
                                                                    relatedBy:relation
                                                                       toItem:self.relatedView attribute:self.relatedLayoutAttribute
                                                                   multiplier:1 constant:value.value * self.coefficient];
    constraint.priority = value.priority;
    return constraint;
}


- (void)applyValue:(KeepValue)value forConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation {
    constraint.constant = value.value * self.coefficient;
    if (constraint.priority != value.priority) {
        constraint.priority = value.priority;
    }
}





@end










#pragma mark -



@implementation KeepMultiplierAttribute





#pragma mark Constraint Overrides


- (KeepLayoutConstraint *)createConstraintWithRelation:(NSLayoutRelation)relation value:(KeepValue)value {
    KeepLayoutConstraint *constraint = [KeepLayoutConstraint constraintWithItem:self.view attribute:self.layoutAttribute
                                                                    relatedBy:relation
                                                                       toItem:self.relatedView attribute:self.relatedLayoutAttribute
                                                                   multiplier:value.value * self.coefficient constant:0];
    constraint.priority = value.priority;
    return constraint;
}


- (void)applyValue:(KeepValue)value forConstraint:(KeepLayoutConstraint *)constraint relation:(NSLayoutRelation)relation {
    // Since multiplier is not read/write proeperty, we need to re-add the whole constraint again.
    [self removeConstraint:constraint];
    constraint = [self createConstraintWithRelation:constraint.relation value:value];
    
    // TODO: Better solution
    switch (relation) {
        case NSLayoutRelationEqual:
            self.equalConstraint = constraint;
            break;
        case NSLayoutRelationGreaterThanOrEqual:
            self.minConstraint = constraint;
            break;
        case NSLayoutRelationLessThanOrEqual:
            self.maxConstraint = constraint;
            break;
    }
    [self setNameForConstraint:constraint relation:relation value:value];
    [self addConstraint:constraint];
}





@end









#pragma mark -


@interface KeepGroupAttribute ()


@property (nonatomic, readwrite, strong) id<NSFastEnumeration> attributes;


@end





@implementation KeepGroupAttribute





#pragma mark Initialization


- (id)init {
    return [self initWithAttributes:nil];
}


- (instancetype)initWithAttributes:(id<NSFastEnumeration>)attributes {
    self = [super init];
    if (self) {
        NSParameterAssert(attributes);
        
        self.attributes = attributes;
    }
    return self;
}





#pragma mark Debugging


- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ %p; %@ %@>", self.class, self, self.name ?: @"(no name)", [self valueForKeyPath:@"attributes.description"]];
}





#pragma mark Accessing Values

- (KeepValue)equal {
    NSLog(@"Warning! Accessing property %@ for grouped attribute, returning KeepNone.", NSStringFromSelector(_cmd));
    return KeepNone;
}

- (KeepValue)min {
    NSLog(@"Warning! Accessing property %@ for grouped attribute, returning KeepNone.", NSStringFromSelector(_cmd));
    return KeepNone;
}

- (KeepValue)max {
    NSLog(@"Warning! Accessing property %@ for grouped attribute, returning KeepNone.", NSStringFromSelector(_cmd));
    return KeepNone;
}





#pragma mark Setting Values


- (void)setEqual:(KeepValue)equal {
    for (KeepAttribute *attribute in self.attributes) attribute.equal = equal;
}


- (void)setMax:(KeepValue)max {
    for (KeepAttribute *attribute in self.attributes) attribute.max = max;
}


- (void)setMin:(KeepValue)min {
    for (KeepAttribute *attribute in self.attributes) attribute.min = min;
}





#pragma mark Remove


- (void)remove {
    for (KeepAttribute *attribute in self.attributes) [attribute remove];
}





@end









#pragma mark -


@interface KeepRemovableGroup ()


@property (nonatomic, readwrite, strong) NSMutableSet *equalAttributes;
@property (nonatomic, readwrite, strong) NSMutableSet *minAttributes;
@property (nonatomic, readwrite, strong) NSMutableSet *maxAttributes;


@end





@implementation KeepRemovableGroup





#pragma mark Initialization


- (instancetype)init {
    self = [super init];
    if (self) {
        self.equalAttributes = [[NSMutableSet alloc] init];
        self.minAttributes = [[NSMutableSet alloc] init];
        self.maxAttributes = [[NSMutableSet alloc] init];
    }
    return self;
}





#pragma mark Building


static KeepRemovableGroup *staticCurrent = nil;


+ (KeepRemovableGroup *)current {
    return staticCurrent;
}


+ (void)setCurrent:(KeepRemovableGroup *)current {
    staticCurrent = current;
}


- (void)addAttribute:(KeepAttribute *)attribute forRelation:(NSLayoutRelation)relation {
    switch (relation) {
        case NSLayoutRelationEqual: [self.equalAttributes addObject:attribute]; break;
        case NSLayoutRelationLessThanOrEqual: [self.maxAttributes addObject:attribute]; break;
        case NSLayoutRelationGreaterThanOrEqual: [self.minAttributes addObject:attribute]; break;
    }
}





#pragma mark Setting Values


- (void)remove {
    for (KeepAttribute *attribute in self.equalAttributes) attribute.equal = KeepNone;
    for (KeepAttribute *attribute in self.minAttributes) attribute.min = KeepNone;
    for (KeepAttribute *attribute in self.maxAttributes) attribute.max = KeepNone;
}





@end