#import "Thinker.h"
#import "SpaceView.h"
#import "BezierViewPart.h"
#import "BackView.h"
#import "BlackView.h"
#import "Password.h"
#import "psfuncts.h"
#import "Localization.h"
#import <appkit/NXColorWell.h>
#import <appkit/Matrix.h>
#import <appkit/graphics.h>
#import <appkit/color.h>
#import <appkit/defaults.h>
#import <appkit/PopUpList.h>
#import <appkit/MenuCell.h>
#import <dpsclient/wraps.h>
#import <objc/objc-runtime.h>
#import <nextdev/evsio.h>
#import <libc.h>
#import <objc/List.h>

#import <streams/streams.h>
#import <objc/objc-load.h>
#import "StringStorage.h"
#import <sys/types.h>

#import <stdlib.h>				/* for getenv() */
#import <strings.h>				/* for index() */
#import <sys/file.h>			/* for access() */
#import <sys/param.h>			/* for MAXPATHLEN */
#import <sys/dir.h>				/* for opendir(), etc. */

#define VIEWDIRECTORY	"/LocalLibrary/BackSpaceViews"

StringStorage *viewNames;

static char *compiledViewNames[] = {
	"Space",
	"Boink",
	"BoinkSpace",
	"Bezier",
	"Black",
	"FadingImage",
	"SlidingImage",
	"Sperm",
	};

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

@implementation Thinker(thinkMore)

- getBackgroundColor
{
	float r, g, b;
	const char *ptr;
	NXColor theColor;

	ptr = NXGetDefaultValue([NXApp appName], "backgroundColor");
	if (!ptr)
	{
		theColor = NXConvertGrayToColor(NX_DKGRAY);
		[myColorWell setColor:theColor];
		return self;
	}

	sscanf(ptr,"%f %f %f",&r, &g, &b);
	if (r<0. || g<0. || b<0. || r>1. || g>1. || b>1.) return self;
	
	theColor = NXConvertRGBAToColor(r,g,b,1);
	[myColorWell setColor:theColor];
	PSWorkspaceColor(r,g,b);

	return self;
}

- setBackgroundColor:sender;
{
	float r,g,b;
	char str[120];
	NXColor theColor = [sender color];
	
	r = NXRedComponent(theColor); 
	g = NXGreenComponent(theColor); 
	b = NXBlueComponent(theColor); 
	PSWorkspaceColor(r,g,b);
	sprintf(str,"%7.5f %7.5f %7.5f ", r,g,b);
	NXWriteDefault([NXApp appName], "backgroundColor", str);
	return self;
}

/**
 **	which-bin -- find the path for the named exectuable files
 **
 **	Lennart <Lovstrand@EuroPARC.Xerox.COM>, September 1, 1991.
 ** slightly modified by sam 910902
 **/

char * which_bin(const char *file)
{
    DIR *dir;
    struct direct *de;
    static char buf[MAXPATHLEN];
    char *paths, *p, *q;
    int filelen, dirlen;

    paths = getenv("PATH");
    if (paths == NULL)
	return NULL;

    filelen = strlen(file);
    for (p = q = paths; *q != '\0'; p = q + 1)
	{
		q = index(p, ':');
		if (q == NULL) q = p + strlen(p);

		dirlen = q - p;
		strncpy(buf, p, dirlen);
		buf[dirlen] = '\0';

		dir = opendir(buf);
		if (dir == NULL) continue;

		buf[dirlen] = '/';
		dirlen++;

		while ((de = readdir(dir)) != NULL)
		{
			if (de->d_namlen != filelen || strcmp(de->d_name, file) != 0)
				continue;

			strcpy(buf + dirlen, file);
			if (access(buf, X_OK) != 0) continue;

			closedir(dir);
			return buf;
		}

		closedir(dir);
    }
	return NULL;
}

void getAppDirectory (char *appDirectory)
{
    char *suffix, *path;

    strcpy (appDirectory,NXArgv[0]);

    suffix = rindex(appDirectory,'/');
    if (suffix != NULL)			// explicit path
	{ 
		*suffix  = '\0'; 		// remove executable name
    }
	else
	{
		path = which_bin([NXApp appName]);
		if (path != NULL)
			strcpy(appDirectory, path);
		else
			// Don't know where we came from -- arbitrarily
			// presume "." and continue (it's better than breaking)
			strcpy(appDirectory, ".");
    }  
}

- getViewType
{
	//must invoke this before creating window or setting up the views
	
	int i;
	const char *ptr;
    char launchdir[256];
	
	viewNames = [[StringStorage alloc] init];

	getAppDirectory (&launchdir[0]);
	[self loadViewsFrom: &launchdir[0]];
	[self loadViewsFrom: VIEWDIRECTORY];

	for (i = 0; i < COMVIEWCOUNT; i++)
	    [viewNames addElement: compiledViewNames[i]];

	[viewNames sort];

	for (i = 0; i < [viewNames count]; i++)
	    // could use addItem:, note that it adds items always at end
	    [[viewSelectionButton target] insertItem: 
			LocalString([viewNames elementAt: i], 0, 0)
			at: (i+1)];

	[[viewSelectionButton target] setTarget:self];
	[[viewSelectionButton target] setAction:@selector(selectRealViewIndex:)];

	realViewIndex = 0;

	ptr = NXGetDefaultValue([NXApp appName], "viewType");
	if (ptr)
	{
	    for (i = 0; i < [viewNames count]; i++)
		if (strcmp(ptr, [viewNames elementAt: i]) == 0)
		{
		    realViewIndex = i+1;
		    break;
		}
	}
	
	[viewSelectionButton setTitle: (realViewIndex ? 
			LocalString([viewNames elementAt: realViewIndex-1], 0, 0) : 
			LocalString("All", 0, 0))];

	return self;
}


