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

#import "MPLayout.h"

#import "MPExpressionView.h"

#import "MPRangePath.h"
#import "NSIndexPath+MPAdditions.h"



@interface MPLayout ()

- (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)initWithParent:(MPLayout *)parent
{
    self = [self init];
    if (self) {
        _parent = parent;
    }
    return self;
}


#pragma mark Properties


- (void)setExpressionView:(MPExpressionView *)expressionView
{
    self.flipped = expressionView.flipped;
    [_cache removeAllObjects];
    [self invalidate];
}


- (NSFont *)normalFontWithSize:(CGFloat)size
{
    return [NSFont fontWithName:@"CMU Serif"
                           size:size];
}


- (NSFont *)specialFontWithSize:(CGFloat)size
{
    return [NSFont fontWithName:@"CMU Serif Italic"
                           size:size];
}


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


- (CGFloat)normalFontSize
{
    return 18.0;
}


- (CGFloat)smallFontSize
{
    return 12.0;
}


- (NSFont *)font
{
    return [self normalFontWithSize:self.contextInferredFontSize];
}


#pragma mark Cache Tree


- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index
{
    if (index >= _cache.count) {
        return NO;
    }
    return _cache[index] != [NSNull null];
}


- (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:[NSNull null]];
    }
}


- (void)clearCacheInRange:(NSRange)range
        replacementLength:(NSUInteger)replacementLength
{
    NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength];
    while (placeholders.count < replacementLength) {
        [placeholders addObject:[NSNull null]];
    }
    [_cache replaceObjectsInRange:range
             withObjectsFromArray:placeholders];
    [self invalidate];
    [self.expressionView invalidateIntrinsicContentSize];
    self.expressionView.needsDisplay = YES;
}


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


- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.length == 0) {
        return self;
    }
    MPLayout *child = [self childLayoutAtIndex:indexPath.firstIndex];
    return [child childLayoutAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}


#pragma mark Calculation and Drawing Methods


- (CTLineRef)createLineForString:(NSString *)aString
{
    return [self createLineForString:aString
                           usingFont:self.font];
}


- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize
{
    return [self createLineForString:aString
                           usingFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
}


- (CTLineRef)createLineForString:(NSString *)aString
                       usingFont:(NSFont *)font
{
    NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
                                                               attributes:@{NSFontAttributeName: font}];
    CFAttributedStringRef attributedString = CFBridgingRetain(text);
    CTLineRef line = CTLineCreateWithAttributedString(attributedString);
    CFRelease(attributedString);
    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;
}


- (BOOL)drawsChildrenManually
{
    return NO;
}


- (void)drawAtPoint:(NSPoint)point
{
    NSAffineTransform *transform = [NSAffineTransform transform];
    [transform translateXBy:point.x
                        yBy:point.y];
    [transform concat];
    [self draw];
    if (!self.drawsChildrenManually) {
        for (NSUInteger index = 0; index < [self numberOfChildren]; index++) {
            MPLayout *childLayout = [self childLayoutAtIndex:index];
            NSPoint offset = [self offsetOfChildLayoutAtIndex:index];
            [childLayout drawAtPoint:offset];
        }
    }
    [transform invert];
    [transform concat];
}

@end