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

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

#import "ODDocumentController.h"

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

#define DOCUMENT_START_X (64.0)
#define DOCUMENT_START_Y ( 8.0)
#define DOCUMENT_DELTA_X (20.0)
#define DOCUMENT_DELTA_Y (25.0)

@interface ODDocument (Private)
+ (NSPoint)_locationForDocument:(ODDocument *)aDocument;
- (BOOL)_saveWithNewPath:(BOOL)doNewPath retainNewPath:(BOOL)doRetain;
@end


@implementation ODDocument

static NSPoint documentLocationOffset = (NSPoint) { DOCUMENT_START_X, DOCUMENT_START_Y };

+ (void)setDocumentLocationOffset:(NSPoint)offset;
{
    documentLocationOffset = offset;
}

+ (NSPoint)documentLocationOffset;
{
    return documentLocationOffset;
}

+ (BOOL)isFilePackage;
{
    return NO;
}

+ (NSString *)nibFilePath;
{
    return [[NSBundle mainBundle] pathForResource:[self description] ofType:@"nib"];
}

+ (ODDocument *)documentWithPath:(NSString *)aPath;
{
    return [[[self alloc] initWithPath:aPath] autorelease];
}


// Init and dealloc

- initWithPath:(NSString *)aPath;
{
    [super init];
    [self loadNib];

    if (aPath)
        [window setTitle:[NSString stringWithFormat:@"Loading %@%@", [aPath lastPathComponent], [NSString horizontalEllipsisString]]];

    // Don't make key yet, because inspectors will try to inspect the unloaded document.
    [window orderFront:nil];
    PSWait();

    if (![self readFromPath:aPath]) {
        // we can't be the active document (if we were)
        [self deactivateDocument];

        // make sure we don't get displays after getting closed/dealloced
        [window setDelegate:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:window];
        [window setViewsNeedDisplay:NO];
	[window close];

	[self release];
	return nil;
    }

    // Ok, now that we are loaded, make key
    [window makeKeyAndOrderFront:nil];
    [self setDocumentPath:aPath];
    [self setIsEdited:NO];

    _docFlags.isClosing = NO;
    _docFlags.isNewDocument = (aPath == nil);

    return self;
}

- (void)dealloc;
{
    if ([window delegate] == self)
        [window setDelegate:nil];
    if (![window isReleasedWhenClosed])
	[window release];
    [path release];
    [super dealloc];
}


// API

- (void)setDocumentPath:(NSString *)aPath;
{
    static unsigned int untitledCount = 0;
    ODDocumentController *controller;

    controller = [ODDocumentController sharedInstance];

    if (path) {
        // new documents have not been registered yet
        [controller deregisterDocumentForPath:path];
        [path autorelease];
    }
    
    if (aPath)
	path = [aPath copy];
    else
	path = [[NSString alloc] initWithFormat:@"UNTITLED-%d", untitledCount++];

    [controller registerDocument:self forPath:path];

    [window setTitleWithRepresentedFilename:[self documentPath]];
}

- (NSString *)documentPath;
{
    return path;
}

- (NSWindow *)window;
{
    return window;
}

- (void)activateDocument;
{
    NSNotificationCenter *center;

    center = [NSNotificationCenter defaultCenter];
    [center postNotificationName:ODDocumentWillActivate object:self];
    [window makeKeyAndOrderFront:nil];
    [[ODDocumentController sharedInstance] setActiveDocument:self];
    [center postNotificationName:ODDocumentDidActivate object:self];
}

- (void)deactivateDocument;
{
    NSNotificationCenter *center;
    ODDocumentController *controller;

    controller = [ODDocumentController sharedInstance];
    if ([controller activeDocument] != self)
        return;

    center = [NSNotificationCenter defaultCenter];
    [center postNotificationName:ODDocumentWillDeactivate object:self];
    [controller setActiveDocument:nil];
    [center postNotificationName:ODDocumentDidDeactivate object:self];
}

- (void)hide;
{
    [window orderOut:nil];
}

- (void)setIsEdited:(BOOL)edited;
{
    [window setDocumentEdited:edited];
}

- (BOOL)isEdited;
{
    return [window isDocumentEdited];
}

- (BOOL)isNewDocument;
{
    return _docFlags.isNewDocument;
}

