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

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

#import "OWTask.h"
#import "OWAddress.h"
#import "OWContentCache.h"
#import "OWPipeline.h"
#import "OWTask.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Pipelines.subproj/OWContentInfo.m,v 1.24 1999/05/06 19:47:30 wiml Exp $")

@interface OWTopLevelActiveContentInfo : OWContentInfo
@end


@interface OWContentInfo (Private)
- (void)treeActiveStatusMayHaveChanged;
- (unsigned int)indexOfTaskWithLowestPriority;
@end

@implementation OWContentInfo

static OWContentInfo *topLevelActiveContentInfo;
static NSLock *headerContentInfoLock;
static NSMutableDictionary *headerContentInfoDictionary;
static NSMutableArray *allActiveTasks;
static NSRecursiveLock *allActiveTasksLock;

NSString *OWOrphanTaskHeaderName = @"Orphans";

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

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

    topLevelActiveContentInfo = [[OWTopLevelActiveContentInfo alloc] initWithContent:nil];
    headerContentInfoLock = [[NSLock alloc] init];
    headerContentInfoDictionary = [[NSMutableDictionary alloc] init];

    allActiveTasks = [[NSMutableArray alloc] initWithCapacity:16];
    allActiveTasksLock = [[NSRecursiveLock alloc] init];
}

+ (OWContentInfo *)topLevelActiveContentInfo;
{
    return topLevelActiveContentInfo;
}

+ (OWContentInfo *)headerContentInfoWithName:(NSString *)name;
{
    OWContentInfo *newContentInfo;

    OBPRECONDITION(name);
    [headerContentInfoLock lock];
    
    newContentInfo = [headerContentInfoDictionary objectForKey:name];
    if (!newContentInfo) {
        OFUserDefaults *userDefaults;
        NSString *priorityDefaultKey;
        NSString *maximumSimultaneousThreadsInGroupDefaultKey;
        
        newContentInfo = [[self alloc] initWithContent:nil];

        // OWTask object is permanent header
        [[OWTask alloc] initWithName:name contentInfo:newContentInfo parentContentInfo:topLevelActiveContentInfo];

        newContentInfo->flags.isHeader = YES;
        // Look up thread limit in prefs based on name
        userDefaults = [OFUserDefaults sharedUserDefaults];

        priorityDefaultKey = [NSString stringWithFormat:@"OWTask-%@-priority", name];
        if (![userDefaults defaultRegisteredForKey:priorityDefaultKey])
            priorityDefaultKey = @"OWTask-DEFAULT-priority";

        maximumSimultaneousThreadsInGroupDefaultKey = [NSString stringWithFormat:@"OWTask-%@-maximumSimultaneousThreadsInGroup", name];
        if (![userDefaults defaultRegisteredForKey:maximumSimultaneousThreadsInGroupDefaultKey])
            maximumSimultaneousThreadsInGroupDefaultKey = @"OWTask-DEFAULT-maximumSimultaneousThreadsInGroup";

        newContentInfo->priority = [userDefaults integerForKey:priorityDefaultKey];
        newContentInfo->maximumSimultaneousThreadsInGroup = [userDefaults integerForKey:maximumSimultaneousThreadsInGroupDefaultKey];

        // newContentInfo is permanent, also
        [headerContentInfoDictionary setObject:newContentInfo forKey:name];
        [newContentInfo release];
    }
    
    [headerContentInfoLock unlock];

    OBPOSTCONDITION(newContentInfo);
    return newContentInfo;
}


+ (OWContentInfo *)orphanParentContentInfo;
{
    return [self headerContentInfoWithName:OWOrphanTaskHeaderName];
}


+ (NSArray *)allActiveTasks;
{
    NSArray *copiedArray;

    [allActiveTasksLock lock];
    copiedArray = [NSArray arrayWithArray:allActiveTasks];
    [allActiveTasksLock unlock];
    
    return copiedArray;
}

