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

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

#import "OWAddress.h"
#import "OWContentCache.h"
#import "OWHTTPProcessor.h"
#import "OWHTTPSession.h"
#import "OWNetLocation.h"
#import "OWURL.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Processors.subproj/Protocols.subproj/HTTP.subproj/OWHTTPSessionQueue.m,v 1.18 1999/09/30 01:36:10 kc Exp $")

@interface OWHTTPSessionQueue (Private)
+ (void)contentCacheFlushedNotification:(NSNotification *)notification;
@end

@implementation OWHTTPSessionQueue

static OFDatedMutableDictionary *queues;
static NSLock *queueLock;
static NSTimeInterval timeout;
static NSDate *lastCleanup;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    // We want to flush our subclasses' caches, too.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentCacheFlushedNotification:) name:OWContentCacheFlushedCacheNotification object:nil];

    if (initialized)
        return;
    initialized = YES;

    timeout = [[OFUserDefaults sharedUserDefaults] floatForKey:@"OWHTTPSessionTimeout"];
    queues = [[OFDatedMutableDictionary alloc] init];
    queueLock = [[NSLock alloc] init];
    lastCleanup = [[NSDate alloc] init];
}

+ (Class)sessionClass;
{
    return [OWHTTPSession class];
}

+ (OFDatedMutableDictionary *)cache;
{
    // This is subclassed by the HTTPS plug-in which has its own cache
    return queues;
}

+ (unsigned int)maximumSessionsPerServer;
{
    return [[OFUserDefaults sharedUserDefaults] integerForKey:@"OWHTTPMaximumSessionsPerServer"];
}

+ (OWHTTPSessionQueue *)httpSessionQueueForAddress:(OWAddress *)anAddress;
{
    NSAutoreleasePool *pool;
    OFDatedMutableDictionary *cache;
    NSString *key;
    OWHTTPSessionQueue *queue;
    NSDate *now;

    pool = [[NSAutoreleasePool alloc] init];
    cache = [self cache];
    key = [[anAddress proxyURL] netLocation];

    [queueLock lock];

    // Lookup the queue for this address, creating if neccesary
    queue = [cache objectForKey:key];
    if (!queue) {
        queue = [[self alloc] initWithAddress:anAddress];
        [cache setObject:queue forKey:key];
        [queue release];
    }

    // If we haven't cleaned up in awhile free up old sessions

    // TODO: lastCleanup is local to this class, but we need to clean up HTTPS' cache, too.  Maybe we should just have a single cache with modified keys for each protocol, rather than trying to maintain separate caches.
    now = [[NSDate alloc] init];
    if ([now timeIntervalSinceDate:lastCleanup] > timeout) {
        NSEnumerator *enumerator;
        OWHTTPSessionQueue *instance;

        enumerator = [[cache objectsOlderThanDate:lastCleanup] objectEnumerator];
        while ((instance = [enumerator nextObject])) {
            if ([instance queueEmptyAndAllSessionsIdle])
                [cache removeObjectForKey:[instance queueKey]];
        }
        [lastCleanup release];
        lastCleanup = now;
    } else {
        [now release];
    }
    
    [queueLock unlock];

    [pool release];
    return queue;
}


- initWithAddress:(OWAddress *)anAddress;
{
    if (![super init])
        return nil;

    address = [anAddress retain];
    idleSessions = [[NSMutableArray alloc] init];
    sessions = [[NSMutableArray alloc] init];
    queuedProcessors = [[NSMutableArray alloc] init];
    abortedProcessors = [[NSMutableSet alloc] init];
    lock = [[NSLock alloc] init];
    flags.serverUnderstandsPipelinedRequests = NO;
    flags.serverCannotHandlePipelinedRequestsReliably = NO;

    return self;
}

- (void)dealloc;
{
    [idleSessions release];
    [sessions release];
    [address release];
    [queuedProcessors release];
    [abortedProcessors release];
    [lock release];
    [super dealloc];
}

- (BOOL)queueProcessor:(OWHTTPProcessor *)aProcessor;
{
    BOOL result;
    int runningSessions;
    
    [lock lock];
    if ([abortedProcessors member:aProcessor]) {
        [abortedProcessors removeObject:aProcessor];
        result = NO;
    } else {
        [queuedProcessors addObject:aProcessor];
        runningSessions = [sessions count] - [idleSessions count];
        result = (runningSessions < [isa maximumSessionsPerServer]);        
    }
    [lock unlock];

    return result;
}

