// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHHTMLAnchor.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OmniAppKit/OmniAppKit.h>
#import <OWF/OWF.h>

#import "OHColorPalette.h"
#import "OHHTMLPageView.h"
#import "OHHTMLView.h"
#import "OWScriptEvent.h"
#import "OWScriptEventHandlerHolder.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/View.subproj/OHHTMLAnchor.m,v 1.21 2000/01/20 00:28:03 kc Exp $")

@implementation OHHTMLAnchor

- initWithColorPalette:(OHColorPalette *)aColorPalette;
{
    if (![super init] || !aColorPalette)
	return nil;

    characterRange = NSMakeRange(NSNotFound, 0);
    colorPalette = nil;
    [self setColorPalette:aColorPalette];
    flags.hasOverrideColor = NO;

    return self;
}

- init;
{
    return [self initWithColorPalette:[OHColorPalette defaultPalette]];
}

- (void)dealloc;
{
    [colorPalette release];
    [lastColor release];
    [super dealloc];
}

// Override setAddress: to keep our color consistent

- (void)setAddress:(OWAddress *)anAddress;
{
    [super setAddress:anAddress];
    [self mainThreadPerformSelectorOnce:@selector(updateColor)];
}

// Our location

- (OHHTMLView *)htmlView;
{
    return nonretainedHTMLView;
}

- (void)setHTMLView:(OHHTMLView *)newHTMLView;
{
    nonretainedHTMLView = newHTMLView;
}

- (NSRange)range;
{
    return characterRange;
}

- (void)setRange:(NSRange)aRange;
{
    characterRange = aRange;
}

- (void)addContainedCell:(OHBasicCell *)newContainingCell;
{
    if ([newContainingCell isMarginalCell]) {
        if (!nonretainedContainedMarginalCells)
            nonretainedContainedMarginalCells = [[NSMutableArray alloc] init];
        [nonretainedContainedMarginalCells addObject:[NSValue valueWithNonretainedObject:newContainingCell]];
    }
}

- (void)removeContainedCell:(OHBasicCell *)oldContainingCell;
{
    if (nonretainedContainedMarginalCells) {
        NSValue *value;

        value = [NSValue valueWithNonretainedObject:oldContainingCell];

        // The NSValue will be equal to any NSValue that was created from the same object pointer
        [nonretainedContainedMarginalCells removeObject:value];
        if (![nonretainedContainedMarginalCells count]) {
            [nonretainedContainedMarginalCells release];
            nonretainedContainedMarginalCells = nil;
        }
    }
}

- (const NSRect *)rectArrayAndCount:(unsigned int *)returnRectCountPtr;
{
    if (nonretainedContainedMarginalCells) {
#warning This is not thread-safe (or even safe to recurse over), is it ever called from multiple threads?
        // If so, we should really add a lock or something
        static NSMutableData *rectArrayData = nil;
        unsigned int dataLength;
        const NSRect *characterRectArray;
        NSRect *rectArray;
        unsigned int characterRectCount, cellIndex, cellCount;

        characterRectArray = [nonretainedHTMLView rectArrayForCharacterRange:characterRange rectCount:&characterRectCount];
        cellCount = [nonretainedContainedMarginalCells count];
        dataLength = (characterRectCount + cellCount) * sizeof(NSRect);
        if (!rectArrayData) {
            rectArrayData = [[NSMutableData alloc] initWithLength:dataLength];
        } else if ([rectArrayData length] < dataLength) {
            [rectArrayData setLength:dataLength];
        }
        [rectArrayData replaceBytesInRange:NSMakeRange(cellCount * sizeof(NSRect), characterRectCount * sizeof(NSRect)) withBytes:characterRectArray];

        rectArray = [rectArrayData mutableBytes];
        OBASSERT(rectArray != NULL);
        for (cellIndex=0; cellIndex < cellCount; cellIndex++)
            rectArray[cellIndex] = [[(NSValue *)[nonretainedContainedMarginalCells objectAtIndex:cellIndex] nonretainedObjectValue] contentFrame];
        *returnRectCountPtr = characterRectCount + cellCount;
        return rectArray;
    } else if (nonretainedHTMLView) {
        return [nonretainedHTMLView rectArrayForCharacterRange:characterRange rectCount:returnRectCountPtr];
    } else {
        *returnRectCountPtr = 0;
        return NULL;
    }
}

static inline NSRect expandUnionRectToContainRect(NSRect unionRect, NSRect aRect)
{
    if (NSMinX(aRect) < NSMinX(unionRect))
        unionRect.origin.x = NSMinX(aRect);
    if (NSMinY(aRect) < NSMinY(unionRect))
        unionRect.origin.y = NSMinY(aRect);
    if (NSMaxX(aRect) > NSMaxX(unionRect))
        unionRect.size.width = NSMaxX(aRect) - NSMinX(unionRect);
    if (NSMaxY(aRect) > NSMaxY(unionRect))
        unionRect.size.height = NSMaxY(aRect) - NSMinY(unionRect);
    return unionRect;
}

- (NSRect)containingRect;
{
    const NSRect *rectArray;
    unsigned int rectIndex, rectCount;
    NSRect unionRect;

    rectArray = [self rectArrayAndCount:&rectCount];
    if (rectCount == 0)
        return NSMakeRect(-1.0, -1.0, 0.0, 0.0);
    unionRect = rectArray[0];
    for (rectIndex = 1; rectIndex < rectCount; rectIndex++) {
        unionRect = expandUnionRectToContainRect(unionRect, rectArray[rectIndex]);
    }
    return unionRect;
}

