// Copyright 1997-1999 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 "NSAttributedString-OAExtensions.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>

#import "OAColorPalette.h"

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniAppKit/OpenStepExtensions.subproj/NSAttributedString-OAExtensions.m,v 1.5 1999/03/30 21:25:59 kc Exp $")

@implementation NSAttributedString (OAExtentions)

static NSDictionary *keywordDictionary = nil;
static NSFontTraitMask mask = NO;
static BOOL underline = NO;
static float size = 12.0;

- (void)initKeywordDictionary;
{
    if (keywordDictionary)
        return;

    keywordDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:
	    @"&amp", @"&",
	    @"&lt", @"<",
	    @"&gt", @">",
        nil] retain];
}

- (void)resetAttributes;
{
    [self initKeywordDictionary];

    mask = 0;
    underline = NO;
    size = 12;
}

- (void)setBold:(BOOL)newBold;
{
    if (newBold)
	mask |= NSBoldFontMask;
    else
	mask &= ~NSBoldFontMask;
}

- (void)setItalic:(BOOL)newItalic;
{
    if (newItalic)
	mask |= NSItalicFontMask;
    else
	mask &= ~NSItalicFontMask;
}

- (void)setUnderline:(BOOL)newUnderline;
{
    underline = newUnderline;
}

- (void)setCurrentAttributes:(NSMutableAttributedString *)attrString;
{
    NSRange range;
    NSFont *font;
    NSFontManager *fontManager;
    NSMutableDictionary *attrDict;

    range.location = 0;
    range.length = [attrString length];

    fontManager = [NSFontManager sharedFontManager];
    font = [fontManager fontWithFamily:@"Helvetica" traits:mask weight:5 size:size];

    attrDict = [NSMutableDictionary dictionaryWithCapacity:0];
    [attrDict setObject:font forKey:NSFontAttributeName];
    [attrDict setObject:[NSNumber numberWithBool:underline] forKey:NSUnderlineStyleAttributeName];

    [attrString addAttributes:attrDict range:range];
}

- (NSAttributedString *)initWithHTML:(NSString *)htmlString;
{
    NSMutableAttributedString *attributedString;
    NSRange range;
    NSScanner *htmlScanner;
    NSMutableString *strippedString;

    [self resetAttributes];

    strippedString = [NSMutableString stringWithCapacity:[htmlString length]];
    htmlScanner = [NSScanner scannerWithString:htmlString];
    while (![htmlScanner isAtEnd]) {
        NSString *token;

        [htmlScanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&token];
        [htmlScanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:NULL];
        [strippedString appendFormat:@"%@ ", token];
    }

    attributedString = [[[NSMutableAttributedString alloc] init] autorelease];

    htmlScanner = [NSScanner scannerWithString:strippedString];
    while (![htmlScanner isAtEnd]) {
        NSString *firstPass, *tagString, *newString;
        NSScanner *keywordScanner;
        NSCharacterSet *openTagSet;
        NSCharacterSet *closeTagSet;
        NSMutableAttributedString *newAttributedString;

        openTagSet = [NSCharacterSet characterSetWithCharactersInString:@"<"];
        closeTagSet = [NSCharacterSet characterSetWithCharactersInString:@">"];
        newAttributedString = [[[NSMutableAttributedString alloc] init] autorelease];

        if ([htmlScanner scanUpToCharactersFromSet:openTagSet intoString:&firstPass]) {
            keywordScanner = [NSScanner scannerWithString:firstPass];
            while (![keywordScanner isAtEnd]) {
                NSString *keyword = nil;
                BOOL knownTag = NO;
                NSCharacterSet *keywordTag;
                NSEnumerator *keyEnum;

                keywordTag = [NSCharacterSet characterSetWithCharactersInString:@"&"];
                keyEnum = [[keywordDictionary allKeys] objectEnumerator];
                [keywordScanner scanUpToCharactersFromSet:keywordTag intoString:&newString];
                [newAttributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:newString]autorelease]];

                while (![keywordScanner isAtEnd] && (keyword = [keyEnum nextObject]))
                    if ([keywordScanner scanString:keyword intoString:NULL]) {
                        [newAttributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:[keywordDictionary objectForKey:keyword]]autorelease]];
                        knownTag = YES;
                    }
                if (!knownTag && [keywordScanner scanCharactersFromSet:keywordTag intoString:&newString])
                    [newAttributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:newString]autorelease]];
            }

            [self setCurrentAttributes:newAttributedString];
            [attributedString appendAttributedString:newAttributedString];
        }
        
        // Either we hit a '<' or we're at the end of the text.
        if (![htmlScanner isAtEnd]) {
            [htmlScanner scanCharactersFromSet:openTagSet intoString:NULL];
            [htmlScanner scanUpToCharactersFromSet:closeTagSet intoString:&tagString];
            [htmlScanner scanCharactersFromSet:closeTagSet intoString:NULL];

            if ([tagString isEqual:@"b"])
                [self setBold:YES];
            else if ([tagString isEqual:@"/b"])
                [self setBold:NO];
            else if ([tagString isEqual:@"i"])
                [self setItalic:YES];
            else if ([tagString isEqual:@"/i"])
                [self setItalic:NO];
            else if ([tagString isEqual:@"u"])
                [self setUnderline:YES];
            else if ([tagString isEqual:@"/u"])
                [self setUnderline:NO];
            else if ([tagString isEqual:@"p"])
                [attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\n\n"]autorelease]];
            else if ([tagString isEqual:@"br"])
                [attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\n"]autorelease]];
            else if ([tagString isEqual:@"/font"])
                size = 12.0;
            else if ([tagString hasPrefix:@"font size="] || [tagString hasPrefix:@"font size ="]) {
                float foo;
                if (sscanf([tagString cString], "font size = +%f", &foo) == 1)
                    size += foo + 9;
                else if (sscanf([tagString cString], "font size = %f", &foo) == 1)
                    size = foo + 9;
            }
        }
    }

    range.location = 0;
    range.length = [attributedString length];

    return [self initWithAttributedString:attributedString];
}


