#import "IArticleD.h"
#import <c.h>
#import <libc.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/dir.h>
#import <ctype.h>
#import <streams/streams.h>
#import <appkit/Panel.h>
#import "data_types.h"
#import "InfoD.h"
#import "ITextD.h"
#import "IAppDelegate.h"
#import "IMediaD.h"
#import "IBinaryD.h"
#import "IMMEditor.h"
#import "IMediaTable.h"
#import "IExternalTable.h"
#import "ILocalFileD.h"
#import "INewsD.h"
#import "Localization.h"
#import "errdebug.h"

// IArticleD is the article object.  It must support some but not all of the
// methods of the media class.  Basically, the article object contains the
// data for the article.  An IMMEditor object is needed to view/edit the
// object. 

@implementation IArticleD

static NXAtom fileExtension;
static IMMEditor *theEditor;
+ initialize
{
    fileExtension = NXUniqueStringNoCopy(MM_FILE_EXTENSION);
    return(self);
}

+ (NXAtom)fileExtension
{
    return(fileExtension);
}

+ (NXAtom)pasteboardType
{
    return(NULL);
}

+ editorWillBeFreed:anEditor
{
    if (anEditor = theEditor) {
        theEditor = nil;
    }
    return(self);
}

- init
{
    [super initWithKey:""];
    externals = [List allocFromZone:[self zone]];
    objects = [List allocFromZone:[self zone]];
    return(self);
}

- initWithKey:(const char *)aKey
{
    [super initWithKey:aKey];
    externals = [List allocFromZone:[self zone]];   // list of external objects
    objects = [List allocFromZone:[self zone]];     // list of media objects
    return(self);
}

- openEditor
{
    if (editor == nil) {
        [self editor];
    }
    [[editor window] makeKeyAndOrderFront:self];
    return(editor);
}

// editor will create a new editor or reuse an existing editor for this article
// if one does not already exists.

- editor
{
    const char *multipleEditors;

    if (editor == nil) {
        [external incrementReferenceCount];
        if ((multipleEditors = NXGetDefaultValue(OWNER, MULTIPLEVIEWERFLAG))
            != 0 && multipleEditors[0] == 'N' && theEditor != nil &&
            [theEditor detachArticle] != nil) {
            // reuse the existing editor
            editor = theEditor;
        } else {
            LoadLocalNib("MMRTFEditor.nib",self,NO,NULL);
	    // MMRTFEditor.nib should be located in default zone
//            [NXApp loadNibSection:"MMRTFEditor.nib" owner:self withNames:NO
//                fromZone:NXCreateZone(vm_page_size, vm_page_size, YES)];
            [editor setSizeAndPosition];
            theEditor = editor;
        }
        [[editor window] makeKeyAndOrderFront:self];
        [editor displayArticle:self];
    }
    [[editor window] makeKeyAndOrderFront:self];
    [[editor window] makeFirstResponder:[editor text]];
    [[NXApp delegate] editorDidBecomeKey:self];
    [[NXApp delegate] setPreviousContext:[NXApp activateSelf:YES]];
    return(editor);
}

- setEditor:theEditor
{
    editor = theEditor;
    return(self);
}

- editorWillClose:sender
{
    editor = nil;
    if ([external decrementReferenceCount] == 0) {
        [external free];
    }
    return(self);
}

- setExternal:(IExternalD *)theExternal
{
    external = theExternal;
    return(self);
}

- external
{
    return(external);
}

// Recursive save is neccessary for hypermedia documents.  It should handle
// both trees and graphs.  The only real difficulty is that, a referenced
// article must have its domain and key set before the article doing the
// referencing is saved.  

- recursiveSaveToDomain:(NXAtom)domain
{
    int i;
    id externalObject;
    id object;
    BOOL flag;
    IArticleD *article;
    NXAtom fetchDomain;
    const char *fetchPath;

    article = [[self editor] sendArticle];
    // Very important that the proper domain be set before accessing key.
    // Although the representation has not not been written to the external
    // we must set the domain and path and clear dirty flag here otherwise
    // for graphs with loops this routine would do an infinite recursion.  
    // Hence this is needed for recursive post of graphs with loops.  Setting
    // the domain and path may be interpreted as a save is scheduled for
    // that domain and path.
    if ([(id)[IExternalTable externalModuleForDomain:domain]
        setSaveDomainAndPath:article] == nil) {
        return(nil);
    }
    for (i = 0; (externalObject = [externals objectAt:i]) != nil; ++i) {
        fetchDomain = [externalObject currentDomain];
        fetchPath = [externalObject currentPath];
        // check if already exists on NNTP server and post if not 
        if ([externalObject keyForDomain:domain isDirty:&flag] == NULL
            || flag == YES) {
            // special handling for articles that are fetched from NNTP server
            // neccessary since they are fetched asychronously
            if ((object = [externalObject objectExists]) != nil ||
                (fetchDomain != [INewsD domain] && 
                (object = [externalObject object]) != nil)) {
                // external object really exists so do recursion
                if ([object recursiveSaveToDomain:domain] == nil) {
                    // cancelling in the middle results in a partial 
                    // recursive save i.e., no clean up is done
                    return(nil);
                }
            } else {
                 // since articles are fetched asynchronously from NNTP
                 // server object will be nill in this case
                 NXRunAlertPanel(LoStr(MMEDITOR),
                     LoStr("%s.%s does not exists locally and will not be "
                     "saved."), NULL, NULL, NULL, fetchPath, fetchDomain);
            }
        }
    }
    // At this point all referenced articles have already been saved so
    // they have valid keys in the domain
    if ([(id)[IExternalTable externalModuleForDomain:domain]
        saveWithoutPrompt:[editor sendArticle]] == YES) {
        return(self);
    } else {
        return(nil);
    }
}

// referenceAllObjects/unreferenceAllObjects is neccessary to create
// temporary references to objects in the article since the real references
// are created too late in some unforseen cases and the objects will be
// deleted.

- referenceAllObjects
{
    int i;
    id object;

    for (i = 0; (object = [self objectAt:i]) != nil; ++i) {
        if ([object respondsTo:@selector(incrementReferenceCount)] == YES) {
            [object incrementReferenceCount];
        }
    }
    return(self);
}

- unreferenceAllObjects
{
    int i;
    id object;

    for (i = 0; (object = [self objectAt:i]) != nil; ++i) {
        if ([object respondsTo:@selector(incrementReferenceCount)] == YES) {
            [object decrementReferenceCount];
        }
    }
    return(self);
}

- setObjectCount:(int)count
{
    objectCount = count;
    return(self);
}    

- (int)objectCount
{
    return(objectCount);
}

- (List *)externalsList
{
    return(externals);
}

- (List *)objectsList
{
    return(objects);
}

- free
{
    [externals freeObjects];
    [externals free];
    [objects free];
    return([super free]);
}

@end


