/*$Copyright:
 * Copyright (C) 1992.5.22. Recruit Co.,Ltd. 
 * Institute for Supercomputing Research
 * All rights reserved.
 * NewsBase  by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue
 *
 * You may freely copy, distribute and reuse the code in this program under 
 * following conditions.
 * - to include this notice in the source code, if it is to be distributed 
 *   with source code.
 * - to add the file named "COPYING" within the code, which shall include 
 *   GNU GENERAL PUBLIC LICENSE(*).
 * - to display an acknowledgement in binary code as follows: "This product
 *   includes software developed by Recruit Co.,Ltd., ISR."
 * - to display a notice which shall state that the users may freely copy,
 *   distribute and reuse the code in this program under GNU GENERAL PUBLIC
 *   LICENSE(*)
 * - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*)
 *
 *   (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
$*/

// Provides support routines for external local disk files. This exists
// as a Class only there are no instances.  Just a convienient way to have
// an object-oriented interface to local disk access.
// Eventually all input/ouput to the local disk should be handled by this
// Class.  Now, some local disk I/O is embedded in some objects.

#import "ILocalFileD.h"
#import <c.h>
#import <libc.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/dir.h>
#import <ctype.h>
#import "IArticleD.h"
#import "InfoD.h"
#import "ITextD.h"
#import "IMediaTable.h"
#import "IMediaD.h"
#import "IBinaryD.h"
#import "data_types.h"
#import <mach.h>
#import <appkit/NXImage.h>
#import <appkit/Pasteboard.h>
#import <appkit/OpenPanel.h>
#import <appkit/SavePanel.h>
#import "IConvertMIME.h"
#import "Localization.h"
#import "errdebug.h"

@implementation ILocalFileD

static NXAtom domain;
static NXAtom fileExtension;
static const char *types[] = {NEWS_FILE_EXTENSION, MM_FILE_EXTENSION,
    MIME_FILE_EXTENSION, NULL};
static char previousDirectory[MAXPATHLEN] = "";

+ initialize
{
    domain = NXUniqueStringNoCopy("local-file");
    fileExtension = NXUniqueStringNoCopy("local-file");
    return(self);
}

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

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

+ (BOOL)open
{
    const char *rootDirectoryName;
    OpenPanel *openPanel;

    if ((rootDirectoryName = NXGetDefaultValue(OWNER, SAVEDIRECTORY)) == 0) {
        rootDirectoryName = getenv("HOME");
    }
    openPanel = [OpenPanel new];
    [openPanel setTitle:"Open NewsBase Article"];
    switch([openPanel runModalForDirectory:rootDirectoryName file:NULL
        types:types]) {
    case 1:
        [self openFromName:[openPanel filename]];
        return(YES);
    case 0:
        return(NO);
    }
    return(YES);
}

+ (BOOL)openFromName:(const char *)filename
{
    IExternalD *external;

    DBG(1, ;);
    external = [[IExternalD
        allocFromZone:NXCreateZone(vm_page_size, vm_page_size, YES)]
        initWithDomain:domain andPath:filename];
    [external performDoubleClickAction2:self];
    return(YES);  // maybe a false positive return but can't be helped!
}