// Generating HTML
static NSMutableDictionary *cachedAttributes = nil;
static NSMutableArray *fontDirectiveStack = nil;

void resetAttributeTags()
{
    if (cachedAttributes)
        [cachedAttributes release];
    if (!fontDirectiveStack)
        fontDirectiveStack = [[NSMutableArray alloc] initWithCapacity:0];
    else
        [fontDirectiveStack removeAllObjects];
    cachedAttributes = nil;
}

- (void)pushFontDirective:(NSString *)aDirective;
{
    [fontDirectiveStack addObject:aDirective];
}

- (void)popFontDirective;
{
    if ([fontDirectiveStack count])
        [fontDirectiveStack removeLastObject];
}

NSString *attributeTagString(NSDictionary *effectiveAttributes)
{
    NSFont *newFont, *oldFont;
    NSColor *newColor, *oldColor;
    NSMutableString *tagString;
    BOOL wasUnderlined = NO, underlined;

    if ([cachedAttributes isEqualToDictionary:effectiveAttributes])
        return nil;

    tagString = [NSMutableString stringWithCapacity:0];

    newFont = [effectiveAttributes objectForKey:NSFontAttributeName];
    oldFont = [cachedAttributes objectForKey:NSFontAttributeName];
    if (newFont != oldFont) {
        NSFontTraitMask newTraits;
        float oldSize, size;
        BOOL wasBold, wasItalic, bold, italic;
        NSFontManager *fontManager = [NSFontManager sharedFontManager];
        size = [newFont pointSize];
        newTraits = [fontManager traitsOfFont:newFont];
        bold = newTraits & NSBoldFontMask;
        italic = newTraits & NSItalicFontMask;

        if (oldFont) {
            NSFontTraitMask oldTraits;
            oldTraits = [fontManager traitsOfFont:oldFont];
            wasBold = oldTraits & NSBoldFontMask;
            wasItalic = oldTraits & NSItalicFontMask;
            oldSize = [oldFont pointSize];
        } else {
            wasBold = wasItalic = NO;
            oldSize = 12.0;
        }

        if (bold && !wasBold)
            [tagString appendString:@"<b>"];
        else if (!bold && wasBold)
            [tagString appendString:@"</b>"];

        if (italic && !wasItalic)
            [tagString appendString:@"<i>"];
        else if (!italic && wasItalic)
            [tagString appendString:@"</i>"];

        if (size != oldSize) {
            if (oldFont)
                [tagString appendString:@"</font>"];
            [tagString appendFormat:@"<font size=%d>", (int)size - 9];
        }
    }

    underlined = [[effectiveAttributes objectForKey:NSUnderlineStyleAttributeName] boolValue];
    wasUnderlined = [[cachedAttributes objectForKey:NSUnderlineStyleAttributeName] boolValue];
    if (underlined && !wasUnderlined)
        [tagString appendString:@"<u>"];
    else if (!underlined && wasUnderlined)
        [tagString appendString:@"</u>"];

    oldColor = [cachedAttributes objectForKey:NSForegroundColorAttributeName];
    newColor = [effectiveAttributes objectForKey:NSForegroundColorAttributeName];
    if (oldColor != newColor) {
        if (oldColor)
            [tagString appendString:@"</font>"];
        [tagString appendFormat:@"<font color=%@>", [OAColorPalette stringForColor:newColor]];
    }

    if (cachedAttributes)
        [cachedAttributes release];
    cachedAttributes = [effectiveAttributes retain];

    return tagString;
}

