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

#import "MPExpressionLayout.h"
#import "MPFunctionLayout.h"

#import "NSIndexPath+MPAdditions.h"

#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)

@interface MPExpressionLayout (MPLineGeneration)

- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;

@end

@implementation MPExpressionLayout (MPLineGeneration)

- (CTLineRef)lineForElementAtIndex:(NSUInteger)index
{
    id element = [self.expression elementAtIndex:index];
    if (![element isString]) {
        return NULL;
    }
    NSString *string = element;
    id lineObject = [self cachableObjectForIndex:index generator:^id{
        CTLineRef line = [self createLineForString:string];
        return CFBridgingRelease(line);
    }];
    return (__bridge CTLineRef)lineObject;
}

@end

@implementation MPExpressionLayout

# pragma mark Creation Methods
- (instancetype)initRootLayoutWithExpression:(MPExpression *)expression
{
    self = [super init];
    if (self) {
        _expression = expression;
    }
    return self;
}

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

#pragma mark Cache Methods
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
{
    id cachedObject = [self cachableObjectForIndex:index generator:^id{
        NSIndexPath *indexPath = [self.expression.indexPath indexPathByAddingIndex:index];
        MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:indexPath
                                                                         inRootExpression:self.expression.rootExpression
                                                                                   parent:self];
        layout.flipped = self.flipped;
        layout.usesSmallSize = self.usesSmallSize;
        return layout;
    }];
    if ([cachedObject isKindOfClass:[MPLayout class]]) {
        return cachedObject;
    }
    return nil;
}

- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
{
    id symbol = [self.expression elementAtIndex:index];
    if ([symbol isString]) {
        CTLineRef line = [self lineForElementAtIndex:index];
        CFRetain(line);
        CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
        CFRelease(line);
        return bounds;
    } else {
        return [self childLayoutAtIndex:index].bounds;
    }
}

#pragma mark Drawing Methods
- (NSRect)generateBounds
{
    if (self.expression.numberOfElements == 0) {
        return NSMakeRect(0, [self.font descender], kMPEmptyBoxWidth, self.fontSize);
    }
    CGFloat x = 0, y = 0, width = 0, height = 0;
    for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
        NSRect elementBounds = [self boundsOfElementAtIndex:index];
        width += elementBounds.size.width;
        height = MAX(height, elementBounds.size.height);
        y = MIN(y, elementBounds.origin.y);
    }
    return NSMakeRect(x, y, width, height);
}

- (NSRect)boundingRectForRange:(NSRange)range
{
    NSUInteger startOffset;
    NSUInteger startElementIndex = [self.expression indexOfElementAtSymbolLocation:range.location
                                                                           offset:&startOffset];
    // Calculate x position
    CGFloat x = 0, width = 0;
    for (NSUInteger index = 0; index < startElementIndex; index++) {
        x += [self boundsOfElementAtIndex:index].size.width;
    }

    if (startOffset > 0) {
        CTLineRef line = [self lineForElementAtIndex:startElementIndex];
        CFRetain(line);
        CGFloat xOffset = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
        x += xOffset;
        width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
        CFRelease(line);
    } else if (startElementIndex < self.expression.numberOfElements) { // Otherwise the selection is after the last symbol
        width += [self boundsOfElementAtIndex:startElementIndex].size.width;
    }
    
    // If we search the caret position we are done
    if (range.length == 0) {
        return NSMakeRect(x, self.bounds.origin.y, 0, self.bounds.size.height);
    }

    NSUInteger endOffset;
    NSUInteger endElementIndex = [self.expression indexOfElementAtSymbolLocation:NSMaxRange(range)
                                                                          offset:&endOffset];

    // Selection is inside of one string element
    if (startElementIndex == endElementIndex) {
        CTLineRef line = [self lineForElementAtIndex:endElementIndex];
        CFRetain(line);
        CGFloat xStart = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
        CGFloat xEnd = CTLineGetOffsetForStringIndex(line, endOffset, NULL);
        width = xEnd - xStart;
        CFRelease(line);
        return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
    }
    
    // Calculate width
    for (NSUInteger index = startElementIndex + 1; index < endElementIndex; index++) {
        width += [self boundsOfElementAtIndex:index].size.width;
    }
    if (endOffset > 0) {
        CTLineRef line = [self lineForElementAtIndex:endElementIndex];
        CFRetain(line);
        width += CTLineGetOffsetForStringIndex(line, endOffset, NULL);
        CFRelease(line);
    }
    return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
}

- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
{
    CGFloat x = 0;
    for (NSUInteger i = 0; i < index; i++) {
        x += [self boundsOfElementAtIndex:i].size.width;
    }
    return NSMakePoint(x, 0);
}

- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
{
    NSUInteger currentPosition = 0;
    for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
        NSRect elementBounds = [self boundsOfElementAtIndex:index];
        NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
        elementBounds.origin.x += elementOffset.x;
        elementBounds.origin.y += elementOffset.y;
        
        id<MPExpressionElement> element = [self.expression elementAtIndex:index];
        if (NSMouseInRect(point, elementBounds, self.flipped)) {
            if ([element isString]) {
                CTLineRef line = [self lineForElementAtIndex:index];
                CFRetain(line);
                CFIndex localIndex = CTLineGetStringIndexForPosition(line, point);
                CFRelease(line);
                return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
            } else {
                NSPoint pointInFunction = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
                NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInFunction];
                if (subPath.length == 1) {
                    // A single index is used to communicate back wether the
                    // selection should be before or after the function.
                    // A 0 means before, a 1 means after.
                    return [NSIndexPath indexPathWithIndex:currentPosition + [subPath indexAtPosition:0]];
                } else {
                    return [subPath indexPathByPreceedingIndex:index];
                }
            }
        }
        currentPosition += element.length;
    }
    if (point.x < self.bounds.size.width / 2) {
        return [NSIndexPath indexPathWithIndex:0];
    } else {
        return [NSIndexPath indexPathWithIndex:self.expression.length];
    }
}

- (void)draw
{
    // Get the current context
    CGContextRef context =
        (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
    CGContextSaveGState(context);
    
    if (self.expression.numberOfElements == 0) {
        
        CGContextRestoreGState(context);
        NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, 0 + self.font.descender, kMPEmptyBoxWidth, self.fontSize)];
        path.lineWidth = 0.5;
        [path stroke];
        return;
    }
    
    // Set the text matrix
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    
    // Track the x position
    CGFloat x = 0;
        
    for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) {
        // The current element
        id element = [self.expression elementAtIndex:index];
        NSRect elementBounds = [self boundsOfElementAtIndex:index];
        
        if ([element isString]) {
            // Get the line to draw
            CTLineRef line = [self lineForElementAtIndex:index];
            CFRetain(line);
            
            // Move to the appropriate position
            CGContextSetTextPosition(context, x, 0);
                        
            // Perform the drawing
            CTLineDraw(line, context);

            CFRelease(line);
        } else {
            // Let the child layout draw itself
            MPLayout *layout = [self childLayoutAtIndex:index];
            [layout drawAtPoint:NSMakePoint(x, 0)];
        }
        x += elementBounds.size.width;
    }
    CGContextRestoreGState(context);
}

@end