- (void)runSession;
{
    OWHTTPSession *session;
    
    [lock lock];
    if ([queuedProcessors count]) {
        if ([idleSessions count]) {
            session = [idleSessions lastObject];
            [idleSessions removeLastObject];
        } else {
            session = [[[isa sessionClass] alloc] initWithAddress:address inQueue:self];
            [sessions addObject:session];
            [session release];
        }
    } else
        session = nil;
    [lock unlock];

    [session runSession];
}

- (void)abortProcessingForProcessor:(OWHTTPProcessor *)aProcessor;
{
    unsigned int index;
    NSArray *temporaryArray;
    
    [lock lock];
    if ((index = [queuedProcessors indexOfObject:aProcessor]) == NSNotFound) {
        [abortedProcessors addObject:aProcessor];
        temporaryArray = [NSArray arrayWithArray:sessions];
        [lock unlock];
        [temporaryArray makeObjectsPerformSelector:@selector(abortProcessingForProcessor:) withObject:aProcessor];
    } else {
        [queuedProcessors removeObjectAtIndex:index];
        [lock unlock];
    }
    [aProcessor retire];
}

- (OWHTTPProcessor *)nextProcessor;
{
    OWHTTPProcessor *result;
    
    [lock lock];
    if ([queuedProcessors count]) {
        result = [[queuedProcessors objectAtIndex:0] retain];
        [queuedProcessors removeObjectAtIndex:0];
    } else {
        result = nil;
    }
    [lock unlock];

    return [result autorelease];
}

- (BOOL)sessionIsIdle:(OWHTTPSession *)session;
{
    BOOL isReallyIdle;
    
    [lock lock];
    if ((isReallyIdle = ([queuedProcessors count] == 0)))
        [idleSessions addObject:session];
    [lock unlock];

    return isReallyIdle;
}

- (void)session:(OWHTTPSession *)session hasStatusString:(NSString *)statusString;
{
    unsigned int index, count;

    [lock lock];
    // Set the status for all processors in the queue
    for (index = 0, count = [queuedProcessors count]; index < count; index++)
        [[queuedProcessors objectAtIndex:index] setStatusString:statusString];
    [lock unlock];
}

- (BOOL)queueEmptyAndAllSessionsIdle;
{
    BOOL result;

    [lock lock];
    result = ([queuedProcessors count] == 0) && ([idleSessions count] == [sessions count]);
    [lock unlock];

    return result;    
}

- (NSString *)queueKey;
{
    return [[[address proxyURL] parsedNetLocation] displayString];
}

- (void)setServerUnderstandsPipelinedRequests;
{
    flags.serverUnderstandsPipelinedRequests = YES;
}

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

- (void)setServerCannotHandlePipelinedRequestsReliably;
{
    flags.serverCannotHandlePipelinedRequestsReliably = YES;
}

- (BOOL)shouldPipelineRequests;
{
    return flags.serverUnderstandsPipelinedRequests && !flags.serverCannotHandlePipelinedRequestsReliably && [[OFUserDefaults sharedUserDefaults] boolForKey:@"OWHTTPEnablePipelinedRequests"];
}

- (unsigned int)maximumNumberOfRequestsToPipeline;
{
    return [[OFUserDefaults sharedUserDefaults] integerForKey:@"OWHTTPMaximumNumberOfRequestsToPipeline"];
}

@end

@implementation OWHTTPSessionQueue (Private)

+ (void)contentCacheFlushedNotification:(NSNotification *)notification;
{
    // When the content cache is flushed, flush all the cached HTTP sessions
    [queueLock lock];
    NS_DURING {
        OFDatedMutableDictionary *cache;
        NSEnumerator *enumerator;
        OWHTTPSessionQueue *instance;

        cache = [self cache];
        enumerator = [[cache objectsOlderThanDate:[NSDate distantFuture]] objectEnumerator];
        while ((instance = [enumerator nextObject])) {
            if ([instance queueEmptyAndAllSessionsIdle])
                [cache removeObjectForKey:[instance queueKey]];
        }
    } NS_HANDLER {
        NSLog(@"+[%@ %@]: caught exception %@", NSStringFromClass(self), NSStringFromSelector(_cmd), localException);
    } NS_ENDHANDLER;
    [queueLock unlock];
}

@end
