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

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OWF/OWF.h>
#import "OHHTMLDocument.h"
#import "OHHTMLPageView.h"
#import "OHInlineImageCell.h"
#import "OWScriptEventHandlerHolder.h"

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

@implementation OWScriptDocumentProxy

- initForPipeline:(OWPipeline *)aPipe
{
    self = [super init];

    nonretainedPipeline = aPipe;

    eventHandlers = [[OWScriptEventHandlerHolder allocWithZone:[self zone]] init];
    havePostedLoadEvent = NO;
    havePostedUnloadEvent = NO;
    protectionDomain = [[[[self url] parsedNetLocation] hostname] copy];
    /* TODO: what if our address doesn't have a netLocation --- e.g. omniweb: URLs? */
    lastPipelineContent = nil;

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(invalidatePipeline:) name:OWPipelineWillDeallocateNotificationName object:aPipe];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pipelineDidDeactivate:) name:OWPipelineTreeDeactivationNotificationName object:aPipe];

    [nonretainedPipeline addObserver:self selector:@selector(pipelineDidFetch:)]; 
    
    return self;
}

- (void)dealloc
{
    /* These removeObservers should not be necessary, because the pipeline indirectly invokes invalidatePipeline before it releases us. But better to be on the safe side. */
    [OWPipeline removeObserver:self];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [lastPipelineContent release];
    [eventHandlers release];
    [protectionDomain release];
    [super dealloc];
}