+ (IArticleD *)loadFromName:(const char *)path inZone:(NXZone *)zone
{
    const char *extension;
    InfoD *header;
    ITextD *text;
    DIR *dirp;
    struct direct *dp;
    IArticleD *article;
    Class MediaClass;
    IMediaD *mediaObject;
    NXStream *stream;
    IConvertMIME *conv;
    char workingDirectoryName[MAXPATHLEN];

    if ((extension = rindex(path, '.')) == 0) {
        NXRunAlertPanel(LoStr("NewsBase"), LoStr("file has no extension: %s"),
            NULL,NULL,NULL, path);
        return(nil);
    }
    ++extension;
    DBG(1, fprintf(stderr, "%s", path);)
    if (strcmp(extension, NEWS_FILE_EXTENSION) == 0) {
        // plain text
        if ((stream = NXMapFile(path, NX_READONLY)) == NULL) {
            NXRunAlertPanel(LoStr("NewsBase"),
                LoStr("file cannot be opened: %s"), NULL, NULL, NULL, path);
            return(nil);
        }
        article = [[IArticleD allocFromZone:zone] init];
        header = [[InfoD allocFromZone:zone] initWithKey:HEADER_INFO];
        [header readFromStream:stream];
        text = [[ITextD allocFromZone:zone] initWithKey:PLAINTEXT];
        [text readFromStream:stream];
        [article insertKeyedObject:(IKeyedObject *)header];
        [article insertKeyedObject:text];
        NXCloseMemory(stream, NX_FREEBUFFER);
        return(article);
    } else if (strcmp(extension, MM_FILE_EXTENSION) == 0) {
        // multimedia 
        article = [[IArticleD allocFromZone:zone] init];
        getwd(workingDirectoryName);
        if (chdir(path) == -1) {
            NXRunAlertPanel(LoStr("NewsBase"),"%s: %s",NULL,NULL,NULL
                ,strerror(errno), path);
            return(nil);
        }
        if ((dirp = opendir(path)) == NULL) {
            NXRunAlertPanel(LoStr("NewsBase"),"%s: %s",NULL,NULL,NULL
                ,strerror(errno), path);
            return(nil);
        }
        for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
            if (dp->d_name[0] == '.') {
                continue;
            }
            if ((extension = rindex(dp->d_name, '.')) != 0) {
                ++extension;
            }
            // There are really two kinds of .rtf objects: index.rtf and
            // regular .rtf objects.  The media table gives the object for
            // index.rtf.  Regular .rtf objects can be handled as binary
            // objects.
            if (extension != 0 && (strcmp(extension, RTF_FILE_EXTENSION)
                != 0 || strcmp(dp->d_name, INDEX_RTF) == 0) &&
                (MediaClass = [IMediaTable mediaClassForFileExtension:
                extension]) != Nil) {
                mediaObject = [[(id)MediaClass allocFromZone:zone]
                    initWithKey:dp->d_name];
            } else {
                // Unknown file type use generic binary object
                mediaObject = [[IBinaryD allocFromZone:zone]
                    initWithKey:dp->d_name];
            }
            if ([mediaObject readFromFile:dp->d_name] == YES) {
                [article insertKeyedObject:mediaObject];
            } else {
                [mediaObject free];
                NXRunAlertPanel(LoStr("NewsBase"),
                    LoStr("file cannot be opened %s/%s"),
                    NULL,NULL,NULL, path, dp->d_name);
            }
        }
        chdir(workingDirectoryName);
        return(article);
    } else if (strcmp(extension, MIME_FILE_EXTENSION) == 0) {
        // MIME
        if ((stream = NXMapFile(path, NX_READONLY)) == NULL) {
            NXRunAlertPanel(LoStr("NewsBase"),
                LoStr("file cannot be opened: %s"), NULL, NULL, NULL, path);
            return(nil);
        }
        conv = [[IConvertMIME allocFromZone:zone] init];
        article = [conv convertToItem:stream];
        [conv free];
        NXCloseMemory(stream, NX_FREEBUFFER);
        return(article);
    } else {
        // unknown
        NXRunAlertPanel(LoStr("NewsBase"),
            LoStr("file has unknown extension: %s"), NULL, NULL, NULL, path);
        return(nil);
    }
    return(nil);
}

+ setSaveDomainAndPath:(IArticleD *)article
{
    const char *directory;
    const char *filename;
    SavePanel *savePanel;

    [self directory:&directory andFilename:&filename forArticle:article];
    savePanel = [SavePanel new];
    [savePanel setTitle:"Save NewsBase Article"];
    if ([article objectWithKey:INDEX_RTF] != nil) {
        [savePanel setRequiredFileType:MM_FILE_EXTENSION];
    } else {
        [savePanel setRequiredFileType:NEWS_FILE_EXTENSION];
    }
    if (([savePanel runModalForDirectory:directory file:filename]) == 1) {
        [[article external] setDomain:domain andPath:[savePanel filename]];
        strncpy(previousDirectory, [savePanel filename],
            sizeof(previousDirectory) - 1);
        *rindex(previousDirectory, '/') = '\0';
        return(self);
    } else {
        return(nil);
    }
}

