//
//  HFLineCountingView.m
//  HexFiend_2
//
//  Copyright 2007 ridiculous_fish. All rights reserved.
//

#import <HexFiend/HFLineCountingView.h>
#import <HexFiend/HFLineCountingRepresenter.h>
#import <HexFiend/HFFunctions.h>

#define TIME_LINE_NUMBERS 0

#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0

#define INVALID_LINE_COUNT NSUIntegerMax

#if TIME_LINE_NUMBERS
@interface HFTimingTextView : NSTextView
@end
@implementation HFTimingTextView
- (void)drawRect:(NSRect)rect {
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
    [super drawRect:rect];
    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"TextView line number time: %f", endTime - startTime);
}
@end
#endif

@implementation HFLineCountingView

- (void)_sharedInitLineCountingView {
    layoutManager = [[NSLayoutManager alloc] init];
    textStorage = [[NSTextStorage alloc] init];
    [textStorage addLayoutManager:layoutManager];
    textContainer = [[NSTextContainer alloc] init];
    [textContainer setLineFragmentPadding:(CGFloat)5];
    [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
    [layoutManager addTextContainer:textContainer];
}

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

- (void)dealloc {
    HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications);
    [_font release];
    [layoutManager release];
    [textContainer release];
    [textStorage release];
    [textAttributes release];
    [super dealloc];
}

- (void)encodeWithCoder:(NSCoder *)coder {
    HFASSERT([coder allowsKeyedCoding]);
    [super encodeWithCoder:coder];
    [coder encodeObject:_font forKey:@"HFFont"];
    [coder encodeDouble:_lineHeight forKey:@"HFLineHeight"];
    [coder encodeObject:_representer forKey:@"HFRepresenter"];
    [coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"];
    [coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"];
    [coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    HFASSERT([coder allowsKeyedCoding]);
    self = [super initWithCoder:coder];
    [self _sharedInitLineCountingView];
    _font = [[coder decodeObjectForKey:@"HFFont"] retain];
    _lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
    _representer = [coder decodeObjectForKey:@"HFRepresenter"];
    _bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"];
    _lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
    useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"];
    return self;
}

- (BOOL)isFlipped { return YES; }

- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length {
    HFLineNumberFormat format = self.lineNumberFormat;
    if (format == HFLineNumberFormatDecimal) {
        strlcpy(outString, "%llu", length);
    }
    else if (format == HFLineNumberFormatHexadecimal) {
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
        // we want a format string like 0x%08llX
        snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2);
#else
        // we want a format string like %08llX
        snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount);
#endif
    }
    else {
        strlcpy(outString, "", length);
    }
}

- (void)windowDidChangeKeyStatus:(NSNotification *)note {
    USE(note);
    [self setNeedsDisplay:YES];
}

- (void)viewDidMoveToWindow {
    HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
    registeredForAppNotifications = YES;
    [super viewDidMoveToWindow];
}

- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
    HFUnregisterViewForWindowAppearanceChanges(self, NO);
    [super viewWillMoveToWindow:newWindow];
}

- (void)drawGradientWithClip:(NSRect)clip {
    [_representer.backgroundColor set];
    NSRectFill(clip);
    
    NSInteger shadowEdge = _representer.interiorShadowEdge;
    
    if (shadowEdge >= 0) {
        const CGFloat shadowWidth = 6;
        NSWindow *window = self.window;
        BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
        HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
    }
}

