//  ThinkMore.m
//
//  Contains additional methods used in the Thinker class.
//
//  You may freely copy, distribute, and reuse the code in this example.
//  NeXT disclaims any warranty of any kind, expressed or  implied, as to its
//  fitness for any particular use.

#import "Thinker.h"
#import "SpaceView.h"
#import "BackView.h"
#import "SleepView.h"
#import "BlackView.h"
#import "Password.h"
#import "Controller.h"
#import "psfuncts.h"


#import <appkit/appkit.h>
#import <objc/NXBundle.h>


#define VIEWDIRECTORY	"/LocalLibrary/BackSpaceViews"

static char *compiledViewNames[] = {
	"Space",
	"Boink",
	"Black",
	};

#define COMVIEWCOUNT	(sizeof(compiledViewNames)/sizeof(*compiledViewNames))

@implementation Thinker(thinkMore)


//must invoke this before creating window or setting up the views
- getViewType
{
	int i;
	id theMatrix;
	char buf[MAXPATHLEN];
	ModuleInfo *m;

	strcpy( buf, NXHomeDirectory());
	strcat( buf, "/Library/BackSpaceViews");

	moduleList = [[ModuleList alloc] init];

	[self loadViewsFrom:buf];
	[[NXApp delegate] setProgressViewRatio:0.2];
	[self loadViewsFrom: [self appDirectory]];
	[[NXApp delegate] setProgressViewRatio:0.4];
	[self loadViewsFrom: VIEWDIRECTORY];
	[[NXApp delegate] setProgressViewRatio:0.6];

	for (i = 0; i < COMVIEWCOUNT; i++)
	{
		m = [[ModuleInfo alloc] 
			initWithView:nil name:compiledViewNames[i] path:NULL];
	    [moduleList addObject: m];
	}
	[[NXApp delegate] setProgressViewRatio:0.8];

	[moduleList sort];
	[viewSelectionBrowser loadColumnZero];
	theMatrix = [viewSelectionBrowser matrixInColumn:0];
	[theMatrix selectCellAt:realViewIndex :0];
	[theMatrix scrollCellToVisible:realViewIndex :0];
	[[NXApp delegate] setProgressViewRatio:0.9];

	return self;
}


- selectRealViewIndex:sender
{
	//sender is the NXBrowser
	int index = [[viewSelectionBrowser matrixInColumn:0] selectedRow];

	if (index == realViewIndex) return self;
	if (index == -1)
	{
		id theMatrix = [viewSelectionBrowser matrixInColumn:0];
		[theMatrix selectCellAt:realViewIndex :0];
		[theMatrix scrollCellToVisible:realViewIndex :0];
		return self;
	}
	
	realViewIndex = index;
	[self setVirtualViewIndexAndIncrement:NO];
	
	return self;
}


// this method is the actual view setting mechanism,
// guaranteed to get called to set the view

- setVirtualViewIndexAndIncrement:(BOOL)flag
{
	id myView;
	id newInspector;
	
	if (realViewIndex)
	{
		virtualViewIndex = realViewIndex-1;
		[self selectScreenSaverViews];
	}

	else
	{
		if (flag)
		{
			virtualViewIndex = random() % [moduleList count];
			[self selectScreenSaverViews];
			myView = [self backView];
			
			while ([myView respondsTo:@selector(isBoringScreenSaver)]
				&& [myView isBoringScreenSaver])
			{
				if (++virtualViewIndex >= [moduleList count])
					virtualViewIndex = 0;

				[self selectScreenSaverViews];
				myView = [self backView];
			}
		}
		else [self selectScreenSaverViews];
	}


	//---------------------------------------------
	// now plug in the inspector
	//---------------------------------------------
	myView = [self backView];

	if ([myView respondsTo:@selector(inspector:)])
	{
		newInspector = [myView inspector:self];
		if (!newInspector) newInspector = [self nullInspector];
	}
	else newInspector = [self nullInspector];
	
	if (newInspector != currentInspector)
	{
		if ([oldInspectorOwner respondsTo:@selector(inspectorWillBeRemoved)])
			[oldInspectorOwner inspectorWillBeRemoved];
		// either myView will be the inspector owner, or it won't care
		oldInspectorOwner = myView;
		
		// don't want it to resize the box.  Suboptimal technique...
		[newInspector setFrame:&inspectorFrame];

		[invisibleInspectorBox setContentView: newInspector];
		currentInspector = newInspector;

		if ([myView respondsTo:@selector(inspectorInstalled)])
			[myView inspectorInstalled];

		[invisibleInspectorBox display];
	}

	return self;
}


