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

#import "MPExpressionView.h"

#import "MPExpressionStorage.h"
#import "MPExpressionLayout.h"

#import "MPFunctionsViewController.h"

#import "MPFunctionLayout.h"
#import "MPFractionFunction.h"
#import "MPParenthesisFunction.h"
#import "MPPowerFunction.h"

#import "MPParsedExpression.h"
#import "MPMathRules.h"
#import "MPToken.h"

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



@interface MPExpressionView ()

@property (nonatomic, strong) NSButton *functionsButton;
@property (nonatomic, strong) NSPopover *functionsPopover;
@property (nonatomic, strong) MPFunctionsViewController *functionsViewController;

@property (nonatomic, strong) NSPopUpButton *syntaxErrorsPopUpButton;
@property (nonatomic, strong) NSTextField *mathErrorTextField;

@property (nonatomic, strong) NSTimer *caretTimer;
@property (nonatomic) NSTimeInterval caretBlinkRate;
@property (nonatomic) BOOL caretVisible;

@property (nonatomic, getter = isSelectionModifyingStart) BOOL selectionModifyingStart;

@property (nonatomic, strong) NSIndexPath *mouseAnchor;

@end



@interface MPExpressionView (MPDrawing)

- (NSPoint)expressionOrigin;

@end



@interface MPExpressionView (MPSelectionHelper)

- (void)restartCaretTimer;
- (void)updateCaret:(NSTimer *)timer;

- (NSRect)selectionRect;

- (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath
                  byExtendingSelection:(BOOL)extendingSelection
                           selectWords:(BOOL)selectWords;
- (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath
                 byExtendingSelection:(BOOL)extendingSelection
                          selectWords:(BOOL)selectWords;

- (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchor
                             newSelectionPath:(NSIndexPath *)newSelection;
@end



@implementation MPExpressionView (MPDrawing)

- (NSPoint)expressionOrigin
{
    NSRect expressionBounds = [self.expressionStorage.rootLayout bounds];
    CGFloat y = (self.bounds.size.height - expressionBounds.size.height) / 2 + fabs(expressionBounds.origin.y);
    return NSMakePoint(10, y);
}

@end



@implementation MPExpressionView (MPSelectionHelper)

- (void)restartCaretTimer
{
    if (self.caretTimer && [self.caretTimer isValid]) {
        [self.caretTimer invalidate];
    }
    self.caretTimer = [NSTimer scheduledTimerWithTimeInterval:self.caretBlinkRate/2 target:self selector:@selector(updateCaret:) userInfo:nil repeats:YES];
    self.caretVisible = NO;
    [self updateCaret:self.caretTimer];
}


- (void)updateCaret:(NSTimer *)timer
{
    self.caretVisible = !self.caretVisible;
    NSRect updatedRect = self.selectionRect;
    NSPoint expressionOrigin = self.expressionOrigin;
    updatedRect.origin.x += expressionOrigin.x;
    updatedRect.origin.y += expressionOrigin.y;
    [self setNeedsDisplayInRect:updatedRect];
}


- (NSRect)selectionRect
{
    NSRect selectionRect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.selection];
    if (self.selection.length == 0) {
        selectionRect.size.width = 1;
    }
    return selectionRect;
}


- (NSIndexPath *)selectionToTheRightOf:(NSIndexPath *)selectionPath
                  byExtendingSelection:(BOOL)extendingSelection
                           selectWords:(BOOL)selectWords
{
    NSIndexPath *targetExpressionPath = [selectionPath indexPathByRemovingLastIndex];
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
    NSUInteger locationInTarget = selectionPath.lastIndex;
    NSUInteger locationInElement;
    NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
                                                fromReferenceFrame:MPSymbolReferenceFrame
                                                  toReferenceFrame:MPElementReferenceFrame
                                                            offset:&locationInElement];
    
    id<MPExpressionElement> targetElement;
    // There is only a target element if the selection is not the last location in an expression
    if (targetElementIndex < targetExpression.countElements) {
        targetElement = [targetExpression elementAtIndex:targetElementIndex];
    }
    
    if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.countSymbols)) {
        // First or last index in an element or expression
        
        if (locationInTarget == targetExpression.countSymbols) {
            // The selection is inside a function and should proceed
            if (selectionPath.length > 1) {
                NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
                MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
                NSUInteger currentChildIndex = [selectionPath indexPathByRemovingLastIndex].lastIndex;
                NSUInteger newChildIndex = [functionLayout indexOfChildAfterChildAtIndex:currentChildIndex];
                
                // The function is to be exited
                if (newChildIndex == NSNotFound) {
                    targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
                    NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
                                                                          fromReferenceFrame:MPElementReferenceFrame
                                                                            toReferenceFrame:MPSymbolReferenceFrame];
                    return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression+1];
                } else {
                    return [[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0];
                }
            } // else the selection does not change
            
        // First Element
        } else {
            if ([targetElement isString]) {
                locationInTarget++;
            } else {
                NSIndexPath *targetFunctionPath = [selectionPath indexPathByReplacingLastIndexWithIndex:targetElementIndex];
                MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:targetFunctionPath];
                NSUInteger leadingChildIndex = [functionLayout indexOfLeadingChild];
                return [[targetFunctionPath indexPathByAddingIndex:leadingChildIndex] indexPathByAddingIndex:0];
            }
        }
    } else if (locationInTarget < targetExpression.countSymbols) {
        if (selectWords) {
            locationInTarget = [targetExpression convertIndex:targetElementIndex+1
                                           fromReferenceFrame:MPElementReferenceFrame
                                             toReferenceFrame:MPSymbolReferenceFrame];
        } else {
            locationInTarget++;
        }
    }
    return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}


