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

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

#import "ODDocument.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniDocument/ODDocumentController.m,v 1.20 2000/01/19 23:36:26 kc Exp $")

@interface ODDocumentController (Private)
- (void)showSplashPanel;
- (void)hideSplashPanel;
@end

@implementation ODDocumentController

static ODDocumentController *sharedInstance = nil;

+ sharedInstance;
{
    if (!sharedInstance)
        [[self alloc] init];
    return sharedInstance;
}

+ (NSString *)appVersionString;
{
    NSString *version;

    version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"ODApplicationVersion"];
    if (!version)
        version = @"unknown";
    return version;
}

+ (NSString *)fullVersionString;
{
    return [NSString stringWithFormat:@"version %@", [self appVersionString]];
}

+ (NSString *)startingOpenPanelPath;
{
    return nil;
}

+ (NSTimeInterval)minimumSplashPanelDisplayTime;
{
    return 4.0;
}


// Init and dealloc

- init;
{
    if (sharedInstance) {
        [self release];
        return sharedInstance;
    }

    if (![super init])
        return nil;

    nonretainedActiveDocument = nil;
    newDocument = nil;
    extensionToClass = [[NSMutableDictionary alloc] init];
    pathToDocument = [[NSMutableDictionary alloc] init];
    allDocuments = [[NSMutableArray alloc] init];

    sharedInstance = self;

    return self;
}


//

- (void)setDefaultDocumentClass:(Class)aClass;
{
    defaultDocumentClass = aClass;
    [self registerDocumentClass:aClass];
}

- (void)registerDocumentClass:(Class)aClass forFileExtension:(NSString *)anExtension;
{
    OBPRECONDITION(aClass);
    OBPRECONDITION(anExtension);
    
    [extensionToClass setObject:(id)aClass forKey:anExtension];
}

- (void)registerDocumentClass:(Class)aClass;
{
    [self registerDocumentClass:aClass forFileExtension:[aClass fileExtension]];
}

- (Class)documentClassForFileExtension:(NSString *)anExtension;
{
    return (Class)[extensionToClass objectForKey:anExtension];
}

- (void)registerDocument:(ODDocument *)aDocument forPath:(NSString *)aPath;
{
    OBPRECONDITION(aDocument);
    OBPRECONDITION(aPath);
    
    [pathToDocument setObject:aDocument forKey:aPath];
    OBASSERT([allDocuments indexOfObjectIdenticalTo:aDocument] == NSNotFound);
    [allDocuments addObject:aDocument];
}

- (void)deregisterDocumentForPath:(NSString *)aPath;
{
    ODDocument *document;
    unsigned int documentIndex;
    
    OBPRECONDITION(aPath);

    document = [pathToDocument objectForKey:aPath];
    OBASSERT(document);
    
    if (document == nonretainedActiveDocument)
        nonretainedActiveDocument = nil;
    [[document retain] autorelease];
    [pathToDocument removeObjectForKey:aPath];
    documentIndex = [allDocuments indexOfObjectIdenticalTo:document];
    OBASSERT(documentIndex != NSNotFound);
    [allDocuments removeObjectAtIndex:documentIndex];
}

- (ODDocument *)documentForPath:(NSString *)aPath;
{
    return [pathToDocument objectForKey:aPath];
}


// API

- (NSArray *)openDocumentPaths;
{
    return [pathToDocument allKeys];
}

- (ODDocument *)activeDocument;
{
    return nonretainedActiveDocument;
}

- (void)setActiveDocument:(ODDocument *)aDocument;
{
    nonretainedActiveDocument = aDocument;
}


- (ODDocument *)openFile:(NSString *)path;
{
    NSString *extension;
    Class documentClass;

    if ((newDocument = [self documentForPath:path])) {
	[newDocument activateDocument];
	return newDocument;
    }

    extension = [path pathExtension];
    if (!(documentClass = [extensionToClass objectForKey:extension])) {
	[NSException raise:@"ODDocumentController" format:@"No document class registered for extension '%@'", extension];
    }
    newDocument = [documentClass documentWithPath:path];
    [newDocument activateDocument];
    return newDocument;
}

- (void)switchToNextDocumentFromDocument:(ODDocument *)document;
{
    unsigned int documentIndex, documentCount;

    documentCount = [allDocuments count];
    if (documentCount < 2)
	return;
    documentIndex = [allDocuments indexOfObjectIdenticalTo:document];
    if (documentIndex == NSNotFound)
	return;
    if (documentIndex == documentCount - 1)
	[[allDocuments objectAtIndex:0] activateDocument];
    else
        [[allDocuments objectAtIndex:documentIndex + 1] activateDocument];
}


- (void)switchToPreviousDocumentFromDocument:(ODDocument *)document;
{
    unsigned int documentIndex, documentCount;

    documentCount = [allDocuments count];
    if (documentCount < 2)
	return;
    documentIndex = [allDocuments indexOfObjectIdenticalTo:document];
    if (documentIndex == NSNotFound)
	return;
    if (documentIndex == 0)
	[[allDocuments objectAtIndex:documentCount - 1] activateDocument];
    else
        [[allDocuments objectAtIndex:documentIndex - 1] activateDocument];
}

// Actions

- (IBAction)new:(id)sender;
{
    OBPRECONDITION(defaultDocumentClass);
    newDocument = [defaultDocumentClass documentWithPath: nil];
}

