// 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 "OWScriptContext.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OWF/OWF.h>
#import "OHHTMLDisplayProcessor.h"
#import "OWScriptDocumentProxy.h"
#import "OWScriptBlockProcessor.h"
#import "OHHTMLDocument.h"
#import "OWContentInfo-Scripting.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Scripting.subproj/OWScriptContext.m,v 1.15 2000/01/20 00:27:56 kc Exp $")

@implementation OWScriptContext

static int OHHTMLDisplayProcessorScriptingDebugLevel = 2;

BOOL OHScriptLanguageCaseInsensitive = YES;

static NSMutableDictionary *scriptLanguages = nil;
static NSMutableDictionary *frameUseMapping = nil;

NSString *OWScriptContextInvalidationNotification = @"OWScriptContextInvalidationNotification";

+ (void)initialize;
{
    [super initialize];

    if (!frameUseMapping)
        frameUseMapping = [[NSMutableDictionary alloc] init];
}

+ (void)registerScriptLanguage:(NSString *)language className:(NSString *)name;
{
    if (OHHTMLDisplayProcessorScriptingDebugLevel >= 2) {
        NSLog(@"Registering class <%@> for language <%@>", name, language);
    }
    if (!scriptLanguages)
        scriptLanguages = [[NSMutableDictionary alloc] init];
    [scriptLanguages setObject:name forKey:language];
}

+ (Class)interpreterClassForScriptLanguage:(NSString *)languageName
{
    NSString *className;

    if (!scriptLanguages)
        return Nil;

    className = [scriptLanguages objectForKey:languageName];

    if (!className && OHScriptLanguageCaseInsensitive) {
        NSEnumerator *languageNames = [scriptLanguages keyEnumerator];
        NSString *aName;

        while((aName = [languageNames nextObject])) {
            if ([languageName caseInsensitiveCompare:aName] == NSOrderedSame) {
                className = [scriptLanguages objectForKey:aName];
                break;
            }
        }
    }

    return className ? [OFBundledClass classNamed:className] : Nil;
}

+ (void)registerItemName:(NSString *)itemName bundle:(NSBundle *)bundle description:(NSDictionary *)description;
{
    NSArray *languages;
    NSDictionary *contentTypes;

    languages = [description objectForKey:@"ScriptLanguages"];
    if (languages && [languages count] > 0) {
        unsigned int languageIndex, languageCount;

        [OFBundledClass createBundledClassWithName:itemName bundle:bundle description:description];
        languageCount = [languages count];
        for (languageIndex = 0; languageIndex < languageCount; languageIndex++) {
            NSString *aScriptLanguage;

            aScriptLanguage = [languages objectAtIndex:languageIndex];
            [self registerScriptLanguage:aScriptLanguage className:itemName];
        }
    }

    contentTypes = [description objectForKey:@"ContentTypes"];
    if (contentTypes && [contentTypes count]) {
        NSEnumerator *types = [contentTypes keyEnumerator];
        NSString *type, *language;
        OWContentType *sourceType;

        while((type = [types nextObject])) {
            sourceType = [OWContentType contentTypeForString:type];
            language = [contentTypes objectForKey:type];
            [OWScriptBlockProcessor registerContentType:sourceType
                                          forLanguage:language];
        }
    }        
}

+ contextAssociatedWithFrame:(OWScriptFrameProxy *)aFrame;
{
    return [frameUseMapping objectForKey:[NSValue valueWithPointer:aFrame]];
}

/* TODO: It might be cleaner if OWScriptContexts were owned by the documents' ContentInfos instead of the documents themselves */
- initWithOwner:(OWContentInfo *)newOwner;
{
    self = [super init];
    ownerInfo = [newOwner retain];
    interpreters = [[NSMutableArray alloc] init];
    invalid = NO;
    hadError = NO;
    baseAddress = nil;
    return self;
}

- (void)dealloc;
{
    /* We should already be invalidated before we hit dealloc, because invalidation is a fairly lengthy procedure and we'll be retained several times. */
    OBASSERT(invalid);
    /* This is insured (I think; check this; TODO) by the fact that we retain our owner until we're invalidated, and our owner retains us until it deallocates. */
    [self invalidate]; /* but be semi-safe nonetheless. */
    [interpreters release];
    [ownerInfo release];
    [baseAddress release];
    [super dealloc];
}

- (void)invalidate
{
    OWScriptDocumentProxy *docProxy;
    
    if (invalid)
        return;
       
    docProxy = [self documentProxy];
    if (docProxy) {
        if (OHHTMLDisplayProcessorScriptingDebugLevel >= 3)
            NSLog(@"%@: Posting unload from invalidate", [self shortDescription]);
        [docProxy postUnloadSynchronously];
    /* If we can't find the docProxy, then its pipeline has already gone away, in which case it already posted the unload event */
    }

    if (OHHTMLDisplayProcessorScriptingDebugLevel >= 3)
        NSLog(@"%@: invalidating", [self shortDescription]);
    
    invalid = YES;
    if (frameProxy) {
        [frameUseMapping removeObjectForKey:[NSValue valueWithPointer:frameProxy]];
        frameProxy = nil;
    }
    [interpreters makeObjectsPerformSelector:@selector(invalidateContext)];
    [[NSNotificationCenter defaultCenter] postNotificationName:OWScriptContextInvalidationNotification object:self];
    
    [ownerInfo autorelease];
    ownerInfo = nil;
}

- (BOOL)isValid
{
    return invalid ? NO : YES;
}

