#import "AppWithDoc.h"
#import "Document.h"

#import <objc/List.h>
#import <appkit/Matrix.h>
#import <appkit/Menu.h>
#import <appkit/MenuCell.h>
#import <appkit/OpenPanel.h>
#import <appkit/defaults.h>
#import <appkit/nextstd.h>
#import <objc/hashtable.h>	/* for NXCopyStringBuffer() */
#import <text/text.h>

#define VERSION_STRING "%.2f"

@implementation AppWithDoc:Application
/*
 * This class is used primarily to handle the opening of new documents and
 * other application-wide activity.  It listens for requests from the
 * Workspace Manager to open a Document file as well as target/action
 * messages from the New and Open... menu items. 
 */

/* Private C functions used to implement methods in this class. */

static id   initMenu(id menu)
/*
 * Returns the activate menu if is found in this menu. 
 */
{
    const char *s;
    static id   activateMenu;
    id          cell, cellTarget;
    id          matrix = [menu itemList];
    int         count = [matrix cellCount];
    while (count--) {
	cell = [matrix cellAt:count :0];
	cellTarget = [cell target];
	if ([cell hasSubmenu])
	    initMenu(cellTarget);
	s = [cell title];
	if (s && !strcmp(s, "Activate"))
	    activateMenu = cellTarget;
    }

    return activateMenu;
}

static id   documentInWindow(id window)
/*
 * Checks to see if the passed window's delegate is a Document. If it is, it
 * returns that document, otherwise it returns nil. 
 */
{
    id          document = [window delegate];
    return [document isKindOf:[Document class]] ? document : nil;
}

static id   findDocument(const char *name)
/*
 * Searches the window list looking for a Document with the specified name.
 * Returns the window containing the document if found. If name == NULL then
 * the first document found is returned. 
 */
{
    id          document, window;
    id          windowList = [NXApp windowList];
    int         count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	document = documentInWindow(window);
	if (document && !(name && strcmp([document filename], name)))
	    return window;
    }

    return nil;
}

static BOOL loadActivateMenu()
/*
 * Adds all open documents to the activate menu. Returns whether any were
 * actually loaded. 
 */
{
    BOOL        foundOne = NO;
    id          document, window;
    id          windowList = [NXApp windowList];
    int         count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	document = documentInWindow(window);
	if (document) {
	    [NXApp addDocument:[document filename]];
	    foundOne = YES;
	}
    }

    return foundOne;
}

static id   openFile(STR directory, id newClass, char* name, BOOL display)
/*
 * Opens a file with the given name in the specified directory. If we already
 * have that file open, it is ordered front. Returns the document if
 * successful, nil otherwise. 
 */
{
    id          window;
    char        path[MAXPATHLEN + 1];
    if (name && *name) {
	if (!directory)
	    directory = pwd();
	else if (*directory != '/')
	    directory = fullPath(pwd(), directory);
	if (isDirectory(directory)) {
	    strcpy(path, fullPath(directory, name));
	    window = findDocument(path);
	    if (window) {
		if (display)
		    [window makeKeyAndOrderFront:window];
		return[window delegate];
	    } else {
		return [newClass newFromFile:path];
	    }
	} else {
	    NXRunAlertPanel("Open", "Invalid path: %s",
			    "OK", NULL, NULL, directory);
	}
    }
    return nil;
}

/* Public methods */

+ initialize
/*
 * Initializes the defaults. 
 */
{
    const NXDefaultsVector AppDefaults = {
				      {"WinLocX", "50"}, {"WinLocY", "100"},
				     {"WinLocDx", "15"}, {"WinLocDy", "15"},
					  {"Quit", NULL}, {NULL, NULL}};
    NXRegisterDefaults(appName, AppDefaults);
    return self;
}

+ new
/*
 * Defaults to Document; 0, 'document' 
 */
{
    return[self newClass:[Document class] extension:"document" version:0];
}

+ newClass:newDoc extension:(STR)newExt version:(int)newVersion
{
    self = [super new];
    docClass = newDoc;
    extension = newExt;
    version = newVersion / 10.0;
    [[self class] setVersion:newVersion];
    return self;
}

/* General application status and information querying/modifying methods. */

- readFromFile:(const char *)filename
{
    NXTypedStream *tstream;
    if (! (tstream=NXOpenTypedStreamForFile(filename, NX_READONLY)) ) {
	NotifyArg("Read","Error opening file %s", filename);
	return nil;
    }
    self = NXReadObject(tstream);
    NXCloseTypedStream(tstream);
    return self;
}

