// 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 <OWF/OWProcessor.h>

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

#import <OWF/OWContentInfo.h>
#import <OWF/OWContentType.h>
#import <OWF/OWDataStream.h>
#import <OWF/OWDataStreamCursor.h>
#import <OWF/OWObjectStream.h>
#import <OWF/OWPipeline.h>
#import <OWF/OWSourceProcessor.h>
#import <OWF/OWProcessorDescription.h>
#import <OWF/OWURL.h>

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Processors.subproj/OWProcessor.m,v 1.30 2002/03/09 01:53:52 kc Exp $")

@implementation OWProcessor

static NSMutableDictionary *classNameToReadableNameDictionary = nil;
static NSLock *readableNameDictionaryLock = nil;
static NSString *StatusStrings[5];
static BOOL OWProcessorTimeLog = NO;
static BOOL OWProcessorPriorityLog = NO;

+ (void)initialize;
{
    NSBundle *myBundle;
    
    OBINITIALIZE;

    readableNameDictionaryLock = [[NSLock alloc] init];
    classNameToReadableNameDictionary = [[NSMutableDictionary alloc] init];
    
    myBundle = [NSBundle bundleForClass:[OWProcessor class]];
    StatusStrings[OWProcessorStarting] = [NSLocalizedStringFromTableInBundle(@"Ready", @"OWF", myBundle, processor status) retain];
    StatusStrings[OWProcessorQueued] = [NSLocalizedStringFromTableInBundle(@"Queued", @"OWF", myBundle, processor status) retain];
    StatusStrings[OWProcessorRunning] = [NSLocalizedStringFromTableInBundle(@"Running", @"OWF", myBundle, processor status) retain];
    StatusStrings[OWProcessorAborting] = [NSLocalizedStringFromTableInBundle(@"Stopping", @"OWF", myBundle, processor status) retain];
    StatusStrings[OWProcessorRetired] = [NSLocalizedStringFromTableInBundle(@"Exiting", @"OWF", myBundle, processor status) retain];
}

+ (NSString *)readableClassName;
{
    NSString *className;
    NSString *readableName;
    NSRange range;
    
    className = NSStringFromClass(self);

    [readableNameDictionaryLock lock];

    readableName = [classNameToReadableNameDictionary objectForKey:className];
    if (readableName)
	goto unlockAndReturn;
    
    if ((range = [className rangeOfString:@"Omni" options:NSAnchoredSearch]).length || (range = [className rangeOfString:@"OW" options:NSAnchoredSearch]).length)
	readableName = [className substringFromIndex:NSMaxRange(range)];
    else
	readableName = className;
	
    if ((range = [readableName rangeOfString:@"Processor" options:NSAnchoredSearch|NSBackwardsSearch]).length)
	readableName = [readableName substringToIndex:range.location];
	
    [classNameToReadableNameDictionary setObject:readableName forKey:className];
	
unlockAndReturn:
    [readableNameDictionaryLock unlock];
    return readableName;
}

+ (OFMessageQueue *)processorQueue;
{
    static OFMessageQueue *processorQueue = nil;

    if (processorQueue == nil) {
        unsigned int threadCount;

        threadCount = [[NSUserDefaults standardUserDefaults] integerForKey:@"OWProcessorThreadCount"];
        if (threadCount == 0)
            threadCount = 12;
	processorQueue = [[OFMessageQueue alloc] init];
	[processorQueue startBackgroundProcessors:threadCount];
    }
    return processorQueue;
}

+ (void)registerProcessorClass:(Class)aClass fromContentType:(OWContentType *)sourceContentType toContentType:(OWContentType *)targetContentType cost:(float)aCost;
{
    OWProcessorDescription *description;
    
    description = [OWProcessorDescription processorDescriptionForProcessorClassName: NSStringFromClass(aClass)];
    [description registerProcessesContentType: sourceContentType toContentType:targetContentType cost:aCost];
}

