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

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

#import "OWContentInfo.h"
#import "OWContentType.h"
#import "OWDataStream.h"
#import "OWDataStreamCursor.h"
#import "OWObjectStream.h"
#import "OWPipeline.h"
#import "OWSourceProcessor.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Processors.subproj/OWProcessor.m,v 1.12 1999/03/30 21:26:33 kc Exp $")

@implementation OWProcessor

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


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

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    classNameToReadableNameDictionary = [[NSMutableDictionary alloc] init];
    readableNameDictionaryLock = [[NSLock alloc] init];
    StatusStrings[OWProcessorStarting] = [@"Ready" retain];
    StatusStrings[OWProcessorQueued] = [@"Queued" retain];
    StatusStrings[OWProcessorRunning] = [@"Running" retain];
    StatusStrings[OWProcessorAborting] = [@"Stopping" retain];
    StatusStrings[OWProcessorRetired] = [@"Exiting" 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;
{
    if (!processorQueue) {
        unsigned int threadCount;

        threadCount = [[OFUserDefaults sharedUserDefaults] 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;
{
    [sourceContentType linkToContentType:targetContentType usingProcessorClassName:NSStringFromClass(aClass) 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;
{
    NSEnumerator *conversionEnumerator;
    NSDictionary *conversionDictionary;

    [OFBundledClass createBundledClassWithName:itemName bundle:bundle description:description];

    conversionEnumerator = [[description objectForKey:@"converts"] objectEnumerator];
    while ((conversionDictionary = [conversionEnumerator nextObject])) {
	OWContentType *inputType, *outputType;
	NSString *aCostObject;
	float aCost;

	inputType = [OWContentType contentTypeForString:[conversionDictionary objectForKey:@"input"]];
	outputType = [OWContentType contentTypeForString:[conversionDictionary objectForKey:@"output"]];
	aCostObject = [conversionDictionary objectForKey:@"cost"];
	aCost = aCostObject ? [aCostObject floatValue] : 1.0;
	[inputType linkToContentType:outputType usingProcessorClassName:itemName cost:aCost];
    }
}


// Init and dealloc

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

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

    pipeline = aPipeline;
    [pipeline addProcessor:self];

    return self;
}

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


//

- (OWPipeline *)pipeline;
{
    return [[pipeline retain] autorelease];
}

- (void)nullifyPipeline
{
    pipeline = nil;
}


// 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;
{
    [self setStatusString:[NSString stringWithFormat:@"%@ %@", [isa readableClassName], newStatus]];
}

- (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;
{
    if (OWProcessorPriorityLog)
        NSLog(@"Pipeline %x, processor %@ instance %x, priority = %d", pipeline, [self class], self, [pipeline taskPriority]);

    return [pipeline taskPriority];
}

- (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;

    [pipeline processor:self hasErrorName:[processingException displayName] reason:[processingException reason]];
    outputObjectStream = [[[OWObjectStream alloc] init] autorelease];
    [outputObjectStream setContentTypeString:@"ObjectStream/ErrorContent"];
    [outputObjectStream writeObject:@"Cannot Load Address"];
    [outputObjectStream writeObject:[processingException reason]];
    [outputObjectStream dataEnd];
    [pipeline contentError];
    [pipeline addSourceContent: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