- selectScreenSaverViews
{
	id theView, bigWindow;
	int myBacking;

	//need to order out big window if that's the type and buffering changed
	
	theView = [self backView];

	myBacking = [self backingTypeForView:theView];

	[self createBigWindowIfNecessaryForView:theView];

	if (myBacking == NX_BUFFERED) bigWindow = bigBufferedWindow;
	else bigWindow = bigUnbufferedWindow;

	if (windowType == BACKWINDOW)
	{
		if (spaceWindow != bigWindow)
		{
			[spaceWindow orderOut:self];
			[self useBackWindow:globalTier];
		}
	}

	spaceView = theView;
	[self installSpaceViewIntoWindow:spaceWindow];

	if ([spaceView respondsTo:@selector(setImage:)])
		[spaceView setImage: image];
	if ([spaceView respondsTo:@selector(newWindow)]) [spaceView newWindow];

	[self setWindowTitle];

	NXWriteDefault("BackSpace", "viewType", (realViewIndex ? 
			([moduleList nameAt: realViewIndex-1]) : "All"));


	if (windowType) 
	{
		// the unbuffered window looks better if you just display
		// its contents, but for a buffered oneshot window, you must
		// display the window to make sure the window server window exists.

		if (myBacking == NX_BUFFERED) 
			[spaceWindow display];
		else 
		{
			[spaceView fillBoundsWithBlack];
			[spaceView display];
		}
	}

	if (normalWindow && (windowType == NORMALWINDOW))
	{
		if (myBacking == NX_BUFFERED)
			[normalWindow setBackingType:NX_BUFFERED];
		else[normalWindow setBackingType:NX_RETAINED];
	}

	return self;
}

- setWindowTitle
{
	if ([spaceView respondsTo:@selector(windowTitle)])
	{
		[normalWindow setTitle: NXLocalString([spaceView windowTitle],0,0)];
	}
	else [normalWindow setTitle: NXLocalString("BackSpace",0,0)];
	return self;
}


- getScreenLockerSetting
{
	const char *ptr;
	
	[screenLocker setState:0];
	
	ptr = NXGetDefaultValue("BackSpace", "screenLocker");

	if (!ptr || !strcmp(ptr,"Off")) [self setScreenLocker:NO andRemember:NO];
	else [self setScreenLocker:YES andRemember:NO];

	return self;
}

- changeScreenLockerSetting:sender
{
	if (![password checkPassword:
		NXLocalString("Enter password to change screen lock setting:",0,0)
		randomPos:NO checkLock:NO withView:nil])
	{
		[screenLocker setState:[password isLocked]];
		return self;
	}

	if (![password validPassword] && ![password setPassword:self])
	{
		[screenLocker setState:[password isLocked]];
		return self;
	}
	
	[self setScreenLocker:([screenLocker state])andRemember:YES];
	return self;
}

- setScreenLocker:(BOOL)val andRemember:(BOOL)rem
{
	[screenLocker setState:val];
	[password setLock: val];

	if (rem)
	{	
		if (val) NXWriteDefault("BackSpace", "screenLocker", "On");
		else NXRemoveDefault("BackSpace", "screenLocker");
	}
	
	return self;
}




//---------------------------------------------------
//			View manager routines	
//---------------------------------------------------