// Init and dealloc

- initWithContent:(id <OWContent>)aContent;
{
    return [self initWithContent:aContent typeString:nil];
}

- initWithContent:(id <OWContent>)aContent typeString:(NSString *)aType;
{
    if (![super init])
        return nil;

    nonretainedContent = aContent;
    
    typeString = [aType copy];
    lock = [[NSRecursiveLock alloc] init];
    tasks = [[NSMutableArray alloc] init];
    childTasks = [[NSMutableArray alloc] init];
    activeChildTasks = [[NSMutableArray alloc] init];

    workToBeDoneIncludingChildren = 0;
    priority = 55;
    maximumSimultaneousThreadsInGroup = INT_MAX;

    return self;
}

- (void)dealloc;
{
    OBPRECONDITION(nonretainedContent == nil);
    OBPRECONDITION([tasks count] == 0);
    OBPRECONDITION([childTasks count] == 0);
    OBPRECONDITION([activeChildTasks count] == 0);

    [typeString release];
    [lock release];
    [tasks release];
    [childTasks release];
    [activeChildTasks release];
    
    [address release];

    [super dealloc];
}

// Actions

- (void)deepFlushContentCache;
{
    NSArray *childrenCopy;
    unsigned int taskIndex;

    [lock lock];

    childrenCopy = [[NSArray alloc] initWithArray:childTasks];
    taskIndex = [childrenCopy count];
    while (taskIndex--)
        [[childrenCopy objectAtIndex:taskIndex] deepFlushContentCache];
    [childrenCopy release];

    [[OWContentCache contentCacheForAddress:address] flushCachedContent];

    [lock unlock];
}


// Content

- (id <OWContent>)content;
{
    return [[nonretainedContent retain] autorelease];
}

- (void)nullifyContent;
{
    nonretainedContent = nil;
    [scriptContext invalidate]; /* this kinda breaks framework encapsulation... */
    [[self childTasks] makeObjectsPerformSelector:@selector(parentContentInfoLostContent)];
    [self treeActiveStatusMayHaveChanged];
    [scriptContext release];
    scriptContext = nil;
}

// Info

- (NSString *)typeString;
{
    return [[typeString copy] autorelease];
}

- (void)setAddress:(id <OWAddress>)newAddress;
{
    [lock lock];
    if (address != newAddress) {
        [address release];
        address = [newAddress retain];
    }
    [lock unlock];
}

- (id <OWAddress>)address;
{
    id <OWAddress> addressAutoreleased;
    [lock lock];
    addressAutoreleased = [[address retain] autorelease];
    [lock unlock];
    return addressAutoreleased;
}


// Inspecting

- (void)tellInspectorPipelineTreeDidChange;
{
    NSArray *pipelinesCopy;
    int taskIndex, taskCount;

    [lock lock];
    pipelinesCopy = [[NSArray alloc] initWithArray:tasks];
    [lock unlock];

    taskCount = [pipelinesCopy count];
    for (taskIndex = 0; taskIndex < taskCount; taskIndex++)
        [[pipelinesCopy objectAtIndex:taskIndex] tellInspectorPipelineTreeDidChange];
    [pipelinesCopy release];
}


// Pipelines

- (NSArray *)tasks;
{
    NSArray *copiedArray;

    [lock lock];
    copiedArray = [NSArray arrayWithArray:tasks];
    [lock unlock];
    return copiedArray;
}

- (void)addTask:(OWTask *)aTask;
{
    [lock lock];
    OBPRECONDITION([tasks indexOfObjectIdenticalTo:aTask] == NSNotFound);
    [tasks addObject:aTask];
    [lock unlock];
    [self queueSelectorOnce:@selector(tellInspectorPipelineTreeDidChange)];
}