- write:obj toFile:(const char *)filename
{
    NXTypedStream *tstream;
    if (! (tstream=NXOpenTypedStreamForFile(filename, NX_WRITEONLY)) ) {
	NotifyArg("Write", "Error opening file %s", filename);
	return nil;
    }
    NXWriteObject(tstream, obj);
    NXCloseTypedStream(tstream);
    return obj;
}

- locateWindow:window
{
    static NXCoord x, dx, y, dy;
    if (!x) {
	x = atof(NXGetDefaultValue(appName, "WinLocX"));
	dx = atof(NXGetDefaultValue(appName, "WinLocDx"));
	y = atof(NXGetDefaultValue(appName, "WinLocY"));
	dy = atof(NXGetDefaultValue(appName, "WinLocDy"));
    }
    [window moveTo:x :y];
    x += dx;
    y += dy;
    return window;
}

/* Application-wide shared info */

- currentDocument
{
    return documentInWindow(mainWindow);
}

- (STR)extension
{
    return extension;
}

- saveAsPanel
/*
 * Returns a regular SavePanel with extension of the required file type. 
 */
{
    id          savepanel = [SavePanel new];
    [savepanel setAccessoryView:nil];
    [savepanel setRequiredFileType:extension];
    return savepanel;
}

/* Target/Action methods */

- info:sender
/*
 * Brings up the information panel. 
 */
{
    char        buf[6];
    if (!infoPanel) {
	infoPanel =
	    [self loadNibSection:"InfoPanel.nib" owner:self withNames:YES];
	sprintf(buf, VERSION_STRING, version);
	[versionField setStringValue:buf];
    }
    [infoPanel orderFront:self];
    return self;
}

- new:sender
/*
 * Called by pressing New in the Window menu. 
 */
{
    [docClass new];
    return self;
}

- open:sender
/*
 * Called by pressing Open... in the Window menu. 
 */
{
    const char *directory;
    const char*  const* files;
    const char *const docType[] = {extension, NULL};
    id          openpanel = [[OpenPanel new] allowMultipleFiles:YES];
    directory = [[self currentDocument] directory];
    if (directory && (*directory == '/')) {
	[openpanel setDirectory:directory];
    }
    if ([openpanel runModalForTypes:docType]) {
	files = [openpanel filenames];
	directory = [openpanel directory];
	while (files && *files) {
	    openFile(directory, docClass, *files, YES);
	    files++;
	}
    }
    return self;
}

- print:sender
/*
 * Print the View
 */
{
    [[[self currentDocument] view] printPSCode:sender];
}

- terminate:sender
/*
 * Overridden to be sure all documents get an opportunity to be saved before
 * exiting the program. 
 */
{
    id          window, document;
    int         count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	document = [window delegate];
	if ([document respondsTo:@selector(windowWillClose:)]) {
	    if ([document windowWillClose:window])
		[window close];
	    else
		return self;
	}
    }

    return[super terminate:sender];
}

/*
 * Application object delegate methods. Since we don't have an application
 * delegate, messages that would normally be sent there are sent to the
 * Application object itself instead. 
 */

- appDidInit:sender
/*
 * Check for files to open specified on the command line. Initialize the
 * menus and get a handle on the activate menu. Add all opened documents to
 * the activate menu. If there are no open documents, then open a blank one. 
 */
{
    int         i;
    char        buffer[MAXPATHLEN + 1];
    char        type[MAXPATHLEN + 1];
    char       *directory, *name, *ext;
    if (NXArgc > 1) {
	for (i = 1; i < NXArgc; i++) {
	    strcpy(buffer, NXArgv[i]);
	    strcpy(type, ".");
	    strcat(type, extension);
	    ext = strrchr(buffer, '.');
	    if (!ext || strcmp(ext, type))
		strcat(buffer, type);
	    directory = parentname(buffer);
	    name = basename(buffer);
	    openFile(directory, docClass, name, YES);
	}
    }
    activateMenu = initMenu([NXApp mainMenu]);
    if (!loadActivateMenu())
	[self new:self];	/* if none opened, open one */

    if (NXGetDefaultValue(appName, "Quit")) {
	[self activateSelf:YES];
	NXPing();
	[self terminate:self];
    }
    return self;
}

- (int)appOpenFile:(char* )path type:(char* )type
/*
 * This method is performed whenever a user double-clicks on an icon in the
 * Workspace Manager representing the document. 
 */
{
    char       *name;
    char       *directory;
    if (type && !strcmp(type, extension)) {
	directory = parentname(path);
	name = basename(path);
	return (openFile(directory, docClass, name, YES) ? YES : NO);
    }
    return NO;
}