- backView
{
	NXRect aFrame = {{0,0},{200,200}};
	id theView;
	
	if (![moduleList viewAt:virtualViewIndex])
	{
		char path[MAXPATHLEN];
		id myClass;
		const char *name;
		char *filenames[] = {path, NULL};
		ModuleInfo *mp;
		struct mach_header *header;

		mp = [moduleList objectAt: virtualViewIndex];
		name = [mp viewName];

		// before I loaded all classes at launch time; now classes are
		// loaded only as needed.  This idea and some of the code here is
		// from bill bumgarner, thanx!

		if ([mp path])	// we have path but no instance, must load class
		{
			long ret;
			do
			{
				sprintf(path, "%s/%sView.BackO", [mp path], name);
				ret = objc_loadModules(filenames, NULL, NULL, &header, NULL);

				// objc_loadModules succeeds with a warning if the architecture of the
				// object file is wrong, so we better check if we really got a class

				if (!ret)	// load succeeded or was wrong architecture
				{
					sprintf(path,"%sView", name);
					myClass = objc_getClass(path);
					if (!myClass) ret = -1;
				}

			} while (ret && [mp useNextPath]);

			[mp discardAltPaths];

			if (ret)
			{
				// Ugh, failed.  Will instantiate a BlackView instead...
				NXRunAlertPanel([NXApp appName], NXLocalString(
					"Could not dynamically load class: %sView",0,0),
					NULL, NULL, NULL, name);
				name = "Black";
			}
			else
			{
				[mp setHeader:header];
			}
		}

		//at this point we must have a valid name for a loaded class
		
		sprintf(path,"%sView", name);
		myClass = objc_getClass(path);

		theView = [[myClass allocFromZone:backZone] initFrame:&aFrame];
		[[moduleList objectAt:virtualViewIndex] setView:theView];
	}

	theView = [moduleList viewAt:virtualViewIndex];
	
	return theView;
}

- showSettingsPanel:sender
{
	[self getScreenSaverSetting];
	[self getScreenLockerSetting];
	[self getPrioritySetting];
	[self getImageFile];
	[self getHotCornerSetting];
	[settingsPanel display];
	[settingsPanel makeKeyAndOrderFront:self];
	windowHasBeenDisplayed = YES;
	return self;
}

- showInfoPanel:sender
{
	if (!infoPanel)
	{
		if (![NXApp loadNibSection:"Info.nib" owner:self withNames:NO fromZone:[self zone]])
			NXLogError ("Can't find Info.nib!");	
	}
	[infoPanel makeKeyAndOrderFront:sender];
	return self;
}

#define SLEEPSIZE (3.0)

- createSleepWindow
{
	if (!sleepWindow)
	{
		NXRect sleep={{0, 0},{SLEEPSIZE, SLEEPSIZE}};
		id aView = [[SleepView alloc] initFrame:&sleep];

		sleepWindow = [[Window alloc]
			initContent:&sleep style:NX_TOKENSTYLE
			backing:NX_NONRETAINED buttonMask:0 defer:NO];

		[sleepWindow setEventMask:(NX_MOUSEENTEREDMASK | NX_MOUSEEXITEDMASK)];
		PSsetwindowlevel(SLEEPTIER, [sleepWindow windowNum]);
		PSWmakeWindowGray([sleepWindow windowNum]);
		[[sleepWindow setContentView: aView] free];

		[sleepWindow setTrackingRect:&sleep inside:YES owner:aView
			tag:3 left:NO right:NO];
	}

	return self;
}

- setSleepCorner:(int)val
{
	NXRect screen={{0, 0}};
	NXCoord x = 0.0, y = 0.0;

	if (val)
	{
		[NXApp getScreenSize:&(screen.size)];
		[self createSleepWindow];
		if (val == 2 || val == 3) x = screen.size.width - SLEEPSIZE;
		if (val == 3 || val == 4) y = screen.size.height - SLEEPSIZE;
		[sleepWindow moveTo:x :y];
		[sleepWindow orderFront:self];
	}
	else [sleepWindow orderOut:self];
	return self;
}

- getHotCornerSetting
{
	const char *ptr;
	int tval, val=0;
	
	ptr = NXGetDefaultValue("BackSpace", "hotCorner");
	if (ptr)
	{
		sscanf(ptr,"%d",&tval);
		if (tval >= 0 && tval <= 4) val = tval;
	}
	
	[cornerView setState:val];
	[self setSleepCorner:val];

	return self;
}


@end