- (NSIndexPath *)selectionToTheLeftOf:(NSIndexPath *)selectionPath
                 byExtendingSelection:(BOOL)extendingSelection
                          selectWords:(BOOL)selectWords
{
    NSIndexPath *targetExpressionPath = [selectionPath indexPathByRemovingLastIndex];
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
    NSUInteger locationInTarget = selectionPath.lastIndex;
    NSUInteger locationInElement;
    NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget
                                                fromReferenceFrame:MPSymbolReferenceFrame
                                                  toReferenceFrame:MPElementReferenceFrame
                                                            offset:&locationInElement];
    
    NSUInteger previousElementIndex = targetElementIndex - (locationInElement == 0 ? 1 : 0);
    id<MPExpressionElement> previousElement;
    if (locationInTarget > 0) {
        previousElement = [targetExpression elementAtIndex:previousElementIndex];
    }
    
    if (!selectWords && !extendingSelection && locationInElement == 0) {
        // First element in expression
        if (locationInTarget == 0) {
            if (selectionPath.length > 1) {
                NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
                MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
                NSUInteger currentChildIndex = [selectionPath indexPathByRemovingLastIndex].lastIndex;
                NSUInteger newChildIndex = [functionLayout indexOfChildBeforeChildAtIndex:currentChildIndex];
                
                // The function is to be exited
                if (newChildIndex == NSNotFound) {
                    targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]];
                    NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex
                                                                          fromReferenceFrame:MPElementReferenceFrame
                                                                            toReferenceFrame:MPSymbolReferenceFrame];
                    return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression];
                } else {
                    targetExpressionPath = [targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex];
                    targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
                    return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
                }
            } // else the selection does not change
            
        // Just
        } else {
            if ([previousElement isString]) {
                locationInTarget--;
            } else {
                NSIndexPath *targetFunctionPath = [selectionPath indexPathByReplacingLastIndexWithIndex:previousElementIndex];
                MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:targetFunctionPath];
                NSUInteger trailingChildIndex = [functionLayout indexOfTrailingChild];
                
                targetExpressionPath = [targetFunctionPath indexPathByAddingIndex:trailingChildIndex];
                targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
                return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols];
            }
        }
    } else if (locationInTarget > 0) {
        if (selectWords) {
            if (locationInElement == 0) {
                targetElementIndex--;
            }
            locationInTarget = [targetExpression convertIndex:targetElementIndex
                                           fromReferenceFrame:MPElementReferenceFrame
                                             toReferenceFrame:MPSymbolReferenceFrame];
        } else {
            locationInTarget--;
        }
    }
    return [selectionPath indexPathByReplacingLastIndexWithIndex:locationInTarget];
}