- (NSString *)closeTags;
{
    NSMutableString *closeTagsString;
    NSFontTraitMask traits;
    NSFontManager *fontManager;
    NSFont *font;

    closeTagsString = [NSMutableString stringWithCapacity:0];
    fontManager = [NSFontManager sharedFontManager];
    font = [cachedAttributes objectForKey:NSFontAttributeName];
    if (([font pointSize] != 12.0) ||
        (![[cachedAttributes objectForKey:NSForegroundColorAttributeName] isEqual:[OAColorPalette colorForString:@"black"]]))
        [closeTagsString appendString:@"</font>"];

    traits = [fontManager traitsOfFont:font];
    if (traits & NSBoldFontMask)
        [closeTagsString appendString:@"</b>"];
    if (traits & NSItalicFontMask)
        [closeTagsString appendString:@"</i>"];
    if ([[cachedAttributes objectForKey:NSUnderlineStyleAttributeName] boolValue])
        [closeTagsString appendString:@"</u>"];

    return closeTagsString;
}

- (NSString *)htmlString;
{
    NSDictionary *effectiveAttributes;
    NSRange range, searchRange;
    int pos = 0;
    NSCharacterSet *newLineCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\n"];
    NSMutableString *storeString = [NSMutableString stringWithCapacity:[self length]];

    resetAttributeTags();
    while ((pos < [self length]) &&
           (effectiveAttributes = [self attributesAtIndex:pos effectiveRange:&range])) {
        NSString *markupString = attributeTagString(effectiveAttributes);
        if (markupString)
            [storeString appendString:markupString];
        [storeString appendString:[[self attributedSubstringFromRange:range] string]];

        pos = range.location + range.length;
    }

    // Replace \n with <br> and 2 or more \n with <p>
    searchRange.location = 0;
    searchRange.length = [storeString length];
    for (range = [storeString rangeOfCharacterFromSet:newLineCharacterSet options:NSLiteralSearch range:searchRange];
         range.length;
         range = [storeString rangeOfCharacterFromSet:newLineCharacterSet options:NSLiteralSearch range:searchRange]) {
        while (((range.location+range.length) < [storeString length]) &&
               ([storeString characterAtIndex:(range.location+range.length)] == '\n'))
            range.length++;
        if (range.length == 1)
            [storeString replaceCharactersInRange:range withString:@"<br>\n"];
        else
            [storeString replaceCharactersInRange:range withString:@"\n<p>\n"];

        searchRange.location = range.location + 5;
        searchRange.length = [storeString length] - searchRange.location;
    }

    [storeString appendString:[self closeTags]];

    return storeString;
}

- (NSData *)rtf;
{
    return [self RTFFromRange:NSMakeRange(0, [self length]) documentAttributes:nil];
}

@end