+ (BOOL)save:anObject toName:(const char *)path
{
    char component[256];
    char *ptr;
    const char *objectKey;
    NXStream *stream;
    int n;
    id mediaObject;
    char workingDirectoryName[MAXPATHLEN];
    char path2[MAXPATHLEN];
    const char *thePath;

    if ([anObject class] != [IArticleD class]) {
        return(NO);
    }
    if ([anObject objectWithKey:INDEX_RTF] == nil) {
        stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
        [[anObject objectWithKey:HEADER_INFO] writeToStream:stream];
        NXWrite(stream, "\r\n", 2);
        [[anObject objectWithKey:PLAINTEXT] writeToStream:stream];
        NXSaveToFile(stream, path);
        NXCloseMemory(stream, NX_FREEBUFFER);
        // update path for this domain
        [[anObject external] setDomain:domain andPath:path];
        strncpy(previousDirectory, path, sizeof(previousDirectory) - 1);
        *rindex(previousDirectory, '/') = '\0';
    } else {
        // add mm extension if neccessary
        if ((ptr = rindex(path, '/'), ptr = rindex(ptr, '.')) == 0 ||
            strcmp(ptr + 1, MM_FILE_EXTENSION) != 0) {
            sprintf(path2, "%s.%s", path, MM_FILE_EXTENSION);
            STRDBG(path2);
            thePath = path2;
        } else {
            thePath = path;
        }
        // this is a multimedia article so make multimedia directory
        mkdir(thePath, 0755);
        getwd(workingDirectoryName);
        chdir(thePath);
        for (n = 0; (mediaObject = [anObject objectAt:n]) != nil;++n) {
            // skip external objects
            if ([mediaObject respondsTo:@selector(writeToStream:)] == NO) {
                continue;
            }
            // make sure the key can be used as a filename
/*
            if ((ptr = rindex(objectKey = [mediaObject key], '/')) == 0) {
                strncpy(component, objectKey, sizeof(component));
            } else {
                strncpy(component, ptr + 1, sizeof(component));
            }
            component[sizeof(component) - 1] = '\0';
            for (ptr = component; *ptr != '\0'; ++ptr) {
                if (isalnum(*ptr) == 0 && *ptr != '.') {
                    *ptr = '_';
                }
            }
*/
            stream = NXOpenMemory(NULL, 0, NX_READWRITE);
            [mediaObject writeToStream:stream];
            NXSaveToFile(stream, [mediaObject key]);
            NXCloseMemory(stream, NX_FREEBUFFER);
        }
        chdir(workingDirectoryName);
        // update path for this domain
        [[anObject external] setDomain:domain andPath:thePath];
        strncpy(previousDirectory, path, sizeof(previousDirectory) - 1);
        *rindex(previousDirectory, '/') = '\0';
    }
    return(YES);
}

+ (BOOL)saveWithoutPrompt:(IArticleD *)saveArticle
{
    const char *externalPath;
    BOOL flag;
    char buffer[2 * MAXPATHLEN];	

    // use the name for this domain stored in the external object
    if ((externalPath = [[saveArticle external] keyForDomain:domain
        isDirty:&flag]) != NULL) {
        strncpy(buffer, externalPath, sizeof(buffer));
        // strip external type extension
        *rindex(buffer, '.') = '\0';
        return([ILocalFileD save:saveArticle toName:buffer]);
    }
    return(NO);
}

+ (BOOL)save:(IArticleD *)saveArticle
{
    const char *directory;
    const char *filename;
    SavePanel *savePanel;

    [self directory:&directory andFilename:&filename forArticle:saveArticle];
    savePanel = [SavePanel new];
    [savePanel setTitle:"Save NewsBase Article"];
    if ([saveArticle objectWithKey:INDEX_RTF] != nil) {
        [savePanel setRequiredFileType:MM_FILE_EXTENSION];
    } else {
        [savePanel setRequiredFileType:NEWS_FILE_EXTENSION];
    }
    if (([savePanel runModalForDirectory:directory file:filename]) == 0) {
        NXRunAlertPanel(LoStr("NewsBase"),LoStr("%s will not be saved."),
            NULL,NULL,NULL, filename);
        return(NO);
    }
    filename = [savePanel filename];
    [ILocalFileD save:saveArticle toName:filename];
    return(YES);
}

