// Copyright 1997-2002 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 "OWCSSSelectorGroup.h"

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


#import "OWCSSDeclarations.h"
#import "OWCSSSelector.h"
#import "OWCSSTokenizer.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/CSS.subproj/OWCSSSelectorGroup.m,v 1.28 2002/03/09 01:53:48 kc Exp $")

@interface OWCSSSelectorGroup (Private)
@end

@implementation OWCSSSelectorGroup

BOOL OWCSSSelectorGroupDebug = NO;


// Init and dealloc

- initWithTokenizer:(OWCSSTokenizer *)tokenizer withPosition:(unsigned int)newPosition;
{
    NSMutableArray *mutableSelectorList;
    OWCSSSelector *selector = nil;
    BOOL endOfSelectors = NO;

    if (![super init])
        return nil;
        
    mutableSelectorList = [[NSMutableArray alloc] init];
        
    while (!endOfSelectors) {
        id tokenValue;

        switch ([tokenizer getNextToken:&tokenValue excludingIdentifiers:YES excludingNumbers:YES]) {

            case OWCSSTokenEOF:
                endOfSelectors = YES;
                break;
        
            case OWCSSTokenWhitespace: // [ ]tag.class#id:pseudoclass 
                selector = nil;
                break;

            case OWCSSTokenString:
                if (selector == nil) {
                    if (OWCSSSelectorGroupDebug) NSLog(@"\tnew selector created for string <%@>", tokenValue);
                    selector = [[OWCSSSelector alloc] init];
                    [mutableSelectorList addObject:selector];
                    [selector release];
                }
                    
                if ([tokenValue characterAtIndex:0] != '#') {
		    // [tag].class#id:pseudoclass 
                    if (OWCSSSelectorGroupDebug) NSLog(@"\ta tag called '%@'", tokenValue);
                    [selector setTagName:[tokenValue lowercaseString]]; // may be nil
                } else {
                    NSString *idName;
                    
                    // tag.class[#id]:pseudoclass 
                    if (OWCSSSelectorGroupDebug) NSLog(@"\tan id called '%@'", tokenValue);
                    idName = [tokenValue substringFromIndex:1];
                    if (!isdigit([idName firstCharacter]))
                        [selector setIDName:idName];
                    else {
                        [tokenizer ungetLastToken];
                        endOfSelectors = YES;
                    }
                }
                break;
            
            case OWCSSTokenPunctuation: {
                unichar punctuation;

                if (OWCSSSelectorGroupDebug) NSLog(@"\tfound punctuation %@", tokenValue);

                punctuation = [tokenValue characterAtIndex:0];
                if (punctuation == '{') {
                    // If we hit left brace with no other selector, it means that this style is the top level.
                    if (selector == nil && [mutableSelectorList count] == 0) {
                        if (OWCSSSelectorGroupDebug) NSLog(@"\tnew selector created for empty selector list, meaning this is a global style sheet", tokenValue);
                        selector = [[OWCSSSelector alloc] init];
                        [mutableSelectorList addObject:selector];
                        [selector release];
                    }
                    [tokenizer ungetLastToken];
                    endOfSelectors = YES;
                } else if (punctuation == ',') {
                    // Break if we hit comma
                    [tokenizer ungetLastToken];
                    endOfSelectors = YES;
                } else {
        
                    if ([tokenizer getNextToken:&tokenValue excludingIdentifiers:YES excludingNumbers:YES] != OWCSSTokenString) {
                        if (OWCSSSelectorGroupDebug) NSLog(@"\but we failed to find a string (found %@)", tokenValue);
                        [tokenizer ungetLastToken];
                        endOfSelectors = YES;
                    } else {
                        if (selector == nil) {
                            if (OWCSSSelectorGroupDebug) NSLog(@"\tnew selector created for punctuation '%@'", tokenValue);
                            selector = [[OWCSSSelector alloc] init];
                            [mutableSelectorList addObject:selector];
                            [selector release];
                        }

                        switch (punctuation) {
                            case '.':
                                if (OWCSSSelectorGroupDebug) NSLog(@"\ta class called '%@'", tokenValue);
                                // tag[.class]#id:pseudoclass 
                                [selector setClassName:tokenValue];
                                break;
                            case ':':
                                if (OWCSSSelectorGroupDebug) NSLog(@"\ta pseudoclass called '%@'", tokenValue);
                                // tag.class#id[:pseudoclass] 
                                [selector setPseudoClassName:tokenValue];
                                break;
                            default:
                                NSLog(@"\tCSS encountered strange punctuation '%c' and value '%@'.", punctuation, tokenValue);
#warning This "not reached" assertion is reached frequently
    // For example, when we scan a stylesheet that begins with <STYLE TYPE="text/css"> ... (which is totally wrong but also very common), we will reach this point.   
                                OBASSERT(NO /* not reached */);
                                break;
                        }
                    }
                }
                break;
            }
            
            default:
                [tokenizer ungetLastToken];
                endOfSelectors = YES;
                break;
        }
    }
    
    if (OWCSSSelectorGroupDebug) NSLog(@"Got selectors '%@'", mutableSelectorList);
    if ([mutableSelectorList count] == 0) {
        [mutableSelectorList release];
        [self release];
        return nil;
    }
    
    selectorList = mutableSelectorList;
    position = newPosition;
    score = 0;
    
    return self;
}

- init;
{
    OBRejectUnusedImplementation(self, _cmd);
    return nil;
}

- (void)dealloc;
{
    [selectorList release];
    [declarations release];
    [super dealloc];
}

// API

- (void)setDeclarations:(OWCSSDeclarations *)newDeclarations;
{
    if (declarations == newDeclarations)
        return;
    [declarations release];
    declarations = [newDeclarations retain];
}

- (OWCSSDeclarations *)declarations;
{
    return declarations;
}

- (OWCSSSelector *)lastSelector;
{
    return [selectorList lastObject];
}

- (NSArray *)selectorList;
{
    return selectorList;
}

- (unsigned int)score;
{
    if (score == 0) {
        unsigned int selectorCount = [selectorList count];
        unsigned int selectorIndex;
        for (selectorIndex = 0; selectorIndex < selectorCount; selectorIndex++) {
            OWCSSSelector *selector = [selectorList objectAtIndex:selectorIndex];
            score += [selector score];
        }
    }
    //NSLog(@"score for group: '%@' is '%d'", [selectorList description], score); 
    return score;
}

- (unsigned int)count;
{
    return [selectorList count];
}

- (unsigned int)position;
{
    return position;
}

- (NSComparisonResult) compare:(OWCSSSelectorGroup *)group;
{
    if ([group score] > [self score])
        return NSOrderedAscending;
    else if (([group score] == [self score]) && ([group position] > [self position]))
        return NSOrderedAscending;
    else
        return NSOrderedDescending;
}

@end

@implementation OWCSSSelectorGroup (Private)

- (NSString *)descriptionWithLocale:(NSDictionary *)locale indent:(unsigned)level;
{
    return [NSString stringWithFormat:@"{selectorList = %@, declarations = %@}", selectorList, declarations];
}

@end
