//
//  MPLayout.m
//  MathPad
//
//  Created by Kim Wittenburg on 11.08.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//

#import "MPLayout.h"
#import "MPRangePath.h"

#import "NSIndexPath+MPAdditions.h"

@interface MPLayout ()

// Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index;
- (void)ensureCacheSizeForIndex:(NSUInteger)index;

@end

@implementation MPLayout {
    NSMutableArray *_cache;
    NSRect _cachedBounds;
}

#pragma mark Creation Methods
- (id)init
{
    self = [super init];
    if (self) {
        _cache = [[NSMutableArray alloc] init];
        _cachedBounds = NSZeroRect;
    }
    return self;
}

- (instancetype)initWithElementAtPath:(NSIndexPath *)path
                     inRootExpression:(MPExpression *)rootExpression
                               parent:(MPLayout *)parent
{
    self = [self init];
    if (self) {
        _parent = parent;
    }
    return self;
}

#pragma mark Properties
- (NSFont *)font
{
    return self.usesSmallSize ? self.smallFont : self.normalFont;
}

- (CGFloat)fontSize
{
    return self.usesSmallSize ? self.smallFontSize : self.normalFontSize;
}

- (NSFont *)normalFont
{
    return [NSFont fontWithName:@"CMU Serif"
                           size:self.fontSize];
}

- (CGFloat)normalFontSize
{
    return 18.0;
}

- (NSFont *)smallFont
{
    return [NSFont fontWithName:@"CMU Serif"
                           size:self.smallFontSize];
}

- (CGFloat)smallFontSize
{
    return 12.0;
}

#pragma mark Cache Tree
// Querying and Storing Caches
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index
{
    if (index >= _cache.count) {
        return NO;
    }
    return _cache[index] != MPNull;
}

- (id)cachableObjectForIndex:(NSUInteger)index
                   generator:(id (^)())generator
{
    if ([self hasCacheForElementAtIndex:index]) {
        return _cache[index];
    }
    id object = generator();
    [self ensureCacheSizeForIndex:index];
    _cache[index] = object;
    return object;
}

- (void)ensureCacheSizeForIndex:(NSUInteger)index
{
    while (index >= _cache.count) {
        [_cache addObject:MPNull];
    }
}

// Clearing Caches
- (void)clearCacheInRange:(NSRange)range
        replacementLength:(NSUInteger)replacementLength
{
    NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength];
    while (placeholders.count < replacementLength) {
        [placeholders addObject:MPNull];
    }
    [_cache replaceObjectsInRange:range
             withObjectsFromArray:placeholders];
    [self invalidate];
}

- (void)invalidate
{
    _cachedBounds = NSZeroRect;
    [self.parent invalidate];
}

#pragma mark Calculation and Drawing Methods
- (CTLineRef)createLineForString:(NSString *)aString
{
    NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
                                                               attributes:@{NSFontAttributeName: self.font}];
    CFAttributedStringRef attributedString = CFBridgingRetain(text);
    CTLineRef line = CTLineCreateWithAttributedString(attributedString);
    CFRelease(attributedString); // TODO: Is this release appropriate?
    return line;
}

- (NSRect)bounds
{
    if (NSEqualRects(_cachedBounds, NSZeroRect)) {
        _cachedBounds = [self generateBounds];
    }
    return _cachedBounds;
}

- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath
{
    if (rangePath.location.length == 1) {
        return [self boundingRectForRange:rangePath.rangeAtLastIndex];
    }
    NSUInteger nextIndex = [rangePath.location indexAtPosition:0];
    NSIndexPath *newLocation = [rangePath.location indexPathByRemovingFirstIndex];
    MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
    NSRect bounds = [[self childLayoutAtIndex:nextIndex] boundingRectForRangePath:newRangePath];
    NSPoint offset = [self offsetOfChildLayoutAtIndex:nextIndex];
    bounds.origin = NSMakePoint(bounds.origin.x + offset.x, bounds.origin.y + offset.y);
    return bounds;
}

- (void)drawAtPoint:(NSPoint)point
{
    NSAffineTransform *transform = [NSAffineTransform transform];
    [transform translateXBy:point.x
                        yBy:point.y];
    [transform concat];
    [self draw];
    [transform invert];
    [transform concat];
}

@end