- (void)drawDividerWithClip:(NSRect)clipRect {
    USE(clipRect);
    

#if 1    
    NSInteger edges = _representer.borderedEdges;
    NSRect bounds = self.bounds;
    
    
    // -1 means to draw no edges
    if (edges == -1) {
        edges = 0;
    }
    
    [_representer.borderColor set];
    
    if ((edges & (1 << NSMinXEdge)) > 0) {
        NSRect lineRect = bounds;
        lineRect.size.width = 1;
        lineRect.origin.x = 0;
        if (NSIntersectsRect(lineRect, clipRect)) {
            NSRectFill(lineRect);
        }
    }
    
    if ((edges & (1 << NSMaxXEdge)) > 0) {
        NSRect lineRect = bounds;
        lineRect.size.width = 1;
        lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
        if (NSIntersectsRect(lineRect, clipRect)) {
            NSRectFill(lineRect);
        }
    }
    
    if ((edges & (1 << NSMinYEdge)) > 0) {
        NSRect lineRect = bounds;
        lineRect.size.height = 1;
        lineRect.origin.y = 0;
        if (NSIntersectsRect(lineRect, clipRect)) {
            NSRectFill(lineRect);
        }
    }
    
    if ((edges & (1 << NSMaxYEdge)) > 0) {
        NSRect lineRect = bounds;
        lineRect.size.height = 1;
        lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height;
        if (NSIntersectsRect(lineRect, clipRect)) {
            NSRectFill(lineRect);
        }
    }
    
    
    // Backwards compatibility to always draw a border on the edge with the interior shadow
    
    NSRect lineRect = bounds;
    lineRect.size.width = 1;
    NSInteger shadowEdge = _representer.interiorShadowEdge;
    if (shadowEdge == NSMaxXEdge) {
        lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
    } else if (shadowEdge == NSMinXEdge) {
        lineRect.origin.x = NSMinX(bounds);
    } else {
        lineRect = NSZeroRect;
    }
    
    if (NSIntersectsRect(lineRect, clipRect)) {
        NSRectFill(lineRect);
    }
    
#else
    
    
    if (NSIntersectsRect(lineRect, clipRect)) {
        // this looks better when we have no shadow
        [[NSColor lightGrayColor] set];
        NSRect bounds = self.bounds;
        NSRect lineRect = bounds;
        lineRect.origin.x += lineRect.size.width - 2;
        lineRect.size.width = 1;
        NSRectFill(NSIntersectionRect(lineRect, clipRect));
        [[NSColor whiteColor] set];
        lineRect.origin.x += 1;
        NSRectFill(NSIntersectionRect(lineRect, clipRect));	
    }
#endif
}

static inline int common_prefix_length(const char *a, const char *b) {
    int i;
    for (i=0; ; i++) {
        char ac = a[i];
        char bc = b[i];
        if (ac != bc || ac == 0 || bc == 0) break;
    }
    return i;
}

/* Drawing with NSLayoutManager is necessary because the 10_2 typesetting behavior used by the old string drawing does the wrong thing for fonts like Bitstream Vera Sans Mono.  Also it's an optimization for drawing the shadow. */
- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect {
#if TIME_LINE_NUMBERS
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
    NSUInteger previousTextStorageCharacterCount = [textStorage length];
    
    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
    NSRect textRect = self.bounds;
    textRect.size.height = _lineHeight;
    textRect.origin.y -= verticalOffset * _lineHeight;
    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
    unsigned long long lineValue = lineIndex * _bytesPerLine;
    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
    char previousBuff[256];
    int previousStringLength = (int)previousTextStorageCharacterCount;
    BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding];
    HFASSERT(conversionResult);
    while (linesRemaining--) {
        char formatString[64];
        [self getLineNumberFormatString:formatString length:sizeof formatString];
        
        if (NSIntersectsRect(textRect, clipRect)) {
            NSString *replacementCharacters = nil;
            NSRange replacementRange;
            char buff[256];
            int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
            HFASSERT(newStringLength > 0);
            int prefixLength = common_prefix_length(previousBuff, buff);
            HFASSERT(prefixLength <= newStringLength);
            HFASSERT(prefixLength <= previousStringLength);
            replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength);
            replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
            NSUInteger glyphCount;
            [textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
            if (previousTextStorageCharacterCount == 0) {
                NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
                [textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
                [atts release];
            }
            glyphCount = [layoutManager numberOfGlyphs];
            if (glyphCount > 0) {
                CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]);
                [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)];
            }
            previousTextStorageCharacterCount = newStringLength;
            [replacementCharacters release];
            memcpy(previousBuff, buff, newStringLength + 1);
            previousStringLength = newStringLength;
        }
        textRect.origin.y += _lineHeight;
        lineIndex++;
        lineValue = HFSum(lineValue, _bytesPerLine);
    }
#if TIME_LINE_NUMBERS
    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"Line number time: %f", endTime - startTime);
#endif
}

- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect {
    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
    NSRect textRect = self.bounds;
    textRect.size.height = _lineHeight;
    textRect.size.width -= 5;
    textRect.origin.y -= verticalOffset * _lineHeight + 1;
    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
    unsigned long long lineValue = lineIndex * _bytesPerLine;
    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
    if (! textAttributes) {
        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        [mutableStyle setAlignment:NSRightTextAlignment];
        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
        [mutableStyle release];
        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
        [paragraphStyle release];
    }
    
    char formatString[64];
    [self getLineNumberFormatString:formatString length:sizeof formatString];
    
    while (linesRemaining--) {
        if (NSIntersectsRect(textRect, clipRect)) {
            char buff[256];
            int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
            HFASSERT(newStringLength > 0);
            NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
            [string drawInRect:textRect withAttributes:textAttributes];
            [string release];
        }
        textRect.origin.y += _lineHeight;
        lineIndex++;
        if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow
    }
}

