// DocControl.m
//
// Free software created 1 Feb 1992
// by Paul Burchard <burchard@math.utah.edu>.

#import "DocControl.h"
#import "Doc.h"
#import <appkit/appkit.h>
#import <sys/file.h>
#import <sys/param.h> 		/* for MAXPATHLEN */
#import <string.h>


@implementation DocControl

- (int)computeTypeIndexOf:(const char *)fileName inList:(const char **)theTypes
{
    int hNum;
    const char **aType, *thisType;

    if(!fileName || !theTypes) return (-1);
    if(!(thisType = strrchr(fileName, '/'))) thisType = fileName;
    if(thisType = strrchr(thisType, '.')) thisType++;
    else thisType = "";
    for(hNum=0, aType=theTypes; *aType; hNum++, aType++)
	if(0 == strcmp(*aType, thisType)) break;
    if(!*aType) return (-1);
    return hNum;
}

- (int)getTypeAndOpenPath:(char *)fileBuf
    defaultFolder:(const char *)theFolder types:(const char **)theTypes
{
    int hNum;
    const char *const *files;
    static id openPanel = nil;

    if(!fileBuf || !theTypes) return (-1);
    if(!openPanel) openPanel = [OpenPanel new];
    [NXApp setAutoupdate:NO];
    if(theFolder && *theFolder)
    {
	if(![openPanel runModalForDirectory:theFolder
	    file:NULL types:theTypes])
	    { [NXApp setAutoupdate:YES]; return (-1); }
    }
    else
    {
	if(![openPanel runModalForTypes:theTypes])
	    { [NXApp setAutoupdate:YES]; return (-1); }
    }
    files = [openPanel filenames];
    if(!files || !files[0])
	{ [NXApp setAutoupdate:YES]; return (-1); }
    strcpy(fileBuf, files[0]);
    [NXApp setAutoupdate:YES];
    if((hNum=[self computeTypeIndexOf:fileBuf inList:theTypes]) < 0)
	return (-1);
    return hNum;
}

// defaultPath: overrides defaultFolder: arg
- (BOOL)getSavePath:(char *)fileBuf
    defaultFolder:(const char *)theFolder defaultPath:(const char *)origName
    type:(const char *)theType
{
    static id savePanel = nil;
    BOOL ok, as;
    char dirName[MAXPATHLEN+1], fileName[MAXPATHLEN+1];

    if(!savePanel)
    {
    	savePanel = [SavePanel new];
	[savePanel setDirectory:[[self class] defaultFolder]];
    }
    if(theType && *theType) [savePanel setRequiredFileType:theType];
    as = NO;
    if(origName && *origName)
    {
	char *p;
	as = YES;
	strcpy(dirName, origName);
	if(!(p=strrchr(dirName, '/'))) as = NO;
	else if(p!=dirName) *p = 0;
	if(p = strrchr(origName, '/')) strcpy(fileName, p+1);
	else strcpy(fileName, origName);
	if(!fileName[0]) as = NO;
    }
    if(!as && theFolder && *theFolder) [savePanel setDirectory:theFolder];

    [NXApp setAutoupdate:NO];
    if(as) ok = [savePanel runModalForDirectory:dirName file:fileName];
    else ok = [savePanel runModal];
    if(!ok) { [NXApp setAutoupdate:YES]; return NO; }
    strcpy(fileBuf,[savePanel filename]);
    [NXApp setAutoupdate:YES];
    return YES;
}

- init
{
    [super init];
    convertWindowToDoc = [[HashTable alloc] initKeyDesc:"@" valueDesc:"@"];
    return self;
}

- setDocHandlers:HandlerList
{
    int i, n;
    
    if((n=[HandlerList count]) <= 0) return nil;
    if(fileTypes) NX_FREE(fileTypes);
    NX_MALLOC(fileTypes, const char *, n+1);
    if(!fileTypes) return nil;
    for(i=0; i<n; i++) fileTypes[i] = [[HandlerList objectAt:i] fileType];
    fileTypes[i] = 0;
    [DocHandlers free];
    DocHandlers = HandlerList;
    return self;
}

+ (const char *)defaultFolder
{
    return NXHomeDirectory();
}

- appDidInit:sender
{
    if(launchWithCreateDoc) [self createDoc:self];
    return self;
}

- free
{
    [convertWindowToDoc free];
    if(fileTypes) NX_FREE(fileTypes);
    [DocHandlers free];
    return [super free];
}

- (BOOL)appAcceptsAnotherFile:sender
{
    return YES;
}

- (int)app:sender openFile:(const char *)fileName type:(const char *)aType
{
    int hNum, nHandlers = [DocHandlers count];
    
    if(!fileName || !aType || [DocHandlers count]<=0) return NO;
    launchWithCreateDoc = NO;
    for(hNum=0; hNum<nHandlers; hNum++)
    	if(0 == strcmp(aType, [[DocHandlers objectAt:hNum] fileType]))
	    break;
    if(hNum >= nHandlers) return NO;
    if([self openForHandlerAt:hNum name:fileName]) return YES;
    else return NO;
}