- (MPRangePath *)rangePathEnclosingAnchorPath:(NSIndexPath *)anchorPath
                             newSelectionPath:(NSIndexPath *)newSelectionPath
{
    if ([anchorPath isEqual:newSelectionPath]) {
        return MPMakeRangePath(anchorPath, 0);
    }
    
    NSIndexPath *commonPath = [anchorPath commonIndexPathWith:newSelectionPath];
    if (commonPath.length == anchorPath.length-1 && commonPath.length == newSelectionPath.length-1) {
        // The two paths point to different locations in the same expression
        NSUInteger anchorIndex = [anchorPath indexAtPosition:commonPath.length];
        NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length];
        NSUInteger minIndex = MIN(anchorIndex, newIndex);
        NSUInteger length = MAX(anchorIndex, newIndex) - minIndex;
        return MPMakeRangePath([commonPath indexPathByAddingIndex:minIndex], length);
    } else {
        if ((commonPath.length & 1) == 1) {
            commonPath = [commonPath indexPathByRemovingLastIndex];
        }
        MPExpression *closestCommonAncestor = [self.expressionStorage elementAtIndexPath:commonPath];
        NSUInteger anchorIndex = [anchorPath indexAtPosition:commonPath.length];
        NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length];
        
        if (commonPath.length < anchorPath.length-1) {
            anchorIndex = [closestCommonAncestor convertIndex:anchorIndex
                                           fromReferenceFrame:MPElementReferenceFrame
                                             toReferenceFrame:MPSymbolReferenceFrame];
        }
        if (commonPath.length < newSelectionPath.length-1) {
            newIndex = [closestCommonAncestor convertIndex:newIndex
                                        fromReferenceFrame:MPElementReferenceFrame
                                          toReferenceFrame:MPSymbolReferenceFrame];
        }
        
        NSUInteger minIndex = MIN(anchorIndex, newIndex);
        if (commonPath.length < anchorPath.length-1 && anchorIndex != minIndex) {
            anchorIndex++;
        } else if (commonPath.length < newSelectionPath.length-1 && newIndex != minIndex) {
            newIndex++;
        }
        
        NSUInteger length = MAX(anchorIndex, newIndex) - minIndex;
        if (anchorIndex == newIndex) {
            length++;
        }
        MPRangePath *newSelection = MPMakeRangePath([commonPath indexPathByAddingIndex:minIndex], length);
        return newSelection;
    }
}

@end



#pragma mark -


@implementation MPExpressionView

#pragma mark Creation Methods


- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initializeExpressionView];
    }
    return self;
}


- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self initializeExpressionView];
    }
    return self;
}


- (void)initializeExpressionView
{
    // Setup the Expression Storage
    MPExpressionStorage *expressionStorage = [[MPExpressionStorage alloc] init];
    expressionStorage.rootLayout.expressionView = self;
    _expressionStorage = expressionStorage;
    
    [self initializeButtons];
    [self initializeErrorsViews];
    
    self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)];
    self.caretBlinkRate = 1.0;
    [self restartCaretTimer];
}


- (void)initializeButtons
{
    NSButton *functionsButton = self.functionsButton;
    [self addSubview:functionsButton];
    
    NSDictionary *variableBindings = NSDictionaryOfVariableBindings(functionsButton);
    
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[functionsButton]-10-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:variableBindings]];
    [self addConstraint:[NSLayoutConstraint constraintWithItem:functionsButton
                                                     attribute:NSLayoutAttributeCenterY
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeCenterY
                                                    multiplier:1
                                                      constant:0]];
}


- (void)initializeErrorsViews
{
    
    NSPopUpButton *syntaxErrors = self.syntaxErrorsPopUpButton;
    NSTextField *mathError = self.mathErrorTextField;
    [self addSubview:syntaxErrors];
    [self addSubview:mathError];
    NSDictionary *variableBindings = NSDictionaryOfVariableBindings(syntaxErrors, mathError);
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[syntaxErrors]"
                                                                options:0
                                                                metrics:nil
                                                                  views:variableBindings]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[syntaxErrors]-10-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:variableBindings]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[mathError]"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:variableBindings]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[mathError]-10-|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:variableBindings]];
}


#pragma mark - NSView Configuration


- (BOOL)acceptsFirstResponder
{
    return YES;
}


- (BOOL)canBecomeKeyView
{
    return YES;
}


- (BOOL)isOpaque
{
    return YES;
}


- (void)resetCursorRects
{
    [self addCursorRect:self.bounds
                 cursor:[NSCursor IBeamCursor]];
}


- (BOOL)becomeFirstResponder
{
    [self restartCaretTimer];
    return [super becomeFirstResponder];
}


- (BOOL)resignFirstResponder
{
    [self.caretTimer invalidate];
    self.caretVisible = NO;
    self.needsDisplay = YES;
    return [super resignFirstResponder];
}


- (void)setFrame:(NSRect)frameRect
{
    [self setNeedsLayout:YES];
    [super setFrame:frameRect];
}


- (NSSize)intrinsicContentSize
{
    NSSize size = self.expressionStorage.rootLayout.bounds.size;
    size.width += 100;
    return size;
}


#pragma mark - Properties