- (IBAction)open:(id)sender;
{
    unsigned int typeCount;

    if ((typeCount = [extensionToClass count])) {
        NSArray *fileTypes;
	NSString *openPath;
	NSOpenPanel *openPanel;

        fileTypes = [extensionToClass allKeys];
	openPanel = [NSOpenPanel openPanel];

	[openPanel setAllowsMultipleSelection:YES];
        if (nonretainedActiveDocument)
            openPath = [[nonretainedActiveDocument documentPath] stringByDeletingLastPathComponent];
	else {
	    openPath = [[self class] startingOpenPanelPath];
	    if (!openPath)
		openPath = NSHomeDirectory();
	}
	[openPanel setDirectory:openPath];

	// run the open panel, filtering for out type of document
	if ([openPanel runModalForTypes:fileTypes]) {
            NSEnumerator *fileEnum;
            NSString *file;

	    // open all the files returned by the open panel
	    fileEnum = [[openPanel filenames] objectEnumerator];
            while ((file = [fileEnum nextObject]))
                [self openFile:file];
	}
    }
}

- (IBAction)saveAll:(id)sender;
{
    unsigned int documentIndex, documentCount;

    documentCount = [allDocuments count];
    for (documentIndex = 0; documentIndex < documentCount; documentIndex++)
        [[allDocuments objectAtIndex:documentIndex] save:sender];
}

- (IBAction)closeAll:(id)sender;
{
    unsigned int documentIndex;

    documentIndex = [allDocuments count];
    while (documentIndex--)
        if ([(ODDocument *)[allDocuments objectAtIndex:documentIndex] close] == ODDocumentCancel)
            return;
}

- (IBAction)showInfoPanel:(id)sender;
{
    if (!infoPanel) {
        if (![NSBundle loadNibNamed:@"Info.nib" owner:self]) {
            NSRunAlertPanel([[NSProcessInfo processInfo] processName], @"Unable to load Info.nib", @"OK", nil, nil);
            return;
        }
        [infoPanelVersionTextField setStringValue:[isa fullVersionString]];
    }
    [infoPanel center];
    [infoPanel setLevel:NSNormalWindowLevel];
    [infoPanel makeKeyAndOrderFront:sender];
}


// NSMenuActionResponder informal protocol

- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
    if ([anItem action] == @selector(saveAll:) || [anItem action] == @selector(closeAll:))
        return [allDocuments count] != 0;
    return YES;
}


//  NSApplication delegate

- (void)applicationWillFinishLaunching:(NSNotification *)notification;
{
    // If this is in -init, OmniPDF crashes though OmniImage doesn't.  I guess some nibs just aren't ready to be loaded this early, while others are.
    [self showSplashPanel];
}

- (void)applicationDidFinishLaunching:(NSNotification *)notification;
{
    if (([[NSUserDefaults standardUserDefaults] boolForKey:@"NXAutoLaunch"] || [[NSUserDefaults standardUserDefaults] boolForKey:@"NSAutoLaunch"]) && [[OFUserDefaults sharedUserDefaults] boolForKey:@"HideOnAutoLaunch"])
        [NSApp hide:self];
}

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
{
    if (filename) {
        if ([self documentClassForFileExtension:[filename pathExtension]])
            return [self openFile:filename] != nil;
    }
    return NO;
}

- (BOOL)applicationShouldTerminate:(NSApplication *)sender;
{
    unsigned int documentIndex, documentCount;
    unsigned int choice;
    NSMutableArray *editedDocuments = [NSMutableArray array];

    documentCount = [allDocuments count];
    for (documentIndex = 0; documentIndex < documentCount; documentIndex++) {
        ODDocument *document;

        document = [allDocuments objectAtIndex:documentIndex];
	if ([document isEdited])
	    [editedDocuments addObject:document];
    }

    if ([editedDocuments count]) {
        choice = NSRunAlertPanel(@"Quit", @"There are unsaved documents.\nReview them?", @"Review Unsaved", @"Quit Anyway", @"Cancel");

        switch (choice) {
            case NSAlertDefaultReturn:
                for (documentIndex = 0; documentIndex < documentCount; documentIndex++) {
                    if ([(ODDocument *)[allDocuments objectAtIndex:documentIndex] close] == ODDocumentCancel)
                        return NO;
                }
                break;
            case NSAlertAlternateReturn:
                // user selected 'Quit Anyway'
                break;
            case NSAlertOtherReturn:
                // user selected Cancel, return nil to cancel quit
                return NO;
                break;
        }
    }

    // ENDGAME: hide all the windows first to avoid the irritation of watching each window become the main window in turn while we're closing documents.  After all, we simply want to quit...
    [allDocuments makeObjectsPerformSelector:@selector(hide)];

    // ...and terminate without any futher ado (or review of docs)
    return YES;
}

// NSWorkspace notifications

- (void)workspaceWillPowerOff:(NSNotification *)notification;
{
    [[NSWorkspace sharedWorkspace] extendPowerOffBy:30000];
    [NSApp terminate:nil];
}

@end


@implementation ODDocumentController (Private)

- (void)showSplashPanel;
{
    [self showInfoPanel:nil];
    [infoPanel setLevel:NSFloatingWindowLevel];
    hideSplashPanelEvent = [[[OFScheduler mainScheduler] scheduleSelector:@selector(hideSplashPanel) onObject:self withObject:nil afterTime:[isa minimumSplashPanelDisplayTime]] retain];
}

- (void)hideSplashPanel;
{
    [infoPanel orderOut:nil];
    [hideSplashPanelEvent release];
    hideSplashPanelEvent = nil;
}

@end