+ (void)registerProcessorClass:(Class)aClass fromContentTypeString:(NSString *)sourceContentTypeString toContentTypeString:(NSString *)targetContentTypeString cost:(float)aCost;
{
    OWContentType *sourceContentType, *targetContentType;

    sourceContentType = [OWContentType contentTypeForString:sourceContentTypeString];
    targetContentType = [OWContentType contentTypeForString:targetContentTypeString];
    [self registerProcessorClass:aClass fromContentType:sourceContentType toContentType:targetContentType cost:aCost];
}

// OFBundleRegistryTarget informal protocol

+ (void)registerItemName:(NSString *)itemName bundle:(NSBundle *)bundle description:(NSDictionary *)description;
{
#warning TJW: Should we just allow you to put your dictionary under OWProccesor or OWProcessorDescription or should we put a log message here?
//    NSLog(@"OWProcessorDescription should be registered instead of OWProcessor in bundle %@ itemName %@ description %@", bundle, itemName, description);
    [OWProcessorDescription registerItemName:(NSString *)itemName bundle:(NSBundle *)bundle description:(NSDictionary *)description];
}

// Used to substitute a different processor when fetching a page via a proxy.
+ (OWContentType *)contentTypeForURL:(OWURL *)request whenProxiedBy:(OWURL *)proxy
{
    return [proxy contentType];
}

// Init and dealloc