- setMainDoc:aDoc
{
    [[aDoc window] makeKeyAndOrderFront:nil];
    return self;
}

- mainDoc
{
    if(![NXApp mainWindow]) return nil;
    return (id)[convertWindowToDoc valueForKey:(void *)[NXApp mainWindow]];
}

- stringTable
{
    return stringTable;	
}

- createDocForHandlerAt:(int)hNum
{
    int useNum;
    id theDoc;
    
    if([DocHandlers count] <= 0) return nil;
    if(hNum<0 || hNum>=[DocHandlers count]) useNum = 0;
    else useNum = hNum;
    theDoc = [[[DocHandlers objectAt:useNum] alloc] init];
    if(!theDoc || ![theDoc window]) { [theDoc free]; return nil; }
    [self setMainDoc:theDoc];
    [convertWindowToDoc
    	insertKey:(void *)[theDoc window] value:(void *)theDoc];
    [[theDoc window] setDocEdited:NO];
    return theDoc;
}

- createDoc:sender
{
    return [self createDocForHandlerAt:(-1)];
}

- createDoc1:sender
{
    return [self createDocForHandlerAt:0];
}

- createDoc2:sender
{
    return [self createDocForHandlerAt:1];
}

- createDoc3:sender
{
    return [self createDocForHandlerAt:2];
}

- handlerForFile:(const char *)fileName
{
    int hNum;
    
    if(!fileName  || [DocHandlers count]<=0 || !fileTypes) return nil;
    hNum = [self computeTypeIndexOf:fileName inList:fileTypes];
    if(hNum<0 || hNum>=[DocHandlers count]) return nil;
    return [DocHandlers objectAt:hNum];    
}

- openForHandlerAt:(int)hNum name:(const char *)fileName
{
    int gotNum = (-1);
    id theDoc, TheHandler;
    char fileBuf[MAXPATHLEN+1];
    const char *oneType[2] = {0, 0};

    if([DocHandlers count]<=0 || !fileTypes) return nil;
    
    // Get Doc handler and file name (putting latter into fileBuf).
    if(fileName)
    {
	strcpy(fileBuf, fileName);
    	if(hNum>=0 && hNum<[DocHandlers count]) gotNum = hNum;
	else gotNum = [self computeTypeIndexOf:fileName inList:fileTypes];
    }
    else if(hNum>=0 && hNum<[DocHandlers count])
    {
	oneType[0] = fileTypes[hNum];
	gotNum = [self getTypeAndOpenPath:fileBuf
	    defaultFolder:[[DocHandlers objectAt:hNum] defaultFolder] 
	    types:oneType];
    }
    else gotNum = [self getTypeAndOpenPath:fileBuf
    	defaultFolder:[[self class] defaultFolder]
	types:fileTypes];
    if(gotNum<0 || gotNum>=[DocHandlers count]) return nil;
    TheHandler = [DocHandlers objectAt:gotNum];
    
    // Check if this file is already open in some Doc; if so just pop up.
    if(theDoc = [TheHandler docForFileName:fileBuf])
    	{ [self setMainDoc:theDoc]; return theDoc; }
    
    // Create new Doc for this file, load it in, and pop it up.
    theDoc = [[TheHandler alloc] init];
    [theDoc setFileName:fileBuf];
    if(!theDoc || ![theDoc window] || ![theDoc load:self])
    {
	NXRunAlertPanel([stringTable valueForStringKey:"Open"],
	    [stringTable valueForStringKey:"Cannot read %s!"],
	    [stringTable valueForStringKey:"OK"], NULL, NULL, fileBuf);
	[theDoc free]; return nil;
    }
    [self setMainDoc:theDoc];
    [convertWindowToDoc
    	insertKey:(void *)[theDoc window] value:(void *)theDoc];
    [[theDoc window] setDocEdited:NO];
    return theDoc;
}

- open:sender
{
    return [self openForHandlerAt:(-1) name:NULL];
}

- open1:sender
{
    return [self openForHandlerAt:0 name:NULL];
}

- open2:sender
{
    return [self openForHandlerAt:1 name:NULL];
}

- open3:sender
{
    return [self openForHandlerAt:2 name:NULL];
}