- (BOOL)hadError
{
    return hadError;
}

- (void)setHadError
{
    hadError = YES;
}

- (void)resetHadError
{
    hadError = NO;
}

/* TODO: Ummmm.... shouldn't this frame proxy be retained or something? */
- (void)setFrame:(OWScriptFrameProxy *)aFrame hint:(BOOL)justAHint;
{
    int interpIndex, interpCount;

    if (OHHTMLDisplayProcessorScriptingDebugLevel >= 3)
        NSLog(@"%@: frame=%@ hint=%d", [self shortDescription], [(OFObject *)aFrame shortDescription], justAHint);

    if (invalid)
        return;
    
    if (frameProxy && !frameProxyIsHint) {
        OBASSERT(aFrame == frameProxy);
        return;
    }

    if (!frameProxy || (!justAHint && frameProxyIsHint)) {
        if (frameProxy) {
            OBASSERT([frameUseMapping objectForKey:[NSValue valueWithPointer:frameProxy]] == self);
            [frameUseMapping removeObjectForKey:[NSValue valueWithPointer:frameProxy]];
        }
        if ([frameUseMapping objectForKey:[NSValue valueWithPointer:aFrame]]) {
            if (OHHTMLDisplayProcessorScriptingDebugLevel >= 3)
                NSLog(@"%@: Bumping old context from frame.", [self shortDescription]);
            [[frameUseMapping objectForKey:[NSValue valueWithPointer:aFrame]] invalidate];
        }
                
        frameProxy = aFrame;
        frameProxyIsHint = justAHint;

        [frameUseMapping setObject:self forKey:[NSValue valueWithPointer:frameProxy]];

        interpCount = [interpreters count];
        for(interpIndex = 0; interpIndex < interpCount; interpIndex ++)
            /* TODO: error handling? */
            [[interpreters objectAtIndex:interpIndex] setFrame:aFrame hint:frameProxyIsHint];
    }
}

- (OWScriptFrameProxy *)getFrameHint:(int *)isHintP;
{
    if (isHintP)
        *isHintP = (int)frameProxyIsHint;
    return frameProxy;
}

- (id <OWScriptInterpreter>)interpreterForLanguage:(NSString *)languageName;
{
#warning Someone needs to worry about thread safety here (perhaps the caller)
    int interpIndex, interpCount;
    id <OWScriptInterpreter> interpreter;

    if (invalid)
        return nil;
    
    // TODO: Settable default script language? See HTML4.0, which also makes not having a default script language an error
    // TODO: If no language is specified but a language was specified for a script block earlier should we just use that language?
    if (!languageName)
        languageName = @"JavaScript";

    // Look for an interpreter which can handle this language
    interpCount = [interpreters count];
    interpreter = nil;
    for(interpIndex = 0; interpIndex < interpCount; interpIndex ++) {
        interpreter = [interpreters objectAtIndex:interpIndex];
        if ([interpreter setLanguage:languageName])
            break;
        else
            interpreter = nil;
    }

    // move it to the front
    if (interpreter && (interpIndex != 0)) {
        [interpreter retain];
        [interpreters removeObjectAtIndex:interpIndex];
        [interpreters insertObject:interpreter atIndex:0];
        [interpreter release];
    }

    // create an interpreter if we have to
    if (!interpreter) {
        Class interpreterClass;

        interpreterClass = [[self class] interpreterClassForScriptLanguage:languageName];
        if (interpreterClass) {
            interpreter = [[interpreterClass alloc] initWithContext:self];
            if (interpreter) {
                OBASSERT([interpreter conformsToProtocol:@protocol(OWScriptInterpreter)]);
                if (languageName) {
                    BOOL languageRecognizedByInterpreter;

                    languageRecognizedByInterpreter = [interpreter setLanguage:languageName];
                    OBASSERT(languageRecognizedByInterpreter);
                }
                if (frameProxy)
                    [interpreter setFrame:frameProxy hint:frameProxyIsHint];
                [interpreters insertObject:interpreter atIndex:0];
                [interpreter release];
            }
        }
    }

    if (interpreter) {
        OBASSERT([interpreter context] == self);
    }

    return interpreter;
}

- (OWScriptDocumentProxy *)documentProxy
{
    return [ownerInfo documentProxy];
}

- (OWContentInfo *)contentInfo
{
    return ownerInfo;
}

- (OWAddress *)baseAddress;
{
    if (!baseAddress)
        return [ownerInfo address];
    return baseAddress;
}

- (void)setBaseAddress:(OWAddress *)newBaseAddress
{
    // Usually newBaseAddress will be the same address as our current baseAddress
    if (newBaseAddress == baseAddress)
        return;

    [newBaseAddress retain];
    [baseAddress release];
    baseAddress = newBaseAddress;
}

- (void)setHandler:(OWScriptEventType)handlerName onObject:anObject toString:(NSString *)handlerCode
{
    [[self interpreterForLanguage:nil] setHandler:handlerName onObject:anObject toString:handlerCode];
}

- (void)setHandler:(OWScriptEventType)handlerName onObject:anObject parented:anotherObject toString:(NSString *)handlerCode;
{
    [[self interpreterForLanguage:nil] setHandler:handlerName onObject:anObject parented:anotherObject toString:handlerCode];
}

// Debugging

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

   debugDictionary = [super debugDictionary];

   [debugDictionary setObject:interpreters forKey:@"interpreters"];
   [debugDictionary setBoolValue:invalid forKey:@"invalid"];
   
   return debugDictionary;
}


@end