- (void)invalidatePipeline:(NSNotification *)aNote
{
    OBASSERT([aNote object] == nonretainedPipeline);

//    NSLog(@"%@: Posting unload from invalidatePipeline", [self shortDescription]);
    [self postUnloadSynchronously];
    
    [OWPipeline removeObserver:self];
    nonretainedPipeline = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/* Called by -invalidatePipeline; also called by the script context when it invalidates, so whichever happens first causes the event to be posted */
- (void)postUnloadSynchronously
{
    OWScriptEvent *unloadEvent;

    if (havePostedUnloadEvent)
        return;

    havePostedUnloadEvent = YES;

    unloadEvent = [[OWScriptEvent alloc] initWithType:OWSE_Unload];
    [unloadEvent setInstigator:self];
    [unloadEvent addResponder:self];
    [unloadEvent deliverSynchronously];
    [unloadEvent release];
}

- (void)pipelineDidDeactivate:(NSNotification *)aNote
{
    OWScriptEvent *theEvent;

    OBASSERT([aNote object] == nonretainedPipeline);

    /* We get a tree deactivation notification every time the tree deactivates, even if it only activated briefly to load an inline image whose src was changed by a script. But we only want to post a Load event once. (TODO: Should this logic be here, or should it be in the script language specific bundle?) */
    if (havePostedLoadEvent)
        return;
    havePostedLoadEvent = YES;

    theEvent = [[OWScriptEvent alloc] initWithType:OWSE_Load];
    [theEvent setInstigator:self];
    [theEvent addResponder:self];
    [theEvent deliverAsynchronously];
    [theEvent release];
}

/* Pipelines don't hold on to their last content after they deliver it to their target. Therefore we listen to the fetch notifications, which include the fetched content in their userInfo, and keep track of the last fetched content on our pipeline. (We allow the pipeline's -lastContent method to override this, but usually that one's nil.) */
- (void)pipelineDidFetch:(NSNotification *)aNote
{
    NSDictionary *userFo = [aNote userInfo];
    id <OWContent> newPipelineContent;

    if (!userFo)
        return;
    newPipelineContent = [userFo objectForKey:@"content"];
    if (!newPipelineContent || (newPipelineContent == lastPipelineContent))
        return;

    [newPipelineContent retain];
    [lastPipelineContent release];
    lastPipelineContent = newPipelineContent;
}

- (NSArray *)anchors
{
    /* TODO: Check whether this array should contain unnamed anchors (e.g. links, etc.). (NS 4.x doesn't implement it at all, so people probably won't depend on it for a while, but that's no reason for us to be lazy. Except that we *are* lazy.) */
    return [[[self htmlDocument] htmlPageView] namedAnchors];
}

- (NSArray *)links
{
    return [[[self htmlDocument] htmlPageView] links];
}

- (NSArray *)applets
{
    return [[self htmlDocument] applets];
}

- (NSArray *)forms
{
    return [[self htmlDocument] forms];
}

- (NSArray *)images
{
    NSArray *docImages;
    unsigned int docImageCount, docImageIndex;
    NSMutableArray *visibleImages;

    // JavaScript (the closest thing to a reference standard I have) doesn't want images that aren't actually *in* the document to be included in this array. (Image objects created by JavaScript instead of by HTML are still in the document's images array.)
    docImages = [[self htmlDocument] images];
    docImageCount = [docImages count];
    visibleImages = [[NSMutableArray alloc] initWithCapacity:docImageCount];
    for(docImageIndex = 0; docImageIndex < docImageCount; docImageIndex ++) {
        OHBasicCell *docImageCell = [docImages objectAtIndex:docImageIndex];
        OBASSERT([docImageCell isKindOfClass:[OHInlineImageCell class]]);
        if ([docImageCell characterPosition] != NSNotFound)
            [visibleImages addObject:docImageCell];
    }
    return [visibleImages autorelease];
}

- (NSArray *)cookies
{
    return [OWCookie cookiesForURL:[self url]];
}

- (void)setCookie:(NSString *)cookieDescription
{
    OWHeaderDictionary *fakeHeaders;

    fakeHeaders = [[OWHeaderDictionary alloc] init];
    [fakeHeaders addString:cookieDescription forKey:@"set-cookie"];
    [OWCookie registerCookiesFromURL:[self url]
                    headerDictionary:fakeHeaders];
    [fakeHeaders release];
}

- (NSString *)protectionDomain
{
    return protectionDomain;
}

- (void)setProtectionDomain:(NSString *)newDomain
{
    [protectionDomain autorelease];
    protectionDomain = [newDomain retain];
}

- (NSDate *)lastModified
{
    OWTimeStamp *stamp;
    OWContentCache *cache;

    cache = [OWContentCache contentCacheForAddress:[self address]];
    stamp = [cache peekAtContentOfType:[OWTimeStamp lastChangedContentType]];

    if (stamp) {
        return [stamp date];
    } else {
        return nil;
    }
}
    
- (NSString *)title
{
    return [OWDocumentTitle titleForAddress:[self address]];
}

- (OHColorPalette *)colorPalette
{
    return [[self htmlDocument] colorPalette];
}

- (OWURL *)url
{
    return [[self address] url];
}

- (OWAddress *)address
{
    return [nonretainedPipeline lastAddress];
}

- (OWAddress *)referringAddress
{
    /* TODO: Are we guaranteed that the pipeline is an OWWebPipeline? */
    return [(OWWebPipeline *)nonretainedPipeline referringAddress];
}

/* TODO: would it be better to hide *all* document interaction behind
   cover methods in script proxies? */
- (OHHTMLDocument *)htmlDocument
{
    id <OWContent> lastContent = [nonretainedPipeline lastContent];

    if (lastContent && lastContent != lastPipelineContent) {
        [lastContent retain];
        [lastPipelineContent release];
        lastPipelineContent = lastContent;
    }

    if (!lastPipelineContent || ![lastPipelineContent isKindOfClass:[OHHTMLDocument class]])
        return nil;
    else
        return (OHHTMLDocument *)lastPipelineContent;
}

/* document events (OWScriptResponder protocol) */
- (BOOL)handleHTMLEvent:(OWScriptEvent *)event;
{
    return [eventHandlers handleHTMLEvent:event];
}

- (BOOL)handleHTMLEventsInMainThread
{
    return [eventHandlers handleHTMLEventsInMainThread];
}

- (void)setHandler:(id <OWScriptResponder>)handler forEventType:(OWScriptEventType)type
{
    [eventHandlers setHandler:handler forEventType:type];
}

- (OWScriptEventHandlerHolder *)eventHandlers;
{
    return eventHandlers;
}

/* for writable documents */

- (BOOL)isWritable
{
    return writable;
}

- (void)writeData:(NSData *)data
{
    [writeBuffer writeData:data];
}

- (void)setWriteBuffer:(OWDataStream *)strm
{
    if (strm) {
        [writeBuffer autorelease]; /* writeBuffer should always be nil here */
        writeBuffer = [strm retain];
        writable = YES;
    } else {
        [writeBuffer release];
        writeBuffer = nil;
        writable = NO;
    }
}

@end