- (BOOL)saveIfNeeded;
{
    if ([self isNewDocument]) {
        if ([self _saveWithNewPath:YES retainNewPath:YES]) {
            _docFlags.isNewDocument = NO;
            return YES;
        }
        return NO;
    }
    
    if (![self isEdited])
        return YES;

    return [self _saveWithNewPath:NO retainNewPath:YES];
}

- (ODDocumentCloseResult)close;
{
    ODDocumentCloseResult result = ODDocumentSave;
    BOOL doWindowClose = !_docFlags.isClosing;

    _docFlags.isClosing = YES;

    if ([self isEdited]) {
        switch ([self willLoseChanges:@"Save changes to %@?" canSave:YES]) {
            case ODDocumentCancel:
                result = ODDocumentCancel;
                break;
            case ODDocumentDiscard:
                result = ODDocumentDiscard;
                break;
            case ODDocumentSave:
            default:
                if ([self saveIfNeeded])
                    result = ODDocumentSave;
                else
                    result = ODDocumentCancel;
                break;
        }
    }

    if (result == ODDocumentSave) {
        NSNotificationCenter *center;

        center = [NSNotificationCenter defaultCenter];
        [center postNotificationName:ODDocumentWillClose object:self];
        if (doWindowClose)
            [window close];
        [[ODDocumentController sharedInstance] deregisterDocumentForPath:path];
        [center postNotificationName:ODDocumentDidClose object:self];
    }

    _docFlags.isClosing = NO;
    
    return result;
}


// Actions

- (IBAction)save:(id)sender;
{
    if (![self isNewDocument])
        [self _saveWithNewPath:NO retainNewPath:YES];
    else if ([self _saveWithNewPath:YES retainNewPath:YES])
        _docFlags.isNewDocument = NO;
}

- (IBAction)saveAs:(id)sender;
{
    [self _saveWithNewPath:YES retainNewPath:YES];
}

- (IBAction)saveToFile:(id)sender;
{
    [self _saveWithNewPath:YES retainNewPath:NO];
}

- (IBAction)revert:(id)sender;
{
    if ([self willLoseChanges:@"Revert changes to %@?" canSave:NO] == ODDocumentDiscard)
        [self readFromPath:path];
}

- (IBAction)closeDocument:(id)sender;
{
    [self close];
}

- (IBAction)switchToNextDocument:(id)sender;
{
    [[ODDocumentController sharedInstance] switchToNextDocumentFromDocument:self];
}

- (IBAction)switchToPreviousDocument:(id)sender;
{
    [[ODDocumentController sharedInstance] switchToPreviousDocumentFromDocument:self];
}

- (IBAction)bringWindowForward:(id)sender;
{
    [window orderFront:nil];
}

- (IBAction)pushWindowBackward:(id)sender;
{
    [window orderBack:nil];
}



// NSWindow delegate

- (BOOL)windowShouldClose:(id)sender;
{
    _docFlags.isClosing = YES;
    if ([self close] == ODDocumentCancel)
	return NO;
    return YES;
}

// NSWindow notifications

- (void)windowDidBecomeMain:(NSNotification *)notification;
{
    [self activateDocument];
}

- (void)windowDidResignMain:(NSNotification *)notification;
{
    [self deactivateDocument];
}

@end


@implementation ODDocument (SubclassesOnly)

+ (NSString *)fileExtension;
{
    OBRequestConcreteImplementation(self, _cmd);
    return nil;
}

- (BOOL)writeToPath:(NSString *)aPath;
{
    OBRequestConcreteImplementation(self, _cmd);
    return NO;
}

- (BOOL)readFromPath:(NSString *)aPath;
{
    OBRequestConcreteImplementation(self, _cmd);
    return NO;
}

- (void)loadNib;
{
    NSString *nibPath;
    NSPoint aPoint;

    if (!(nibPath = [isa nibFilePath]))
	[NSException raise:@"ODDocument_NibError" format:@"Unable to find nib for %@", isa];
    nibPath = [nibPath lastPathComponent];
    if (![NSBundle loadNibNamed:nibPath owner:self])
	[NSException raise:@"ODDocument_NibError" format:@"Unable to load %@", nibPath];

    aPoint = [isa _locationForDocument:self];
    [window setFrameOrigin:aPoint];
    [window setDelegate:self];
}

