#import "IExternalD.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 <objc/hashtable.h>
#import <appkit/Panel.h>
#import "data_types.h"
#import "IExternalTable.h"
#import "ILocalFileD.h"
#import "INewsD.h"
#import "InfoD.h"
#import "ITextD.h"
#import "IMediaD.h"
#import "IBinaryD.h"
#import "IMMEditor.h"
#import "IMediaTable.h"
#import "Localization.h"
#import "errdebug.h"

// IExternalD links an object to its external representations.  Currently, the
// external representation is either a file on the local disk or an article on
// the NNTP server.  In the future a file on an anonymous ftp server can also
// be an external representation of an object.  The local disk and the NNTP
// server are considered to be distinct domains.  The link for an object in a
// domain is the name of the external representation for that object in the
// domain. For the file on the local disk the link is the path name of the
// file.  For an article on the NNTP server the link is the message-id of the
// article.  An object is allowed to more than one external representation. It
// may have a representation on the disk and a  representation on the NNTP
// server.  However, in this implementation only one link is allowed per
// domain.  This is logically incorrect and should be fixed.  IGraphicImage 
// objects and IEditor objects reference external representations of media
// objects and articles through an IExternalD object.  IExternalD also
// maintains a reference count to manage freeing. If there is more than one
// domain the last referenced domain is considered the current domain.  Since
// some methods (due to the way this software has evolved) will not allow you
// to specify the domain it is very important to set the current domain first.
// IExternalD exists only when they are referenced by a IGraphicImage or a
// IMMEditor object.  Further, at any time one of the following is true:
//      Only the IExternalD exists for an object.
//      The IExternalD and the object exists.
//      The IExternalD, the object and a viewer/editor for the object exists.

@implementation IExternalD

static NXAtom fileExtension;
static NXImage *image;
static NXImage *icon;
static List *externalsList;       // list of all IExternalD's

+ initialize
{
    fileExtension = NXUniqueStringNoCopy("external");
    image = [[NXImage alloc] initFromSection:"multi_icon.tiff"];
    externalsList = [[List allocFromZone:[self zone]] init];
    return(self);
}

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

+ setIcon:(NXImage *)theIcon
{
    icon = theIcon;
    return(self);
}

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

+ (NXImage *)icon
{
    return(icon);
}

// externalExistsWithDomain:andPath: is a Class method used search to all
// IExternalD's.  Neccessary since there should be only one IExternalD
// for an external representation.

+ externalExistsWithDomain:(NXAtom)domain andPath:(const char *)path
{
    int i;
    IExternalD *external;

    for (i = 0; (external = [externalsList objectAt:i]) != nil; ++i) {
        if ([external hasDomain:domain andPath:path] == YES) {
            return(external);
        }
    }
    return(nil);
}

- (const char *)key
//  The key for the current domain is returned.  You must have correctly
//  set the current domain for this to work properly.  This is really a
//  bad way to do this but this code evolved from code that did not need
//  domains.
{
    BOOL flag;
    const char *key;

    DBG(1, fprintf(stderr, "%s",
        [self keyForDomain:NULL isDirty:&flag]));
    key = [self keyForDomain:NULL isDirty:&flag];
    if (key != NULL) {
        return(key);
    } else {
        return("");
    }
}

- (const char *)keyForDomain:(NXAtom)domain isDirty:(BOOL *)dirtyFlag
// If domain == NULL return key for current domain. 
// Representation is dirty if it is inconsistent with current object
// that is the object has been edited and not resaved.
{
    struct artaddr *addr;

    if (domain != NULL) {
        for (addr = addrlist; addr != NULL && addr->aa_domain != domain;
            addr = addr->aa_next);
        if (addr != NULL) {
            curaddr = addr;
        } else {
            curaddr = NULL;
        }
    }
    if (curaddr != NULL) {
        if (curkey != NULL) {
            NXZoneFree([self zone], (char *)curkey);
        }
        curkey = NXZoneMalloc([self zone], strlen(curaddr->aa_name) +
            strlen(curaddr->aa_domain) + 2);
        sprintf((char *)curkey, "%s.%s", curaddr->aa_name, curaddr->aa_domain);
        // path is dirty if it is inconsistent with current article or path
        // belongs to a temporary article
        *dirtyFlag = (curaddr->aa_status && AS_DIRTY_MASK) != 0 ||
            strncmp(curaddr->aa_name, "/tmp/", 5) == 0;
        return(curkey);
    } else {
        return(NULL);
    }
}