- (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage
{
    _expressionStorage.rootLayout.expressionView = nil;
    _expressionStorage = expressionStorage;;
    _expressionStorage.rootLayout.expressionView = self;
    [self invalidateIntrinsicContentSize];
}


- (void)setSelection:(MPRangePath *)selection
{
    _selection = selection;
    [self restartCaretTimer];
    self.needsDisplay = YES;
}


- (void)setSyntaxErrors:(NSArray *)syntaxErrors
{
    _syntaxErrors = syntaxErrors;
    [self.syntaxErrorsPopUpButton removeAllItems];
    if (syntaxErrors.count == 0) {
        self.syntaxErrorsPopUpButton.hidden = YES;
        return;
    }
    self.syntaxErrorsPopUpButton.hidden = NO;
    NSString *title;
    if (syntaxErrors.count == 1) {
        title = NSLocalizedString(@"1 Syntax Error", nil);
    } else {
        title = [NSString stringWithFormat:NSLocalizedString(@"%ld Syntax Errors", nil), syntaxErrors.count];
    }
    NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor redColor],
                                 NSFontAttributeName: [NSFont boldSystemFontOfSize:12.0]};
    NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes];
    NSMenuItem *titleItem = [[NSMenuItem alloc] init];
    titleItem.attributedTitle = attributedTitle;
    [self.syntaxErrorsPopUpButton.menu addItem:titleItem];

    NSUInteger index = 0;
    for (NSError *error in syntaxErrors) {
        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:error.localizedDescription
                                                      action:NULL
                                               keyEquivalent:@""];
        [self.syntaxErrorsPopUpButton.menu addItem:item];
        index++;
    }
}


- (void)setMathError:(NSError *)mathError
{
    _mathError = mathError;
    self.mathErrorTextField.stringValue = mathError != nil ? mathError.localizedDescription : @"";
}


- (NSButton *)functionsButton
{
    if (!_functionsButton) {
        NSBundle *frameworkBundle = [NSBundle bundleForClass:[self class]];
        NSImage *image = [frameworkBundle imageForResource:@"FunctionsButtonDisclosure"];
        [image setName:@"FunctionsButtonDisclosure"];
        NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
        button.translatesAutoresizingMaskIntoConstraints = NO;
        button.target = self;
        button.action = @selector(toggleFunctionsPopover:);
        button.buttonType = NSMomentaryChangeButton;
        button.bezelStyle = NSShadowlessSquareBezelStyle;
        button.bordered = NO;
        NSFont *font = [NSFont fontWithName:@"Times New Roman" size:25.0];
        NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:@"Σ" attributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: [NSColor colorWithWhite:.61 alpha:1]}];
        button.attributedTitle = attributedTitle;
        button.imagePosition = NSImageLeft;
        button.image = image;
        _functionsButton = button;
    }
    return _functionsButton;
}


- (NSPopUpButton *)syntaxErrorsPopUpButton
{
    if (!_syntaxErrorsPopUpButton) {
        NSPopUpButton *button = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 26)
                                                           pullsDown:YES];
        button.translatesAutoresizingMaskIntoConstraints = NO;
        [button.cell setArrowPosition:NSPopUpArrowAtBottom];
        button.bezelStyle = NSRecessedBezelStyle;
        button.buttonType = NSPushOnPushOffButton;
        button.showsBorderOnlyWhileMouseInside = YES;
        button.font = [NSFont boldSystemFontOfSize:12.0];
        button.hidden = YES;
        button.target = self;
        button.action = @selector(didSelectError:);
        _syntaxErrorsPopUpButton = button;
    }
    return _syntaxErrorsPopUpButton;
}


- (NSTextField *)mathErrorTextField
{
    if (!_mathErrorTextField) {
        NSTextField *textField = [[NSTextField alloc] init];
        textField.translatesAutoresizingMaskIntoConstraints = NO;
        textField.bordered = NO;
        textField.selectable = NO;
        textField.editable = NO;
        textField.textColor = [NSColor redColor];
        textField.font = [NSFont boldSystemFontOfSize:12.0];
        textField.drawsBackground = NO;
        _mathErrorTextField = textField;
    }
    return _mathErrorTextField;
}


- (void)didSelectError:(NSPopUpButton *)sender
{
    NSError *error = self.syntaxErrors[sender.indexOfSelectedItem-1];
    NSIndexPath *pathToExpression = error.userInfo[MPPathToExpressionKey];
    NSRange errorRange = [error.userInfo[MPErrorRangeKey] rangeValue];
    pathToExpression = [pathToExpression indexPathByAddingIndex:errorRange.location];
    self.selection = MPMakeRangePath(pathToExpression, errorRange.length);
}


#pragma mark - Mouse Event Handling


- (void)mouseDown:(NSEvent *)theEvent
{
    NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
                                    fromView:nil];
    NSPoint expressionOrigin = self.expressionOrigin;
    pointInView.x -= expressionOrigin.x;
    pointInView.y -= expressionOrigin.y;
    NSIndexPath *selectionPath = [self.expressionStorage.rootLayout indexPathForMousePoint:pointInView];
    self.mouseAnchor = selectionPath;
    self.selection = MPMakeRangePath(selectionPath, 0);
}