- (NSRect)containingRectForScroll;
{
    NSRect anchorRect;

    anchorRect = [self containingRect];

    if (characterRange.location == 0 && NSHeight(anchorRect) != 0.0) {
        // If we're scrolling to make the first character visible, include the view's origin.
        anchorRect = NSUnionRect(anchorRect, NSMakeRect(0.0, 0.0, 1.0, 1.0));
    }
    return anchorRect;
}

- (BOOL)mouseInAnchorAtPoint:(NSPoint)aPoint;
{
    const NSRect *rectArray;
    unsigned int rectIndex, rectCount;

    rectArray = [self rectArrayAndCount:&rectCount];
    for (rectIndex = 0; rectIndex < rectCount; rectIndex++) {
        if (NSMouseInRect(aPoint, rectArray[rectIndex], YES))
            return YES;
    }
    return NO;
}

//

- (void)followFromEvent:(NSEvent *)event;
{
    OWScriptEvent *click;

    click = [[OWScriptEvent alloc] initWithType:OWSE_Click fromEvent:event];
    [click setTarget:self action:@selector(followFromScriptEvent:)];
    [click setInstigator:self];

    [click addResponder:eventHandlers];

    [click deliverAsynchronously];
    [click release];
}

- (void)followFromScriptEvent:(OWScriptEvent *)scriptEvent;
{
    [[nonretainedHTMLView htmlPageView] followAnchor:self withModifierFlags:[[scriptEvent nsEvent] modifierFlags]];
}


//

- (OHColorPalette *)colorPalette;
{
    return colorPalette;
}

- (void)setColorPalette:(OHColorPalette *)aColorPalette;
{
    if (aColorPalette == colorPalette)
	return;
    [colorPalette release];
    colorPalette = [aColorPalette retain];
}

- (BOOL)hasOverrideColor;
{
    return flags.hasOverrideColor;
}

- (void)setHasOverrideColor;
{
    flags.hasOverrideColor = YES;
}

- (OHColorPaletteIndex)colorIndex;
{
    if (!address)
        return OHColorPaletteTextIndex;
    if ([address isVisited])
        return OHColorPaletteVisitedLinkIndex;
    return OHColorPaletteLinkIndex;
}

- (NSColor *)color;
{
    if (flags.hasOverrideColor)
        return nil;
    else
        return [colorPalette colorAtColorIndex:[self colorIndex]];
}

- (void)updateColor;
{
    [self setColorToIndex:[self colorIndex]];
}

- (void)setColorToIndex:(unsigned int)newColorIndex;
{
    NSColor *newColor;
    NSRange clippedRange;
    NSTextStorage *textStorage;

    if (flags.hasOverrideColor)
        return;
    
    newColor = [colorPalette colorAtColorIndex:newColorIndex];
    if (newColor == lastColor)
        return;

    [lastColor release];
    lastColor = [newColor retain];

    if (nonretainedContainedMarginalCells) {
        [self invalidateDisplay];
	return;
    }

    textStorage = [nonretainedHTMLView textStorage];
    clippedRange = NSIntersectionRange(NSMakeRange(0, [textStorage length]), characterRange);
    if (!textStorage || clippedRange.length == 0)
        return; // Don't need to redraw

    [textStorage beginEditing];
    [textStorage addAttribute:NSForegroundColorAttributeName value:lastColor range:clippedRange];
    [textStorage endEditing];
    [self invalidateDisplay];
}

- (void)invalidateDisplay;
{
    if (nonretainedContainedMarginalCells)
        [nonretainedHTMLView setNeedsDisplayInRect:[self containingRect]];
    else
        [nonretainedHTMLView setNeedsDisplayForCharacterRange:characterRange];
}

- (NSString *)label;
{
    NSRange clippedRange;
    NSTextStorage *textStorage;
    NSString *label;

    if (!nonretainedHTMLView)
        return nil;
    textStorage = [nonretainedHTMLView textStorage];
    clippedRange = NSIntersectionRange(NSMakeRange(0, [textStorage length]), characterRange);
    if (!textStorage || clippedRange.length == 0)
        return @"";

    label = [[textStorage string] substringWithRange:clippedRange];
    // TODO: Strip out all attachment characters, rather than filtering out just those strings that only contain an attachment?
    if ([label length] == 1 && [label characterAtIndex:0] == NSAttachmentCharacter)
        return @"";
    return label;
}


// Scrolling

- (void)scrollToTop;
{
    if (nonretainedContainedMarginalCells) {
        if (characterRange.length) {
        NSPoint anchorPoint;

        anchorPoint.x = 0.0;
        anchorPoint.y = NSMinY([self containingRectForScroll]);
        [nonretainedHTMLView scrollPoint:anchorPoint];
        } else
            [nonretainedHTMLView scrollPoint:[self containingRect].origin];
    } else
        [nonretainedHTMLView scrollCharacterRangeToTop:characterRange];
}

- (void)scrollToVisible;
{
    if (nonretainedContainedMarginalCells)
        [nonretainedHTMLView scrollRectToVisible:[self containingRectForScroll]];
    else
        [nonretainedHTMLView scrollCharacterRangeToVisible:characterRange];
}

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];
    if (nonretainedHTMLView) {
        [debugDictionary setObject:OBShortObjectDescription(nonretainedHTMLView) forKey:@"nonretainedHTMLView"];
        [debugDictionary setObject:[NSString stringWithFormat:@"(%d, %d)", characterRange.location, characterRange.length] forKey:@"characterRange"];
    }
    if (nonretainedContainedMarginalCells)
        [debugDictionary setObject:[nonretainedContainedMarginalCells shortDescription] forKey:@"nonretainedContainedMarginalCells"];
    if ([self label])
        [debugDictionary setObject:[self label] forKey:@"label"];

    return debugDictionary;
}

@end