- init;
{
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- initWithPipeline:(OWPipeline *)aPipeline;
{
    if (![super init])
	return nil;
    
    OFSimpleLockInit(&displayablesSimpleLock);
    [self setStatus:OWProcessorStarting];

    pipeline = [aPipeline weakRetain];
    [pipeline addProcessor:self];

    return self;
}

- (void)dealloc;
{
    OFSimpleLockFree(&displayablesSimpleLock);
    [statusString release];
    [firstBytesDate release];
    [pipeline weakRelease];
    [super dealloc];
}


//

- (OWPipeline *)pipeline;
{
    return pipeline;
}

// Processing

- (void)startProcessingInQueue:(OFMessageQueue *)aQueue;
{
    if (status == OWProcessorAborting) {
	[self retire];
	return;
    }
    [self setStatus:OWProcessorQueued];
    [aQueue queueSelector:@selector(processInThread) forObject:self];
}

- (void)startProcessing;
{
    [self startProcessingInQueue:[isa processorQueue]];
}

- (void)abortProcessing;
{
    [self setStatus:OWProcessorAborting];
}


// Status

- (void)setStatus:(OWProcessorStatus)newStatus;
{
    if (status == newStatus)
        return;
    status = newStatus;
    [self setStatusStringWithClassName:StatusStrings[status]];
}

- (OWProcessorStatus)status;
{
    return status;
}

- (void)setStatusString:(NSString *)newStatus;
{
    if (statusString == newStatus)
	return;
    OFSimpleLock(&displayablesSimpleLock);
    [statusString release];
    statusString = [newStatus retain];
    OFSimpleUnlock(&displayablesSimpleLock);
    [pipeline processorStatusChanged:self];
}

- (void)setStatusFormat:(NSString *)aFormat, ...;
{
    NSString *newStatus;
    va_list argList;

    va_start(argList, aFormat);
    newStatus = [[NSString alloc] initWithFormat:aFormat arguments:argList];
    va_end(argList);
    [self setStatusString:newStatus];
    [newStatus release];
}

- (void)setStatusStringWithClassName:(NSString *)newStatus;
{
    NSMutableString *newStatusString;

#warning This needs to be localizable
    // Avoid +stringWithFormat: since this is simple
    newStatusString = [[NSMutableString alloc] initWithString: [isa readableClassName]];
    [newStatusString appendString: @" "];
    [newStatusString appendString: newStatus];
    
    [self setStatusString: newStatusString];
    [newStatusString release];
}

- (NSString *)statusString;
{
    NSString *aStatus;

    OFSimpleLock(&displayablesSimpleLock);
    aStatus = [[statusString retain] autorelease];
    OFSimpleUnlock(&displayablesSimpleLock);
    return aStatus;
}

- (void)processedBytes:(unsigned int)bytes;
{
    [self processedBytes:bytes ofBytes:NSNotFound];
}

- (void)processedBytes:(unsigned int)bytes ofBytes:(unsigned int)newTotalBytes;
{
    unsigned int oldBytesProcessed;
    unsigned int oldTotalBytes;

    OFSimpleLock(&displayablesSimpleLock);
    oldBytesProcessed = bytesProcessed;
    oldTotalBytes = totalBytes;

    if (newTotalBytes != NSNotFound)
	totalBytes = newTotalBytes;
    if (!firstBytesDate)
	firstBytesDate = [[NSDate date] retain];

    bytesProcessed = bytes;
    OFSimpleUnlock(&displayablesSimpleLock);

    if ((oldBytesProcessed == 0 && bytesProcessed > 0) || (oldTotalBytes < 10 && totalBytes >= 10) || (bytesProcessed == totalBytes))
	[pipeline processorStatusChanged:self];
}

- (NSDate *)firstBytesDate;
{
    NSDate *aDate;

    OFSimpleLock(&displayablesSimpleLock);
    aDate = [[firstBytesDate retain] autorelease];
    OFSimpleUnlock(&displayablesSimpleLock);
    return aDate;
}

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

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

// OFMessageQueuePriority protocol

- (unsigned int)priority;
{
    unsigned int priority;

    if (pipeline != nil)
        priority = [pipeline taskPriority];
    else
        priority = OFLowPriority;

    if (OWProcessorPriorityLog)
        NSLog(@"Pipeline 0x%x, processor %@ instance 0x%x, priority = %d", pipeline, [self class], self, priority);

    return priority;
}

- (unsigned int)group;
{
    return [pipeline taskGroup];
}

- (unsigned int)maximumSimultaneousThreadsInGroup;
{
    return [pipeline taskMaximumSimultaneousThreadsInGroup];
}

@end


@implementation OWProcessor (SubclassesOnly)

- (void)processBegin;
{
    [self setStatus:OWProcessorRunning];
    if (OWProcessorTimeLog)
        NSLog(@"%@: begin", [self shortDescription]);
}

- (void)process;
{
}

- (void)processEnd;
{
    if (OWProcessorTimeLog)
        NSLog(@"%@: end", [self shortDescription]);
}

- (void)processAbort;
{
}

// Stuff only used by OWProcessor, or by subclasses which don't want to start a subthread

- (void)processInThread;
{
    if (status == OWProcessorAborting) {
        [self retire];
        return;
    }
    NS_DURING {
        [self processBegin];
        [self process];
        [self processEnd];
    } NS_HANDLER {
        if (status != OWProcessorAborting)
            [self handleProcessingException:localException];
        [self processAbort];
    } NS_ENDHANDLER;
    [self retire];
}

- (void)retire;
{
    [self setStatus:OWProcessorRetired];
    [pipeline processorDidRetire:self];
}

- (void)handleProcessingException:(NSException *)processingException;
{
    OWObjectStream *outputObjectStream;

    NSLog(@"%@(%@): %@: %@", [[pipeline lastAddress] addressString], [isa readableClassName], [processingException displayName], [processingException reason]);
    if ([pipeline hadError])
        return;
    [pipeline processor:self hasErrorName:[processingException displayName] reason:[processingException reason]];
    outputObjectStream = [[[OWObjectStream alloc] init] autorelease];
    [outputObjectStream setContentTypeString:@"ObjectStream/ErrorContent"];
    [outputObjectStream writeObject:NSLocalizedStringFromTableInBundle(@"Cannot Load Address", @"OWF", [OWProcessor bundle], processor error)];
    [outputObjectStream writeObject:[processingException reason]];
    [outputObjectStream dataEnd];
    [pipeline contentError];
    [pipeline addContent:outputObjectStream];
    [pipeline startProcessingContent];
}


// Debugging

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

    debugDictionary = [super debugDictionary];
    [debugDictionary setObject:[pipeline shortDescription] forKey:@"pipeline"];
    if (statusString)
        [debugDictionary setObject:statusString forKey:@"statusString"];

    return debugDictionary;
}

@end