- (void)mouseDragged:(NSEvent *)theEvent
{
    NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
                                    fromView:nil];
    NSPoint expressionOrigin = self.expressionOrigin;
    pointInView.x -= expressionOrigin.x;
    pointInView.y -= expressionOrigin.y;
    NSIndexPath *mouseSelectionPath = [self.expressionStorage.rootLayout indexPathForMousePoint:pointInView];
    
    self.selection = [self rangePathEnclosingAnchorPath:self.mouseAnchor
                                       newSelectionPath:mouseSelectionPath];
}


#pragma mark Key Event Handling


- (void)keyDown:(NSEvent *)theEvent
{
    NSString *characters = theEvent.characters;
    
    if ([characters isEqualToString:@"("]) {
        [self insertParenthesisFunction:nil];
        return;
    }
    if ([characters isEqualToString:@")"]) {
        [self insertClosingParenthesis];
        return;
    }
    if (theEvent.keyCode == 10) {
        [self insertPowerFunction];
        return;
    }
    if ([characters isEqualToString:@"/"]) {
        [self insertFractionFunction];
        return;
    }
    
    NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
    NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
    [allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]];
    
    if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) {
        if ([characters isEqualTo:@"i"]) {
            if (self.selection.location.lastIndex > 0) {
                id<MPExpressionElement> symbol = [self.expressionStorage symbolAtIndex:self.selection.location.lastIndex-1];
                if ([symbol isEqual:@"p"]) {
                    self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], self.selection.length+1);
                    characters = @"π";
                }
            }
        }
        if ([characters isEqualToString:@"*"]) {
            characters = @"⋅";
        }
        [self.expressionStorage replaceItemsInRangePath:self.selection
                                         referenceFrame:MPSymbolReferenceFrame
                                           withElements:@[characters]];
        self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0);
    } else {
        [self interpretKeyEvents:@[theEvent]];
    }
}


#pragma mark - Actions


- (void)switchToDegrees:(id)sender
{
    [MPMathRules sharedRules].isUsingDegrees = YES;
}


- (void)switchToRadians:(id)sender
{
    [MPMathRules sharedRules].isUsingDegrees = NO;
}


- (void)switchRadiansDegrees:(id)sender
{
    [MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees;
}


- (IBAction)toggleFunctionsPopover:(id)sender
{
    if (self.functionsPopover == nil || self.functionsViewController == nil) {
        self.functionsViewController = [[MPFunctionsViewController alloc] init];
        self.functionsViewController.target = self;
        self.functionsViewController.action = @selector(insertFunction:);
        self.functionsPopover = [[NSPopover alloc] init];
        self.functionsPopover.contentViewController = self.functionsViewController;
        self.functionsPopover.animates = YES;
        self.functionsPopover.behavior = NSPopoverBehaviorSemitransient;
    }
    if (self.functionsPopover.isShown) {
        [self.functionsPopover close];
    } else {
        [self.functionsPopover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
    }
}


- (void)insertFunction:(MPFunction *)function
{
    [self.functionsPopover close];
    [self.expressionStorage replaceItemsInRangePath:self.selection
                                     referenceFrame:MPSymbolReferenceFrame
                                       withElements:@[function]];
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
    NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
                                                  fromReferenceFrame:MPSymbolReferenceFrame
                                                    toReferenceFrame:MPElementReferenceFrame];
    NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
    MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
    self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0);
}


#pragma mark Editing Actions


- (void)insertParenthesisFunction:(id)sender
{
    MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
                                                                                   referenceFrame:MPSymbolReferenceFrame];
    MPParenthesisFunction *function = [[MPParenthesisFunction alloc] init];
    function.expression = selectedElementsExpression;
    [self.expressionStorage replaceItemsInRangePath:self.selection
                                     referenceFrame:MPSymbolReferenceFrame
                                       withElements:@[function]];
    
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
    NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
                                                  fromReferenceFrame:MPSymbolReferenceFrame
                                                    toReferenceFrame:MPElementReferenceFrame];
    NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
    self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}