- init
{
    addrlist = NULL;
    curaddr = NULL;
    object = nil;
    [externalsList addObject:self];
    return(self);
}

- initWithDomain:(NXAtom)domain andPath:(const char *)path
{
    int i;
    IExternalD *external;

    for (i = 0; (external = [externalsList objectAt:i]) != nil; ++i) {
        if ([external hasDomain:domain andPath:path] == YES) {
            // already exists!
            [self free];
            return(external);
        }
    }
    addrlist = NXZoneMalloc([self zone],
        sizeof(struct artaddr) + strlen(path));
    addrlist->aa_next = NULL;
    addrlist->aa_domain = domain;
    addrlist->aa_status = 0;
    strcpy(addrlist->aa_name, path);
    curaddr = addrlist;
    object = nil;
    [externalsList addObject:self];
    return(self);
}

// setDomain:andPath: sets the current domain and path and also clears the
// dirty flag.  This is used when writing a new representation to an
// external domain.

- setDomain:(NXAtom)domain andPath:(const char *)path
{
    struct artaddr **addrp, *next;

    next = NULL;
    for (addrp = &addrlist; *addrp != NULL; addrp = &(*addrp)->aa_next) {
        if ((*addrp)->aa_domain == domain) {
            if (strcmp((*addrp)->aa_name, path) == 0) {
                // already exists so just exit
                curaddr = *addrp;
                curaddr->aa_status = 0;
                return(self);
            } else {
                // remove previous path for this domain    
                next = (*addrp)->aa_next;
                NXZoneFree([self zone], *addrp);
                break;
            }
        }
    }
    // add new path
    *addrp = NXZoneMalloc([self zone],
        sizeof(struct artaddr) + strlen(path));
    (*addrp)->aa_next = next;
    (*addrp)->aa_domain = domain;
    (*addrp)->aa_status = 0;
    strcpy((*addrp)->aa_name, path);
    curaddr = *addrp;
    return(self);
}

- (BOOL)hasDomain:(NXAtom)domain andPath:(const char *)path 
{
    struct artaddr *addr;

    for (addr = addrlist; addr != NULL; addr = addr->aa_next) {
        if (addr->aa_domain == domain && strcmp(addr->aa_name, path) == 0) {
            return(YES);
        }
    }
    return(NO);
}

- (NXAtom)currentDomain
{
    if (curaddr != NULL) {
        return(curaddr->aa_domain);
    } else {
        return(addrlist->aa_domain);
   }
}

- (const char *)currentPath
{
    if (curaddr != NULL) {
        return(curaddr->aa_name);
    } else {
        return(addrlist->aa_name);
   }
}

- (NXImage *)image
{
    return(image);
}

- setReferenceCount:(int)newCount
{
    refCount = newCount;
    return self;
}

- (int)referenceCount
{
    return (refCount);
}

- (int)incrementReferenceCount
{
    return(++refCount);
}

- (int)decrementReferenceCount
{
    return(--refCount);
}

- (int)size
{
    return(0);
}

// object returns the object.  If the object is not loaded object will 
// load it from the current domain
- object
{
    if (object == nil) {
        if (curaddr == NULL) {
            curaddr = addrlist;
        }
        if ((object = [(id)[IExternalTable externalModuleForDomain:
            curaddr->aa_domain] loadFromName:curaddr->aa_name
                inZone:[self zone]]) != nil) {
            [object setExternal:self];
        }
    }
    return(object);
}

- setObject:theObject
{
    object = theObject;
    return(self);
}

- objectExists
{
    return(object);
}

- performDoubleClickAction:sender
{
    [self perform:@selector(performDoubleClickAction2:) with:sender
         afterDelay:1 cancelPrevious:YES];
    return(self);	
}

// performDoubleClickAction2: is neccessary because of race condition
- performDoubleClickAction2:sender
{
    if (object == nil) {
        [self object];
    }
    if (object != nil) {
        [object openEditor];
    }
    return(self);
}

// free only if reference count is 0

- markAsDirty
{
    struct artaddr *addr;

    for (addr = addrlist; addr != NULL; addr = addr->aa_next) {
        addr->aa_status |= AS_DIRTY_MASK;
    }
    return(self);
}

- free
{
    NXZone *zone;

    if (--refCount <= 0) {
        if (object != nil) {
            [object free];
        }
        [externalsList removeObject:self];
        zone = [self zone];
        [super free];
        NXDestroyZone(zone);
        return(nil);
    }
    return(self);
}

@end