- (BOOL) appAcceptsAnotherFile:sender
/*
 * We accept any number of appOpenFile:type: messages. 
 */
{
    return YES;
}

/* Listener/Speaker remote methods */

- (int)msgVersion:(char* *)aString ok:(int *)flag
{
    char        buf[6];
    sprintf(buf, VERSION_STRING, version);
    *aString = NXCopyStringBuffer(buf);
    *flag = YES;

    return 0;
}

- (int)msgDirectory:(const char **)totalPath ok:(int *)flag
{
    *totalPath = [[self currentDocument] directory];
    if (!*totalPath)
	*totalPath = [[OpenPanel new] directory];
    if (*totalPath) {
	*flag = YES;
    } else {
	*totalPath = "";
	*flag = NO;
    }
    return 0;
}

- (int)msgFile:(const char **)totalPath ok:(int *)flag
{
    const char *file;
    file = [[self currentDocument] filename];
    if (file) {
	*totalPath = file;
	*flag = YES;
    } else {
	*totalPath = "";
	*flag = NO;
    }

    return 0;
}

- (int)msgPrint:(char* )totalPath ok:(int *)flag
{
    BOOL        close;
    id          document = nil;
    char       *directory, *name;
    char       *path;
    directory = parentname(totalPath);
    name = basename(totalPath);

    if (isDirectory(directory)) {
	path = NXCopyStringBuffer(fullPath(directory, name));
	document = [findDocument(path) delegate];
    }
    if (document) {
	close = NO;
    } else {
	document = openFile(directory, docClass, name, NO);
	close = YES;
    }
    if (document && [document contents]) {
	[document print:self];
	if (close)
	    [[document window] close];
	*flag = YES;
    } else {
	*flag = NO;
    }

    return 0;
}

/* Activate menu updating */

static int  cellcmp(const void *first, const void *second)
{
    const char *s1 = [*((id *) first) title];
    const char *s2 = [*((id *) second) title];
    return (s1 && s2) ? strcmp(s1, s2) : 0;
}

#define CellSortIf(COND)	if (COND) {\
	qsort(NX_ADDRESS(cells), [cells count], sizeof(id *), cellcmp);\
	[activateMenu sizeToFit];    }

- addDocument:(char *)name
/*
 * Adds the name passed in to the Activate menu (if it isn't already there). 
 */
{
    int         i;
    const char *s;
    id          matrix = [activateMenu itemList];
    id          cells = [matrix cellList];
    if (!name)
	return self;

    for (i = [cells count] - 1; i >= 0; i--) {
	s = [[cells objectAt:i] title];
	if (s && !strcmp(s, name))
	    return self;
    }

    [activateMenu addItem:name action:@selector(activateDocument:)
     keyEquivalent :0];

    CellSortIf(cells);
    return self;
}

- removeDocument:(char *)name
/*
 * Removes the passed name from the Activate menu. 
 */
{
    int         i;
    const char *s;
    id          matrix = [activateMenu itemList];
    id          cells = [matrix cellList];
    int         count = [cells count];
    if (!name)
	return self;

    for (i = 0; i < count; i++) {
	s = [[cells objectAt:i] title];
	if (s && !strcmp(s, name)) {
	    [matrix removeRowAt:i andFree:YES];
	    break;
	}
    }

    CellSortIf(i != count);
    return self;
}

- renameDocument:(char *)oldName to:(char *)newName
/*
 * Finds the passed oldName in the Activate menu and changes the title of
 * that menu entry to newName. 
 */
{
    int         i;
    const char *s;
    id          matrix = [activateMenu itemList];
    id          cell, cells = [matrix cellList];
    int         count = [cells count];
    if (!oldName)
	return self;

    for (i = 0; i < count; i++) {
	cell = [cells objectAt:i];
	s = [cell title];
	if (s && !strcmp(s, oldName)) {
	    [cell setTitle:newName];
	    break;
	}
    }

    CellSortIf(i != count);
    return self;
}

- activateDocument:sender
/*
 * Sent by items in the Activate menu.  Queries back to the sender to find
 * out which document to activate, then activates it. 
 */
{
    [findDocument([[sender selectedCell] title]) makeKeyAndOrderFront :self];
    return self;
}

- setVersionField:anObject
{
    versionField = anObject;
    return self;
}

@end