- (void)insertClosingParenthesis
{
    if (self.selection.length > 0) {
        [self insertParenthesisFunction:self];
    } else {
        NSIndexPath *currentPath = [[self.selection.location indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
        while (currentPath.length > 0) {
            id element = [self.expressionStorage elementAtIndexPath:currentPath];
            if ([element isKindOfClass:[MPParenthesisFunction class]]) {
                MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[currentPath indexPathByRemovingLastIndex]];
                NSUInteger selectedIndex = currentPath.lastIndex + 1;
                selectedIndex = [targetExpression convertIndex:selectedIndex
                                            fromReferenceFrame:MPElementReferenceFrame
                                              toReferenceFrame:MPSymbolReferenceFrame];
                self.selection = MPMakeRangePath([currentPath indexPathByReplacingLastIndexWithIndex:selectedIndex], 0);
                break;
            } else {
                currentPath = [[currentPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex];
            }
        }
    }
}


- (void)insertPowerFunction
{
    MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
                                                                                   referenceFrame:MPSymbolReferenceFrame];
    MPPowerFunction *function = [[MPPowerFunction alloc] init];
    function.exponentExpression = selectedElementsExpression;
    [self.expressionStorage replaceItemsInRangePath:self.selection
                                     referenceFrame:MPSymbolReferenceFrame
                                       withElements:@[function]];
    
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
    NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
                                                  fromReferenceFrame:MPSymbolReferenceFrame
                                                    toReferenceFrame:MPElementReferenceFrame];
    NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
    self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length);
}


- (void)insertFractionFunction
{
    if (self.selection.length == 0) {
        MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
        NSInteger index = self.selection.location.lastIndex;
        NSRange numeratorSymbols = NSMakeRange(index, 0);
        while (--index >= 0) {
            id<MPExpressionElement> symbol = [targetExpression symbolAtIndex:index];
            if ([symbol isString]) {
                NSUInteger tokenIndex = [targetExpression convertIndex:index
                                                    fromReferenceFrame:MPSymbolReferenceFrame
                                                      toReferenceFrame:MPTokenReferenceFrame];
                id<MPToken> token = [targetExpression tokenAtIndex:tokenIndex];
                if (token.tokenType == MPNumberToken || token.tokenType == MPVariableToken || token.tokenType == MPElementaryFunctionToken) {
                    numeratorSymbols.location--;
                    numeratorSymbols.length++;
                } else {
                    break;
                }
            } else if (numeratorSymbols.length == 0) {
                numeratorSymbols.location--;
                numeratorSymbols.length++;
            } else {
                break;
            }
        }
        MPExpression *numeratorExpression = [targetExpression subexpressionWithRange:numeratorSymbols
                                                                      referenceFrame:MPSymbolReferenceFrame];
        MPFractionFunction *function = [[MPFractionFunction alloc] init];
        function.numeratorExpression = numeratorExpression;
        [targetExpression replaceItemsInRange:numeratorSymbols referenceFrame:MPSymbolReferenceFrame withElements:@[function]];
        NSUInteger functionElementIndex = [targetExpression convertIndex:numeratorSymbols.location
                                                      fromReferenceFrame:MPSymbolReferenceFrame
                                                        toReferenceFrame:MPElementReferenceFrame];
        NSUInteger selectedSubexpression = numeratorSymbols.length == 0 ? 0 : 1;
        self.selection = MPMakeRangePath([[[self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex] indexPathByAddingIndex:selectedSubexpression] indexPathByAddingIndex:0], 0);
    } else {
        MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection
                                                                                       referenceFrame:MPSymbolReferenceFrame];
        MPFractionFunction *function = [[MPFractionFunction alloc] init];
        function.numeratorExpression = selectedElementsExpression;
        [self.expressionStorage replaceItemsInRangePath:self.selection
                                         referenceFrame:MPSymbolReferenceFrame
                                           withElements:@[function]];
        MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
        NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex
                                                      fromReferenceFrame:MPSymbolReferenceFrame
                                                        toReferenceFrame:MPElementReferenceFrame];
        NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex];
        self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], 0);
    }
}


- (void)insertNewline:(id)sender
{
    if (self.target && self.action) {
        [self.target performSelector:self.action
                          withObject:self
                          afterDelay:0.0];
    }
}