- saveDoc:theDoc as:(BOOL)yn
{
    id otherDoc;
    const char *fileName;
    char fileBuf[MAXPATHLEN+1];
    
    // Get new file name if still untitled or "save as" is true.
    if(yn || ![theDoc fileName])
    {
	if(![self getSavePath:fileBuf
	    defaultFolder:[[theDoc class] defaultFolder]
	    defaultPath:[theDoc fileName] 
	    type:[[theDoc class] fileType]])
		return nil;
	// Make sure new file name is not already in use in this app.
	otherDoc = [[theDoc class] docForFileName:fileBuf];
	if(otherDoc && otherDoc!=theDoc)
	{
/*!!!
	    int choice;
	    
	    choice = NXRunAlertPanel([stringTable valueForStringKey:"Save"],
		[stringTable valueForStringKey:"You already have %s open in another window.\nDestroy other window?"],
		[stringTable valueForStringKey:"Yes"],
		[stringTable valueForStringKey:"Cancel"], NULL, fileBuf); 
	    if(choice != NX_ALERTDEFAULT) return nil;
!!!*/
	    [[otherDoc window] setDocEdited:NO];
	    [self closeDoc:otherDoc andFree:YES];
	}
	[theDoc setFileName:fileBuf];
    }
    if(!(fileName = [theDoc fileName])) return nil;
	
    // If backup option on, append "~" to the existing file.
    if([[theDoc class] backupOnSave] && access(fileName, F_OK)==0)
    {
	strcpy(fileBuf, fileName);
	strcat(fileBuf, "~");
	rename(fileName, fileBuf);
    }
    
    // Write to file and alert user on error.
    if(![theDoc dump:self])
    {
    	NXRunAlertPanel([stringTable valueForStringKey:"Save"],
	    [stringTable valueForStringKey:"Cannot write %s!"],
	    [stringTable valueForStringKey:"OK"], NULL, NULL, fileName);
	return nil;
    }
    
    [[theDoc window] setDocEdited:NO];
    return theDoc;
}

- save:sender
{
    return [self saveDoc:[self mainDoc] as:NO];
}

- saveAs:sender
{
    return [self saveDoc:[self mainDoc] as:YES];
}

- revertDocToSaved:theDoc
{
    const char *fileName;
    
    // If still untitled or unedited no reversion is possible.
    if([theDoc window] && ![[theDoc window] isDocEdited]) return nil;
    if(!(fileName = [theDoc fileName])) return nil;

    // Ask user if reversion is really what was meant.
    if(NXRunAlertPanel([stringTable valueForStringKey:"Revert"], 		
	[stringTable valueForStringKey:"Revert to saved version of %s?"],
	[stringTable valueForStringKey:"Revert"],
	[stringTable valueForStringKey:"Cancel"], NULL, fileName)
	!= NX_ALERTDEFAULT)
	    return nil;
	    
    // Re-read file from disk.
    if(![theDoc load:self])
    {
	NXRunAlertPanel([stringTable valueForStringKey:"Revert"],
	    [stringTable valueForStringKey:"Cannot read %s!"],
	    [stringTable valueForStringKey:"OK"], NULL, NULL, fileName);
	return nil;
    }
    [[theDoc window] setDocEdited:NO];
    return theDoc;
}
  
- revertToSaved:sender
{
    return [self revertDocToSaved:[self mainDoc]];
}

- closeDoc:theDoc andFree:(BOOL)yn
{
    int choice;
    const char *fileName;
    
    // Untitled files OK---user may still want to save them.
    fileName = [theDoc fileName];
    
    // Give user a chance to save if edited.
    if([[theDoc window] isDocEdited])
    {
	choice = NXRunAlertPanel([stringTable valueForStringKey:"Close"],
            [stringTable valueForStringKey:"%s has been modified.\nSave it?"],
	    [stringTable valueForStringKey:"Yes"],
	    [stringTable valueForStringKey:"No"],
	    [stringTable valueForStringKey:"Cancel"],
	    fileName ? fileName : [stringTable valueForStringKey:"This file"]); 
	switch (choice)
	{
	case NX_ALERTALTERNATE:
	    break;
	case NX_ALERTOTHER:
	    return nil;
	case NX_ALERTDEFAULT:
	default:
	    [self saveDoc:theDoc as:(fileName ? NO : YES)];
	    break;
	}
    }
    [[theDoc window] setDocEdited:NO];
    
    // Remove Doc from index.
    [convertWindowToDoc removeKey:(void *)[theDoc window]];

    // Note that doc closes its window upon freeing.
    if(yn) [[theDoc window] close];//!!!
    [theDoc setFileName:NULL];//!!!
    //!!!if(yn) [theDoc free];
    return self;
}

- close:sender
{
    return [self closeDoc:[self mainDoc] andFree:YES];
}

- appWillTerminate:sender
{
    const void *windowKey;
    void  *docValue;
    NXHashState state = [convertWindowToDoc initState];
    
    // Give user a chance to review unsaved documents and cancel the Quit.
    while([convertWindowToDoc nextState:&state key:&windowKey value:&docValue]) 
	if([(id)windowKey isDocEdited])
	{
	    [self setMainDoc:(id)docValue];
	    if(![self closeDoc:(id)docValue andFree:YES]) return nil;
	}
    return self;
}

@end