- selectRealViewIndex:sender
{
	//sender is the PopUpList's matrix
	int index = [sender selectedRow];

	if (index == realViewIndex) return self;
	realViewIndex = index;
	[self setVirtualViewIndexAndIncrement:NO];
	
	return self;
}

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

	else
	{
		if (flag)
		{	do
			{
/*				if (++virtualViewIndex >= [viewNames count])
					virtualViewIndex = 0; */
/* NEW */			virtualViewIndex = random()%[viewNames count];

				[self selectScreenSaverViews];
				myView = [self bigView];

			} while ([myView respondsTo:@selector(isBoringScreenSaver)]
				&& [myView isBoringScreenSaver]);
		}
		else [self selectScreenSaverViews];
	}
	
	
	return self;
}


- selectScreenSaverViews
{
	id tnormalView;
	id bigWindow, tbigView;
	int myBacking;

	//need to order out big window if that's the type and buffering changed
	
	tnormalView = [self normalView];
	tbigView = [self bigView];

	myBacking = [self backingTypeForView:tnormalView];

	[self createBigWindowIfNecessaryForView:tbigView];

	if (myBacking == NX_BUFFERED) bigWindow = bigBufferedWindow;
	else bigWindow = bigUnbufferedWindow;
		
	[bigWindow setContentView:tbigView];

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

	[normalWindow setContentView:tnormalView];


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

	[self setWindowTitle];

	NXWriteDefault([NXApp appName], "viewType", (realViewIndex ? 
			([viewNames elementAt: 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)
			convertToBuffered([normalWindow windowNum]);
		else convertToRetained([normalWindow windowNum]);
	}

	return self;
}

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


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

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

- changeScreenLockerSetting:sender
{
	if (![password checkPassword:
		LocalString("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([NXApp appName], "screenLocker", "On");
		else NXRemoveDefault([NXApp appName], "screenLocker");
	}
	
	return self;
}




//---------------------------------------------------
//			View manager routines	
// This code uses List in kind of a gross way,
// prefilling the List with self as a null entry
// to indicate that instances of the View classes
// haven't been created.
// By the way, List and Storage both refuse to accept
// nil as entry's - List won't store it and Storage
// attempts to dereference it...
//---------------------------------------------------

- createViewLists
{
	int i;
	
	normalViewList = [[List allocFromZone:[self zone]] init];
	bigViewList = [[List allocFromZone:[self zone]] init];

	for (i=0; i< [viewNames count]; i++)
	{
		//this is ugly! Prime the Lists.  wish I could store nil.
		[normalViewList insertObject:self at:i];
		[bigViewList insertObject:self at:i];
	}
	
	return self;
}


- normalView
{
	return [self accessViewList:normalViewList];
}

- bigView
{
	return [self accessViewList:bigViewList];
}


- accessViewList: (id) whichList
{
	id theView;
	
	if ([whichList objectAt:virtualViewIndex] == self)
	{
		id myClass;
		char str[50];
		const char *ptr;
		
		ptr = [viewNames elementAt: virtualViewIndex];
		sprintf(str,"%sView", ptr);
		myClass = objc_getClass(str);

		theView = [[myClass allocFromZone:backZone] init];
		if (theView) [whichList replaceObjectAt:(virtualViewIndex) with:theView];
	}

	theView = [whichList objectAt:(virtualViewIndex)];
	if (theView == self) theView = nil;
	
	return theView;
}

//
//  Dynamically load all object files found in the specified directory
//
// MEMO: Must make sure this doesn't screw up when it finds a new
// object file with a class that's already loaded!!!!
- loadViewsFrom: (const char *) dirname
{
    DIR *dir;
    struct direct *de;
    NXStream *nxstderr;
    char path[MAXPATHLEN];
    char *filenames[] = {path, NULL};

    nxstderr = NXOpenFile(2, NX_WRITEONLY);

    dir = opendir(dirname);
    if (dir == NULL) {
		//perror(dirname);
		return self;
    }

    while ((de = readdir(dir)) != NULL)
	{
		int i, numstrings;
		BOOL ok;
	
		// Ignore '.'-files (not really necessary, I guess)
		if (de->d_name[0] == '.')
			continue;

		// Check that the file has the right extension
		if (de->d_namlen < 3 ||
				strcmp(&de->d_name[de->d_namlen-2], ".o") != 0)
			continue;
			
		// refuse to load if the name matches a module already loaded
		numstrings = [viewNames count];
		ok = YES;
		strcpy(path, de->d_name);
		*(rindex(path, 'V')) = '\0';

		for (i=0; i< numstrings; i++)
		{
			if (!strcmp(path, [viewNames elementAt:i]))
			{	ok = NO;
				break;
			}
		}
		if (!ok) continue;
		

		// OK, all is well -- go load the little bugger
		sprintf(path, "%s/%s", dirname, de->d_name);
		if (objc_loadModules(filenames, nxstderr, NULL, NULL, NULL) == 1)
			// Ugh, failed.  Continue as usual.
			continue;

		// Smash out the 'V' in ".../FooView.o"
		*(rindex(path, 'V')) = '\0';
		[viewNames addElement: rindex(path, '/') + 1];
    }

    closedir(dir);

    NXClose(nxstderr);

    return self;
}

@end