- (void)deleteBackward:(id)sender
{
    if (self.selection.length > 0) {
        [self.expressionStorage replaceItemsInRangePath:self.selection
                                         referenceFrame:MPSymbolReferenceFrame
                                           withElements:@[]];
        self.selection = MPMakeRangePath(self.selection.location, 0);
    } else if (self.selection.location.lastIndex > 0) {
        MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
        id<MPExpressionElement> elementToDelete = [targetExpression elementAtIndex:[targetExpression convertIndex:self.selection.location.lastIndex-1
                                                                                               fromReferenceFrame:MPSymbolReferenceFrame
                                                                                                 toReferenceFrame:MPElementReferenceFrame]];
        if ([elementToDelete isFunction]) {
            self.selection = MPMakeRangePath(self.selection.location.indexPathByDecrementingLastIndex, 1);
        } else {
            [targetExpression replaceItemsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1)
                                   referenceFrame:MPSymbolReferenceFrame
                                     withElements:@[]];
            self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0);
        }
    } else {
        NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
        MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
        if (targetExpression.parent != nil) {
            NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
            MPFunction *function = [self.expressionStorage elementAtIndexPath:functionPath];
            MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
            NSIndexSet *remainingIndexes = [functionLayout indexesOfRemainingChildren];
            
            NSMutableArray *remainder = [[NSMutableArray alloc] init];
            for (NSUInteger index = remainingIndexes.firstIndex;
                 index <= remainingIndexes.lastIndex;
                 index = [remainingIndexes indexGreaterThanIndex:index]) {
                MPExpression *expression = [function childAtIndex:index];
                [remainder addObjectsFromArray:[expression allItemsInReferenceFrame:MPElementReferenceFrame]];
            }
            
            NSIndexPath *newTargetExpressionPath = [functionPath indexPathByRemovingLastIndex];
            MPExpression *newTargetExpression = [self.expressionStorage elementAtIndexPath:newTargetExpressionPath];
            NSUInteger newSelectionElementIndex = [newTargetExpression convertIndex:functionPath.lastIndex
                                                                 fromReferenceFrame:MPElementReferenceFrame
                                                                   toReferenceFrame:MPSymbolReferenceFrame];
            NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:newSelectionElementIndex];
            
            [self.expressionStorage replaceItemsInRangePath:MPMakeRangePath(newSelectionLocation, 1)
                                             referenceFrame:MPSymbolReferenceFrame
                                               withElements:remainder];
            self.selection = MPMakeRangePath(newSelectionLocation, 0);
        }
    }
}


- (void)delete:(id)sender
{
    [self.expressionStorage replaceItemsInRangePath:self.selection
                                     referenceFrame:MPSymbolReferenceFrame
                                       withElements:@[]];
    self.selection = MPMakeRangePath(self.selection.location, 0);
}


#pragma mark Selection Actions


- (void)moveRight:(id)sender
{
    if (self.selection.length > 0) {
        self.selection = MPMakeRangePath(self.selection.maxRangePath, 0);
    } else {
        NSIndexPath *newSelectionLocation = [self selectionToTheRightOf:self.selection.location
                                                   byExtendingSelection:NO
                                                            selectWords:NO];
        self.selection = MPMakeRangePath(newSelectionLocation, 0);
    }
}


- (void)moveLeft:(id)sender
{
    if (self.selection.length > 0) {
        self.selection = MPMakeRangePath(self.selection.location, 0);
    } else {
        NSIndexPath *newSelectionLocation = [self selectionToTheLeftOf:self.selection.location
                                                  byExtendingSelection:NO
                                                           selectWords:NO];
        self.selection = MPMakeRangePath(newSelectionLocation, 0);
    }
}


- (void)moveWordRight:(id)sender
{
    NSIndexPath *location = self.selection.maxRangePath;
    NSIndexPath *newSelectionLocation = [self selectionToTheRightOf:location
                                               byExtendingSelection:NO
                                                        selectWords:YES];
    self.selection = MPMakeRangePath(newSelectionLocation, 0);
}


- (void)moveWordLeft:(id)sender
{
    NSIndexPath *location = self.selection.location;
    NSIndexPath *newSelectionLocation = [self selectionToTheLeftOf:location
                                              byExtendingSelection:NO
                                                       selectWords:YES];
    self.selection = MPMakeRangePath(newSelectionLocation, 0);
}


- (void)moveToBeginningOfLine:(id)sender
{
    self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:0], 0);
}


- (void)moveToEndOfLine:(id)sender
{
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]];
    self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0);
}


- (void)moveLeftAndModifySelection:(id)sender
{
    if (self.selection.length == 0) {
        self.selectionModifyingStart = YES;
    }
    NSIndexPath *location = self.selection.location;
    NSIndexPath *maxLocation = self.selection.maxRangePath;
    if (self.selectionModifyingStart) {
        location = [self selectionToTheLeftOf:location
                         byExtendingSelection:YES
                                  selectWords:NO];
    } else {
        maxLocation = [self selectionToTheLeftOf:maxLocation
                            byExtendingSelection:YES
                                     selectWords:NO];
    }
    self.selection = [self rangePathEnclosingAnchorPath:maxLocation newSelectionPath:location];
}