- (void)removeTask:(OWTask *)aTask;
{
    unsigned int index;

    [lock lock];
    index = [tasks indexOfObjectIdenticalTo:aTask];
    OBPRECONDITION(index != NSNotFound);
    [tasks removeObjectAtIndex:index];
    [lock unlock];
    [self queueSelectorOnce:@selector(tellInspectorPipelineTreeDidChange)];
}


// Children tasks

- (void)addChildTask:(OWTask *)aTask;
{
    [lock lock];
    OBPRECONDITION([childTasks indexOfObjectIdenticalTo:aTask] == NSNotFound);
    [childTasks addObject:aTask];
    [lock unlock];
    [self queueSelectorOnce:@selector(tellInspectorPipelineTreeDidChange)];
}

- (void)removeChildTask:(OWTask *)aTask;
{
    unsigned int index;

    [lock lock];
    index = [childTasks indexOfObjectIdenticalTo:aTask];
    OBPRECONDITION(index != NSNotFound);
    [childTasks removeObjectAtIndex:index];
    [lock unlock];
    [self queueSelectorOnce:@selector(tellInspectorPipelineTreeDidChange)];
}

- (NSArray *)childTasks;
{
    NSArray *copiedArray;

    [lock lock];
    copiedArray = [NSArray arrayWithArray:childTasks];
    [lock unlock];
    return copiedArray;
}

- (int)workDoneByChildTasksIncludingFinished:(BOOL)includeFinished;
{
    int work;
    
    if (flags.wasActiveOnLastCheck) {
        NSArray *childrenCopy;
        int taskIndex;

        [lock lock];
        childrenCopy = [[NSArray alloc] initWithArray:childTasks];
        [lock unlock];

        work = 0;
        taskIndex = [childrenCopy count];
        while (taskIndex--)
            work += [[childrenCopy objectAtIndex:taskIndex] workDoneIncludingChildrenIncludingFinished:includeFinished];
        [childrenCopy release];
    } else if (includeFinished)
        work = workToBeDoneIncludingChildren;
    else
        work = 0;

    return work;
}

- (int)workToBeDoneByChildTasksIncludingFinished:(BOOL)includeFinished;
{
    int work;
    
    if (flags.wasActiveOnLastCheck) {
        NSArray *childrenCopy;
        int taskIndex;

        [lock lock];
        childrenCopy = [[NSArray alloc] initWithArray:childTasks];
        [lock unlock];

        work = 0;
        taskIndex = [childrenCopy count];
        while (taskIndex--)
            work += [[childrenCopy objectAtIndex:taskIndex] workToBeDoneIncludingChildrenIncludingFinished:includeFinished];
        [childrenCopy release];
        workToBeDoneIncludingChildren = work;
    } else if (includeFinished)
        work = workToBeDoneIncludingChildren;
    else
        work = 0;

    return work;
}

- (void)putCompositeTypeStringsOfActiveChildTasksInArray:(NSMutableArray *)compositeTypeStrings andSet:(NSCountedSet *)compositeTypeStringsSet;
{
    NSArray *childrenCopy;
    unsigned int taskIndex;

    [lock lock];
    childrenCopy = [[NSArray alloc] initWithArray:childTasks];
    [lock unlock];

    taskIndex = [childrenCopy count];
    while (taskIndex--)
        [[childrenCopy objectAtIndex:taskIndex] hasChildrenInCompositeTypeStringsInArray:compositeTypeStrings andSet:compositeTypeStringsSet];
    [childrenCopy release];
}


// Active tree

- (BOOL)treeHasActiveChildren;
{
    return [activeChildTasks count] > 0;
}