+ (BOOL)saveAsMIME:(IArticleD *)saveArticle
{
    IConvertMIME *conv;
    NXStream *articleStream;
    const char *directory;
    const char *filename;
    SavePanel *savePanel;

    conv = [[IConvertMIME alloc] init];
    articleStream = NXOpenMemory(NULL, 0, NX_READWRITE);
    [conv convertToMIME:saveArticle stream:articleStream];
    [conv free];
    [self directory:&directory andFilename:&filename forArticle:saveArticle];
    savePanel = [SavePanel new];
    [savePanel setTitle:"Save NewsBase Article"];
    [savePanel setRequiredFileType:MIME_FILE_EXTENSION];
    if (([savePanel runModalForDirectory:directory file:filename]) == 0) {
        NXRunAlertPanel(LoStr("NewsBase"),LoStr("%s will not be saved."),
            NULL,NULL,NULL, filename);
        return(NO);
    }
    NXSaveToFile(articleStream, [savePanel filename]);
    NXCloseMemory(articleStream, NX_FREEBUFFER);
    return(YES);
}


// construct path for disk file

+ directory:(const char **)directory andFilename:(const char **)filename
    forArticle:saveArticle
{
    const char *externalPath;
    const char *rootDirectory;
    const char *newsgroup, *subject;
    int articleNum;
    char number[16];
    char *ptr, *commaPtr;
    static char buffer[MAXPATHLEN], *bufferEnd;
    InfoD *header;
    BOOL flag;

    bufferEnd = &buffer[sizeof(buffer) - 1];
    *bufferEnd = '\0';
    // if it already has a name for a disk file then use that name
    if ((externalPath = [[saveArticle external] keyForDomain:domain
        isDirty:&flag]) != NULL) {
        strncpy(buffer, externalPath, sizeof(buffer));
        *directory = buffer;
        ptr = rindex(buffer, '/');
        *ptr = '\0';
        *filename = ++ptr;
        *rindex(ptr, '.') = '\0';
        *rindex(ptr, '.') = '\0';
        return(self);
    }
    *directory = buffer;
    header = [saveArticle objectWithKey:HEADER_INFO];
    // use previous directory if one exists
    if (previousDirectory[0] != '\0') {
        strncpy(buffer, previousDirectory, sizeof(buffer) - 1);
        ptr = index(buffer, '\0');
    } else {
        // construct it from defaults and newsgroup name
        if ((rootDirectory = NXGetDefaultValue(OWNER, SAVEDIRECTORY)) == 0) {
            rootDirectory = getenv("HOME");
        }
        strncpy(buffer, rootDirectory, sizeof(buffer) - 1);
        // add newsgroup name to path
        ptr = index(buffer, '\0');
        if ((newsgroup = (const char *)[header infoForKey:GROUPNAME]) != NULL
            && newsgroup[0] != '\0') {
            if (bufferEnd - ptr) {
            *ptr++ = '/';
            strncpy(ptr, newsgroup, bufferEnd - ptr);
            // use first newsgroup name
            if ((commaPtr = index(ptr, ',')) != 0) {
                *commaPtr = '\0';
            }
            // change '.' to '/'
            for (; *ptr != '\0'; ++ptr) {
                if (*ptr == '.') {
                    *ptr = '/';
                }
            }
            }
        }
    }
    STRDBG(buffer);
    // now construct filename
    *filename = ++ptr;
    if ((subject = (const char *)[header infoForKey:SUBJECT]) != NULL &&
        subject[0] != '\0') {
        if (bufferEnd - ptr > 0) {
            strncpy(ptr, subject, bufferEnd - ptr);
            // replace non-alphanumerics with '_'
            for (; *ptr != '\0'; ++ptr) {
                if (isalnum(*ptr) == 0) {
                    *ptr = '_';
                }
            }
        }
    }
    if ((articleNum = (int)[header infoForKey:ARTICLE_NUM]) != 0) {
        sprintf(number, "-%d", articleNum);
        strncpy(ptr, number, bufferEnd - ptr);
    }
    return(self);
}

@end