- (void)moveRightAndModifySelection:(id)sender
{
    if (self.selection.length == 0) {
        self.selectionModifyingStart = NO;
    }
    NSIndexPath *location = self.selection.location;
    NSIndexPath *maxLocation = self.selection.maxRangePath;
    
    if (self.selectionModifyingStart) {
        location = [self selectionToTheRightOf:location
                        byExtendingSelection:YES
                                 selectWords:NO];
    } else {
        maxLocation = [self selectionToTheRightOf:maxLocation
                             byExtendingSelection:YES
                                      selectWords:NO];
    }
    self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}


- (void)moveWordRightAndModifySelection:(id)sender
{
    if (self.selection.length == 0) {
        self.selectionModifyingStart = NO;
    }
    NSIndexPath *location = self.selection.location;
    NSIndexPath *maxLocation = self.selection.maxRangePath;
    if (self.selectionModifyingStart) {
        location = [self selectionToTheRightOf:location
                          byExtendingSelection:YES
                                   selectWords:YES];
        if (location.lastIndex > maxLocation.lastIndex) {
            location = [location indexPathByReplacingLastIndexWithIndex:maxLocation.lastIndex];
        }
    } else {
        maxLocation = [self selectionToTheRightOf:maxLocation
                             byExtendingSelection:YES
                                      selectWords:YES];
    }
    self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}


- (void)moveWordLeftAndModifySelection:(id)sender
{
    if (self.selection.length == 0) {
        self.selectionModifyingStart = YES;
    }
    NSIndexPath *location = self.selection.location;
    NSIndexPath *maxLocation = self.selection.maxRangePath;
    if (self.selectionModifyingStart) {
        location = [self selectionToTheLeftOf:location
                         byExtendingSelection:YES
                                  selectWords:YES];
    } else {
        maxLocation = [self selectionToTheLeftOf:maxLocation
                            byExtendingSelection:YES
                                     selectWords:YES];
        if (maxLocation.lastIndex < location.lastIndex) {
            maxLocation = [maxLocation indexPathByReplacingLastIndexWithIndex:location.lastIndex];
        }
    }
    self.selection = MPMakeRangePath(location, maxLocation.lastIndex-location.lastIndex);
}


- (void)moveUp:(id)sender
{
    NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
    if (targetExpression.parent != nil) {
        NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
        MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
        NSUInteger newChildIndex = [functionLayout indexOfChildAboveChildAtIndex:targetExpressionPath.lastIndex];
        self.selection = MPMakeRangePath([[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0], 0);
    }
}


- (void)moveDown:(id)sender
{
    NSIndexPath *targetExpressionPath = [self.selection.location indexPathByRemovingLastIndex];
    MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath];
    if (targetExpression.parent != nil) {
        NSIndexPath *functionPath = [targetExpressionPath indexPathByRemovingLastIndex];
        MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath];
        NSUInteger newChildIndex = [functionLayout indexOfChildBelowChildAtIndex:targetExpressionPath.lastIndex];
        self.selection = MPMakeRangePath([[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0], 0);
    }
}


- (void)selectAll:(id)sender
{
    NSIndexPath *location = [NSIndexPath indexPathWithIndex:0];
    self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols);
}


#pragma mark Drawing Methods


- (void)drawRect:(NSRect)dirtyRect
{
    // Draw the background
    [super drawRect:dirtyRect];
    [[NSColor whiteColor] set];
    NSRectFill(dirtyRect);
    
    // Calculate the position of the expression (probably also forces layout of the expression the first time)
    NSPoint expressionOrigin = self.expressionOrigin;
    
    NSAffineTransform *transform = [NSAffineTransform transform];
    [transform translateXBy:expressionOrigin.x
                        yBy:expressionOrigin.y];
    [transform concat];
        
    // Draw the selection
    if (self.caretVisible || self.selection.length > 0) {
        if (self.selection.length == 0) {
            [[NSColor blackColor] set];
        } else {
            [[NSColor selectedTextBackgroundColor] set];
        }
        NSRectFill([self selectionRect]);
    }
    
    [transform invert];
    [transform concat];
    
    // Draw the expression
    [[NSColor textColor] set];
    [self.expressionStorage.rootLayout drawAtPoint:expressionOrigin];
}


#pragma mark - User Interface Validations


- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
    BOOL degrees = [MPMathRules sharedRules].isUsingDegrees;
    if (anItem.action == @selector(switchToRadians:)) {
        id anObject = anItem;
        if ([anObject respondsToSelector:@selector(setState:)]) {
            [anObject setState:degrees?NSOffState:NSOnState];
        }
        return YES;
    } else if (anItem.action == @selector(switchToDegrees:)) {
        id anObject = anItem;
        if ([anObject respondsToSelector:@selector(setState:)]) {
            [anObject setState:degrees?NSOnState:NSOffState];
        }
        return YES;
    }
    return YES;
}

@end