- (void)addActiveChildTask:(OWTask *)aTask;
{
    [lock lock];
    OBPRECONDITION([activeChildTasks indexOfObjectIdenticalTo:aTask] == NSNotFound);

    [allActiveTasksLock lock];
    OBPRECONDITION([allActiveTasks indexOfObjectIdenticalTo:aTask] == NSNotFound);
    [allActiveTasks addObject:aTask];
    [allActiveTasksLock unlock];
        
    // Member, not subclass
    if ([aTask isMemberOfClass:[OWTask class]])
        // If we're at the top level, sort the OWTask headers by priority so they don't jump around as they appear and disappear.  (eg, "Saving files, Web pages, Downloads,...")
        [activeChildTasks insertObject:aTask inArraySortedUsingSelector:@selector(comparePriority:)];
    else
        [activeChildTasks addObject:aTask];
    if ([activeChildTasks count] == 1)
        [self treeActiveStatusMayHaveChanged];
    [lock unlock];
    [OWPipeline activeTreeHasChanged];
}

- (void)removeActiveChildTask:(OWTask *)aTask;
{
    unsigned int index;

    [lock lock];
    
    [allActiveTasksLock lock];
    index = [allActiveTasks indexOfObjectIdenticalTo:aTask];
    OBPRECONDITION(index != NSNotFound);
    [allActiveTasks removeObjectAtIndex:index];
    [allActiveTasksLock unlock];

    index = [activeChildTasks indexOfObjectIdenticalTo:aTask];
    OBPRECONDITION(index != NSNotFound);
    [activeChildTasks removeObjectAtIndex:index];
    if ([activeChildTasks count] == 0)
        [self treeActiveStatusMayHaveChanged];
    [lock unlock];
    [OWPipeline activeTreeHasChanged];
}

- (NSArray *)activeChildTasks;
{
    NSArray *childrenCopy;

    [lock lock];
    childrenCopy = [[NSArray alloc] initWithArray:activeChildTasks];
    [lock unlock];
    return [childrenCopy autorelease];
}

- (unsigned int)activeChildTasksCount;
{
    unsigned int activeChildTasksCount;

    [lock lock];
    activeChildTasksCount = [activeChildTasks count];
    [lock unlock];
    return activeChildTasksCount;
}

- (void)abortActiveChildTasks;
{
    NSArray *activeChildrenCopy;
    unsigned int taskIndex, taskCount;

    [lock lock];
    activeChildrenCopy = [[NSArray alloc] initWithArray:activeChildTasks];
    [lock unlock];

    taskCount = [activeChildrenCopy count];
    for (taskIndex = 0; taskIndex < taskCount; taskIndex++)
        [[activeChildrenCopy objectAtIndex:taskIndex] abortTreeActivity];
    [activeChildrenCopy release];
}

- (NSTimeInterval)estimatedRemainingTreeTimeIntervalForActiveChildTasks;
{
    NSTimeInterval maxTimeInterval = 0.0;


    if (flags.wasActiveOnLastCheck) {
        NSArray *childrenCopy;
        int taskIndex;

        [lock lock];
        childrenCopy = [[NSArray alloc] initWithArray:childTasks];
        [lock unlock];
        
        taskIndex = [childrenCopy count];
        while (taskIndex--)
            maxTimeInterval = MAX(maxTimeInterval, [[childrenCopy objectAtIndex:taskIndex] estimatedRemainingTreeTimeInterval]);
        [childrenCopy release];
    }
    return maxTimeInterval;
}


// OFMessageQueue protocol helpers

- (unsigned int)baseTaskPriority;
{
    unsigned int taskCount;
    unsigned int aPriority;

    [lock lock];
    taskCount = [tasks count];
    if (taskCount == 0 || flags.isHeader)
        aPriority = priority;
    else
        aPriority = [[tasks objectAtIndex:[self indexOfTaskWithLowestPriority]] baseTaskPriority];
    [lock unlock];
    return aPriority;
}

- (unsigned int)taskGroup;
{
    unsigned int taskCount;
    unsigned int aGroup;

    [lock lock];
    taskCount = [tasks count];
    if (taskCount == 0 || flags.isHeader)
        aGroup = (unsigned int)self;
    else
        aGroup = [[tasks objectAtIndex:[self indexOfTaskWithLowestPriority]] taskGroup];
    [lock unlock];
    return aGroup;
}

