// MOController.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.

// ABOUT MOKit
// 
// MOKit is a collection of useful and general objects.  Permission is 
// granted by the author to use MOKit in your own programs in any way 
// you see fit.  All other rights to the kit are reserved by the author 
// including the right to sell these objects as part of a LIBRARY or as 
// SOURCE CODE.  In plain English, I wish to retain rights to these 
// objects as objects, but allow the use of the objects as pieces in a 
// fully functional program.  NO WARRANTY is expressed or implied.  The author 
// will under no circumstances be held responsible for ANY consequences to 
// you from the use of these objects.  Since you don't have to pay for 
// them, and full source is provided, I think this is perfectly fair.

#import "MOKit/MOController.h"
#import "MOKit/MOString.h"
#import "MOKit/MOClassVariable.h"
#import <objc/objc-runtime.h>

#define CLASS_VERSION						0
#define CLASS_NAME							"MOController"

#define BUNDLE_TYPE							"bundle"

#define MOSTRING_CLASS_NAME					"MOString"
#define MOCLASSVARIABLE_CLASS_NAME			"MOClassVariable"

@interface MOController(Private)

+ (Class)MO_loadClassBundle:(const char *)className;

@end

@implementation MOController

// True class variable object
static MOClassVariable *MO_classNib;

// Looked up classes we use
static Class MOStringClass;
static Class MOClassVariableClass;

+ (Class)MO_loadClassBundle:(const char *)className
// Finds the bundle of the same name as the class, grabs it and loads the
// class from it and returns the named class.
{
	char pathBuff[MAXPATHLEN+1];
	id classBundle = nil;
	Class class = nil;
	
	// Load the bundle
	if ((class = objc_lookUpClass(className)) == nil)  {
		// class is not already loaded... load it.
		
		// Look for the bundle in the main bundle first, 
		// else try in this class's bundle.
		if (![[NXBundle mainBundle] getPath:pathBuff forResource:className 
					ofType:BUNDLE_TYPE])  {
			if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff 
						forResource:className ofType:BUNDLE_TYPE])  {
				NXLogError("[%s loadClassBundle] failed to "
						"find %s class bundle.", [self name], className);
				return nil;
			}
		}
		classBundle = [[NXBundle allocFromZone:[self zone]] 
					initForDirectory:pathBuff];
		if (!classBundle)  {
			NXLogError("[%s loadClassBundle] failed to "
						"create bundle for class %s.", [self name], className);
			return nil;
		}
		if ((class = [classBundle classNamed:className]) == nil)  {
			NXLogError("[%s loadClassBundle] failed to "
						"load %s class from bundle.", [self name], className);
			return nil;
		}
	}
	
	return class;
}

+ initialize
// Set the version.  Load classes, and init class variables.
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];

		// Load the string class if necessary
		MOStringClass = [self MO_loadClassBundle:MOSTRING_CLASS_NAME];
		MOClassVariableClass = [self MO_loadClassBundle:
					MOCLASSVARIABLE_CLASS_NAME];
		
		// Set up class variables
		MO_classNib = [[MOClassVariableClass allocFromZone:[self zone]] 
					initDoesFreeValues:YES];
	}
	return self;
}

+ setClassNib:(const char *)nibName
// Set the nib file which contains the window this controller class controls.
{
	id newVal;
	
	if (nibName)  {
		newVal = [[MOStringClass allocFromZone:[self zone]] 
				initStringValue:nibName];
	}  else  {
		newVal = nil;
	}
	[MO_classNib setObject:newVal forClass:self];
	return self;
}

+ (const char *)classNib
// Returns the nib file name for this controller class.
{
	return [(MOString *)[MO_classNib getObjectForClass:self] stringValue];
}

+ startUnloading
// Free our class variables.
{
	[MO_classNib free];
	return self;
}

- init
// Just calls the DI.
{
	return [self initWithFrameName:NULL];
}

- initWithFrameName:(const char *)theFrameName
// Designated Initializer.
// Initialize some instance variables.
{
	[super init];
	nibIsLoaded=NO;
	window=nil;
	frameName = [[MOStringClass allocFromZone:[self zone]] 
				initStringValue:theFrameName];
	return self;
}

- free
// Free our window, and frameName.
{
	if (window)  [window free];
	[frameName free];
	return [super free];
}

- setFrameName:(const char *)theFrameName
// Sets the name to save the windows frame info under in the defaults 
// database.
{
	[frameName setStringValue:theFrameName];
	return self;
}

- (const char *)frameName
// Return the name to save the windows frame info under in the defaults 
// database.
{
	return [frameName stringValue];
}

- nibDidLoad
// This is called after our nib has been loaded.  You may override it to do
// whatever needs to be done.  But call super's implementation first.
{
	return self;
}

- loadNibIfNeeded
// Loads the nib file if it hasn't already been loaded.  Also performs
// set up that must be deferred till the nib is loaded.
{
	const char *nibName = [[self class] classNib];
	const char *frmName = NULL;
	if ((!nibIsLoaded) && (nibName) && (*nibName))  {
		if (![NXApp loadNibSection:nibName owner:self])  {
			NXLogError("[%s loadNibIfNeeded]: failed to load nib file %s.", 
						[[self class] name], nibName);
			return nil;
		}
		nibIsLoaded = YES;
		
		// configure window
		frmName = [self frameName];
		if ((frmName) && (*frmName))  {
			[window setFrameAutosaveName:frmName];
			[window setFrameUsingName:frmName];
		}
		
		[self nibDidLoad];
		
	}
	return self;
}

- window
// Return the window.  (Returns nil if the nib file is not loaded.)
{
	return [self window:NO];
}

- window:(BOOL)loadFlag
// Return the window.  (If loadFlag is NO and window is not loaded, 
// returns nil.  If loadFlag is YES, window is loaded if necessary.)
{
	if (loadFlag)  [self loadNibIfNeeded];
	return window;
}

- showWindow:sender
// Show the window (loading it first if necessary).
{
	[self loadNibIfNeeded];
	if (window)  [window makeKeyAndOrderFront:sender];
	
    return self;
}

- awake
// Sets up ivars we don't archive.
{
	window = nil;
	nibIsLoaded = NO;
	return self;
}

- read:(NXTypedStream *)strm
// This method is probably not useful, but here it is for completeness sake.
{
	int classVersion;

	[super read:strm];
	
	classVersion = NXTypedStreamClassVersion(strm, CLASS_NAME);
	
	switch (classVersion)  {
		case 0:		// First version.
			frameName = NXReadObject(strm);
			break;
		default:
			NXLogError("[%s read:] class version %d cannot read "
						"instances archived with version %d", 
						CLASS_NAME, CLASS_VERSION, classVersion);
			frameName = [[MOStringClass allocFromZone:[self zone]] init];
			break;
	}
	return self;
}

- write:(NXTypedStream *)strm
// This method is probably not useful, but here it is for completeness sake.
{
	[super write:strm];
	NXWriteObject(strm, frameName);
	return self;
}

@end