- (NSUInteger)characterCountForLineRange:(HFRange)range {
    HFASSERT(range.length <= NSUIntegerMax);
    NSUInteger characterCount;
    
    NSUInteger lineCount = ll2l(range.length);
    const NSUInteger stride = _bytesPerLine;
    HFLineCountingRepresenter *rep = self.representer;
    HFLineNumberFormat format = self.lineNumberFormat;
    if (format == HFLineNumberFormatDecimal) {
        unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
        characterCount = lineCount /* newlines */;
        while (lineCount--) {
            characterCount += HFCountDigitsBase10(lineValue);
            lineValue += stride;
        }
    }
    else if (format == HFLineNumberFormatHexadecimal) {
        characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines
    }
    else {
        characterCount = -1;
    }
    return characterCount;
}

- (NSString *)newLineStringForRange:(HFRange)range {
    HFASSERT(range.length <= NSUIntegerMax);
    if(range.length == 0)
        return [[NSString alloc] init]; // Placate the analyzer.
    
    NSUInteger lineCount = ll2l(range.length);
    const NSUInteger stride = _bytesPerLine;
    unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
    NSUInteger characterCount = [self characterCountForLineRange:range];
    char *buffer = check_malloc(characterCount);
    NSUInteger bufferIndex = 0;
    
    char formatString[64];
    [self getLineNumberFormatString:formatString length:sizeof formatString];
    
    while (lineCount--) {
        int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset);
        HFASSERT(charCount > 0);
        bufferIndex += charCount;
        buffer[bufferIndex++] = '\n';   
        lineValue += stride;
    }
    HFASSERT(bufferIndex == characterCount);
    
    NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES];
    return string;
}

- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining {
    const BOOL debug = NO;
    [textStorage beginEditing];
    
    if (storedLineCount == INVALID_LINE_COUNT) {
        /* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */
        NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
        [textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string];
        [string release];
        
    }
    else {
        HFRange leftRangeToReplace, rightRangeToReplace;
        HFRange leftRangeToStore, rightRangeToStore;
        
        HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount);
        HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining);
        HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange);
        
        if (rangeToPreserve.length == 0) {
            leftRangeToReplace = oldRange;
            leftRangeToStore = newRange;
            rightRangeToReplace = HFZeroRange;
            rightRangeToStore = HFZeroRange;
        }
        else {
            if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length);
            HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange));
            HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange));
            const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve);
            leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location);
            leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location);
            rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve);
            rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve);
        }
        
        if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange));
        if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore));
        if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore));
        
        HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange));
        HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange));
        
        if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) {
            NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace];
            NSRange rangeToDelete = NSMakeRange(0, charactersToDelete);
            if (leftRangeToStore.length == 0) {
                [textStorage deleteCharactersInRange:rangeToDelete];
                if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete));
            }
            else {
                NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore];
                [textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString];
                if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString);
                [leftRangeString release];
            }
        }
        
        if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) {
            NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace];
            NSUInteger stringLength = [textStorage length];
            HFASSERT(charactersToDelete <= stringLength);
            NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete);
            if (rightRangeToStore.length == 0) {
                [textStorage deleteCharactersInRange:rangeToDelete];
                if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete));
            }
            else {
                NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore];
                [textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString];
                if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore));
                [rightRangeString release];
            }
        }
    }
    
    if (! textAttributes) {
        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        [mutableStyle setAlignment:NSRightTextAlignment];
        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
        [mutableStyle release];
        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
        [paragraphStyle release];
        [textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
    }
    
    [textStorage endEditing];
    
#if ! NDEBUG
    NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
    if (! [comparisonString isEqualToString:[textStorage string]]) {
        NSLog(@"Not equal!");
        NSLog(@"Expected:\n%@", comparisonString);
        NSLog(@"Actual:\n%@", [textStorage string]);
    }
    HFASSERT([comparisonString isEqualToString:[textStorage string]]);
    [comparisonString release];
#endif
    
    storedLineIndex = startingLineIndex;
    storedLineCount = linesRemaining;
}

- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect {
    USE(clipRect);
    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
    
    CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
    CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
    NSRect textRect = self.bounds;
    textRect.size.width -= 5;
    textRect.origin.y -= verticalOffset;
    textRect.size.height += verticalOffset;
    
    if (! textAttributes) {
        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        [mutableStyle setAlignment:NSRightTextAlignment];
        [mutableStyle setMinimumLineHeight:_lineHeight];
        [mutableStyle setMaximumLineHeight:_lineHeight];
        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
        [mutableStyle release];
        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
        [paragraphStyle release];
    }    
    
    
    NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
    [string drawInRect:textRect withAttributes:textAttributes];
    [string release];
}

- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
    USE(clipRect);
    const CGFloat cellTextContainerPadding = 2.f;
    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
    
    CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
    CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
    NSRect textRect = self.bounds;
    textRect.size.width -= 5;
    textRect.origin.y -= verticalOffset;
    textRect.origin.x += cellTextContainerPadding;
    textRect.size.height += verticalOffset;
    
    if (! textAttributes) {
        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        [mutableStyle setAlignment:NSRightTextAlignment];
        [mutableStyle setMinimumLineHeight:_lineHeight];
        [mutableStyle setMaximumLineHeight:_lineHeight];
        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
        [mutableStyle release];
        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
        [paragraphStyle release];
    }
    
    NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes];
    [string release];
    NSCell *cell = [[NSCell alloc] initTextCell:@""];
    [cell setAttributedStringValue:attributedString];
    [cell drawWithFrame:textRect inView:self];
    [[NSColor purpleColor] set];
    NSFrameRect(textRect);
    [cell release];
    [attributedString release];
}

- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect {
    USE(clipRect);
    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
    if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) {
        [self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining];
    }
    
    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
    
    NSPoint textPoint = self.bounds.origin;
    textPoint.y -= verticalOffset * _lineHeight;
    [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint];
}

- (void)drawLineNumbersWithClip:(NSRect)clipRect {
#if TIME_LINE_NUMBERS
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
    NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
    switch (drawingMode) {
        // Drawing can't be done right if fonts are wider than expected, but all
        // of these have rather nasty behavior in that case. I've commented what
        // that behavior is; the comment is hypothetical 'could' if it shouldn't
        // actually be a problem in practice.
        // TODO: Make a drawing mode that is "Fonts could get clipped if too wide"
        //       because that seems like better behavior than any of these.
        case 0:
            // Most fonts are too wide and every character gets piled on right (unreadable).
            [self drawLineNumbersWithClipLayoutManagerPerLine:clipRect];
            break;
        case 1:
            // Last characters could get omitted (*not* clipped) if too wide.
            // Also, most fonts have bottoms clipped (very unsigntly).
            [self drawLineNumbersWithClipStringDrawing:clipRect];
            break;
        case 2:
            // Most fonts are too wide and wrap (breaks numbering).
            [self drawLineNumbersWithClipFullLayoutManager:clipRect];
            break;
        case 3:
            // Fonts could wrap if too wide (breaks numbering).
            // *Note that that this is the only mode that generally works.*
            [self drawLineNumbersWithClipSingleStringDrawing:clipRect];
            break;
        case 4:
            // Most fonts are too wide and wrap (breaks numbering).
            [self drawLineNumbersWithClipSingleStringCellDrawing:clipRect];
            break;
    }
#if TIME_LINE_NUMBERS
    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"Line number time: %f", endTime - startTime);
#endif
}

- (void)drawRect:(NSRect)clipRect {
    [self drawGradientWithClip:clipRect];
    [self drawDividerWithClip:clipRect];
    [self drawLineNumbersWithClip:clipRect];
}

- (void)setLineRangeToDraw:(HFFPRange)range {
    if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) {
        _lineRangeToDraw = range;
        [self setNeedsDisplay:YES];
    }
}

- (void)setBytesPerLine:(NSUInteger)val {
    if (_bytesPerLine != val) {
        _bytesPerLine = val;
        storedLineCount = INVALID_LINE_COUNT;
        [self setNeedsDisplay:YES];
    }
}

- (void)setLineNumberFormat:(HFLineNumberFormat)format {
    if (format != _lineNumberFormat) {
        _lineNumberFormat = format;
        storedLineCount = INVALID_LINE_COUNT;
        [self setNeedsDisplay:YES];
    }
}

- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont {
    NSString *name = [testFont fontName];
    // No, Menlo does not work here.
    return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"];
}

- (void)setFont:(NSFont *)val {
    if (val != _font) {
        [_font release];
        _font = [val copy];
        [textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render
        [textAttributes release];
        textAttributes = nil;
        storedLineCount = INVALID_LINE_COUNT;
        useStringDrawingPath = [self canUseStringDrawingPathForFont:_font];
        [self setNeedsDisplay:YES];
    }
}

- (void)setLineHeight:(CGFloat)height {
    if (_lineHeight != height) {
        _lineHeight = height;
        [self setNeedsDisplay:YES];
    }
}

- (void)setFrameSize:(NSSize)size {
    [super setFrameSize:size];
    [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
}

- (void)mouseDown:(NSEvent *)event {
    USE(event);
    // [_representer cycleLineNumberFormat];
}

- (void)scrollWheel:(NSEvent *)scrollEvent {
    [_representer.controller scrollWithScrollEvent:scrollEvent];
}

+ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format {
    switch (format) {
        case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber);
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
        case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber);
#else
        case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber);
#endif
        default: return 0;
    }
}

@end