- (unsigned int)taskMaximumSimultaneousThreadsInGroup;
{
    unsigned int taskCount;
    unsigned int aThreadCount;

    [lock lock];
    taskCount = [tasks count];
    if (taskCount == 0 || flags.isHeader)
        aThreadCount = maximumSimultaneousThreadsInGroup;
    else
        aThreadCount = [[tasks objectAtIndex:[self indexOfTaskWithLowestPriority]] taskMaximumSimultaneousThreadsInGroup];
    [lock unlock];
    return aThreadCount;
}

@end

@implementation OWContentInfo (Private)

- (void)treeActiveStatusMayHaveChanged;
{
    BOOL treeHasActiveChildren;

    [lock lock];
    treeHasActiveChildren = [self treeHasActiveChildren];
    if (!treeHasActiveChildren) {
        if (flags.wasActiveOnLastCheck) {
            [tasks makeObjectsPerformSelector:@selector(treeActiveStatusMayHaveChanged)];
        }

        // Are we dead, but just don't know it yet?
        if (!flags.isHeader && !nonretainedContent && [childTasks count] == 0) {
            [[self retain] autorelease];
            [tasks makeObjectsPerformSelector:@selector(nullifyContentInfo)];
            [tasks release];
            tasks = nil;
        }
    } else if (!flags.wasActiveOnLastCheck)
        [tasks makeObjectsPerformSelector:@selector(treeActiveStatusMayHaveChanged)];

    flags.wasActiveOnLastCheck = treeHasActiveChildren;
    [lock unlock];
}

- (unsigned int)indexOfTaskWithLowestPriority;
    // We MUST be locked before entering this routine.
{
    unsigned int lowestPriority;
    unsigned int lowestPriorityTaskIndex;
    unsigned int taskIndex;

    OBPRECONDITION(!flags.isHeader);

    taskIndex = [tasks count];
    if (taskIndex == 1)
        return 0;
    
    lowestPriority = INT_MAX;
    lowestPriorityTaskIndex = NSNotFound;

    while (taskIndex--) {
        unsigned int taskPriority;

        taskPriority = [[tasks objectAtIndex:taskIndex] taskPriority];
        if (taskPriority < lowestPriority) {
            lowestPriorityTaskIndex = taskIndex;
            lowestPriority = taskPriority;
        }
    }

    return lowestPriorityTaskIndex;
}

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

    debugDictionary = [super debugDictionary];

    [lock lock];
    if (nonretainedContent)
        [debugDictionary setObject:[(OBObject *)nonretainedContent shortDescription] forKey:@"nonretainedContent"];
    if (tasks)
        [debugDictionary setObject:[NSString stringWithFormat:@"0x%x", tasks] forKey:@"tasks"];
    if (childTasks)
        [debugDictionary setObject:childTasks forKey:@"childTasks"];
    if (activeChildTasks)
        [debugDictionary setObject:activeChildTasks forKey:@"activeChildTasks"];
    if (scriptContext)
        [debugDictionary setObject:scriptContext forKey:@"scriptContext"];
    [lock unlock];

    return debugDictionary;
}

@end


@implementation OWTopLevelActiveContentInfo

- (void)treeActiveStatusMayHaveChanged;
{
    BOOL treeHasActiveChildren;

    [lock lock];
    treeHasActiveChildren = [self treeHasActiveChildren];
    if (treeHasActiveChildren != flags.wasActiveOnLastCheck) {
        if (treeHasActiveChildren) {
            [OWPipeline queueSelector:@selector(startActiveStatusUpdateTimer)];
        } else {
            [OWPipeline queueSelector:@selector(stopActiveStatusUpdateTimer)];
        }
    }
    flags.wasActiveOnLastCheck = treeHasActiveChildren;
    [lock unlock];
}

@end