- (ODDocumentCloseResult)willLoseChanges:(NSString *)reason canSave:(BOOL)canSave;
{
    int choice;

    if (!canSave) {
	choice = NSRunAlertPanel([self documentPath], reason, @"Discard Changes", @"Cancel",  nil, [path lastPathComponent]);
        switch (choice) {
            case NSAlertDefaultReturn:
                return ODDocumentDiscard;
            default:
            case NSAlertAlternateReturn:
                return ODDocumentCancel;
        }
    } else {
	choice = NSRunAlertPanel([self documentPath], reason, @"Save", @"Discard Changes", @"Cancel", path);
        switch (choice) {
            case NSAlertDefaultReturn:
                return ODDocumentSave;
            case NSAlertAlternateReturn:
                return ODDocumentDiscard;
            default:
            case NSAlertOtherReturn:
                return ODDocumentCancel;
        }
    }
}

@end


@implementation ODDocument (Private)

+ (NSPoint)_locationForDocument:(ODDocument *)aDocument;
{
    static unsigned int count = 0;
    NSWindow *docWindow;
    NSRect windowFrame, mainScreenFrame;
    NSScreen *mainScreen;
    NSPoint docPoint;
    NSMenu *mainMenu;

    mainMenu = [NSApp mainMenu];
    mainScreen = [NSScreen mainScreen];
#ifdef YELLOW_BOX
    mainScreenFrame = [mainScreen visibleFrame];
#else
    mainScreenFrame = [mainScreen frame];
#endif

    docWindow = [aDocument window];
    windowFrame = [docWindow frame];

    // If we have no documents, go back to the starting upper-left position
    if (![[[ODDocumentController sharedInstance] openDocumentPaths] count])
	count = 0;

    docPoint.x = documentLocationOffset.x + DOCUMENT_DELTA_X * count;
    docPoint.y = NSHeight(mainScreenFrame) - (NSHeight(windowFrame) + documentLocationOffset.y + DOCUMENT_DELTA_Y * count);

    if (docPoint.x + NSWidth(windowFrame) > NSWidth(mainScreenFrame) || docPoint.y < 0.0) {
	count = 0;
	docPoint.x = NSWidth(mainScreenFrame) + documentLocationOffset.x;
	docPoint.y = NSHeight(mainScreenFrame) - (NSHeight(windowFrame) + documentLocationOffset.y);
    }
    count++;
    return docPoint;
}

// All varieties of save go through this routine.  It covers all the cases of running the Save Panel and retaining the path chosen.
- (BOOL)_saveWithNewPath:(BOOL)doNewPath retainNewPath:(BOOL)doRetain;
{
    NSSavePanel *savePanel;
    NSString    *savePath;
    BOOL         saved;
    NSString    *error;
    
    if (!path || doNewPath) {
        // saveAs or saveTo or untitled
        savePanel = [NSSavePanel savePanel];
        [savePanel setRequiredFileType:[isa fileExtension]];
        [savePanel setTreatsFilePackagesAsDirectories:[isa isFilePackage]];

        if ([savePanel runModalForDirectory:@"" file:@""]) {
            savePath = [savePanel filename];
        } else
            // aborted out?
            return NO;
    } else
        // ordinary Save
        savePath = path;

    NS_DURING {
        saved = [self writeToPath:savePath];
        if (!saved)
            error = @"";
    } NS_HANDLER {
        saved = NO;
        error = [localException reason];
    } NS_ENDHANDLER;

    if (!saved) {
        NSRunAlertPanel(@"Error", @"Unable to save %@.  %@", @"OK", nil, nil, savePath, error);
        return NO;
    }
    
    // update the document path if requested
    if (doRetain)
        [self setDocumentPath:savePath];

    [self setIsEdited:NO];
    return YES;
}

@end

NSString *ODDocumentWillClose = @"ODDocumentWillClose";
NSString *ODDocumentDidClose = @"ODDocumentDidClose";

NSString *ODDocumentWillActivate = @"ODDocumentWillActivate";
NSString *ODDocumentDidActivate = @"ODDocumentDidActivate";

NSString *ODDocumentWillDeactivate = @"ODDocumentWillDeactivate";
NSString *ODDocumentDidDeactivate = @"ODDocumentDidDeactivate";
