/**********************************************************************
  ABAppInfo.m

  Copyright (c) 1998, David C. Lambert.  All Rights Reserved.
**********************************************************************/
#import <time.h>
#import "table.h"
#import "ABWraps.h"
#import "ABAppInfo.h"

static id			processTable;
static id			pidUidTable;
static id			pidMenuTable;
static id			pidIconTable;
static id			pidMinisTable;
static id			pidCtxtTable;
static id			ctxtPidTable;
static NXSize		screenSize;
static DPSContext	myContext;

static int
safeGetCtxtIconPidAndMenu(int context, int *icon, int *pid, int *menu)
{
	int		status = 0;

	if (context < 0) return -1;

	DPSTraceContext(myContext, NO);
	NX_DURING
		PSWGetCtxtIconPidAndMenu(context, icon, pid, menu);
	    NXPing();
	NX_HANDLER
		NXReportError(&NXLocalHandler);
		switch (NXLocalHandler.code) {
		  case dps_err_ps:
			status = -1;
			break;
		  default:
			NX_RERAISE();
		}
	NX_ENDHANDLER
	if (*menu == 100000) *menu = -1;
	return status;
}

static int
safeGetWindowList(int *windows)
{
	int		count;

	if (!windows) return -1;

	NX_DURING
		PScountwindowlist(0, &count);
		PSwindowlist(0, count, windows);
        NXPing();
	NX_HANDLER
		NXReportError(&NXLocalHandler);
   		switch (NXLocalHandler.code) {
		  case dps_err_ps:
			PScountwindowlist(0, &count);
			break;
		  default:
			NX_RERAISE();
   		}
	NX_ENDHANDLER
    return count;
}

static int
safeGetCurrrentOwner(int windowNum, int *owner)
{
	int		status = 0;

	if (windowNum <= 0) return -1;

	NX_DURING
		PSWGetCurrentOwner(windowNum, owner);
        NXPing();
	NX_HANDLER
		NXReportError(&NXLocalHandler);
		switch (NXLocalHandler.code) {
		  case dps_err_ps:
			status = -1;
			break;
		  default:
			NX_RERAISE();
		}
	NX_ENDHANDLER
	return status; 
}

static int
safeUnhideContext(int appCtxt, int raiseFlag, int activateFlag, int hideFlag)
{
	int		status = 0;

	if (appCtxt <= 0) return -1;

	DPSTraceContext(myContext, NO);
	NX_DURING
		PSWUnhideContext(appCtxt, raiseFlag, activateFlag, hideFlag);
	    NXPing();
	NX_HANDLER
		NXReportError(&NXLocalHandler);
		switch (NXLocalHandler.code) {
		  case dps_err_ps:
			status = -1;
			NXLogError("PS error in PSWUnhideContext: %d, %d, %d, %d",
					   appCtxt, raiseFlag, activateFlag, hideFlag);
			break;
		  default:
			NX_RERAISE();
		}
	NX_ENDHANDLER

	return status;
}

static int
safeGetMiniWindows(int appCtxt, int *minis)
{
	int		miniCount;
	int		screenCount;

	if (appCtxt <= 0) return -1;

	DPSTraceContext(myContext, NO);
	NX_DURING
		PScountscreenlist(appCtxt, &screenCount);
		PSWGetMiniWindows(appCtxt, screenCount, &miniCount, minis);
	NX_HANDLER
		NXReportError(&NXLocalHandler);
		switch (NXLocalHandler.code) {
		  case dps_err_ps:
			miniCount = 0;
			NXLogError("PS error in PSWGetMiniWindows: %d, %d, %d",
					   appCtxt, screenCount, miniCount);
			break;
		  default:
			NX_RERAISE();
		}
	NX_ENDHANDLER
	return miniCount;
}

@implementation ABAppInfo : Object

+ initialize
{
	static BOOL done = NO;

	if (!done)	{
		done = YES;

		processTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"%" valueDesc:"i"];

		pidUidTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"i"];
		pidIconTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"i"];
		pidMenuTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"i"];
		pidCtxtTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"i"];
		ctxtPidTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"i"];
		pidMinisTable = [[HashTable allocFromZone:[self zone]] initKeyDesc:"i" valueDesc:"@"];

		[NXApp getScreenSize:&screenSize];
		myContext = DPSGetCurrentContext();
		[self reset];
	}
	return self;
}

+ reset
{
	id			miniStore;
	int			i;
	int			j;
	int			pid;
	int			icon;
	int			menu;
	int			count;
	int			owner;
	int			miniCount;
	int			screenCount;
	int			*minis;
	int			*windows;
	int			table();
	char		nameUidString[40];
    struct tbl_procinfo pi;
	NXZone		*theZone = [self zone];

	[pidUidTable empty];
	[pidMenuTable empty];
	[pidIconTable empty];
	[pidCtxtTable empty];
	[ctxtPidTable empty];
	[processTable empty];
	[pidMinisTable freeObjects];

	PScountwindowlist(0, &count);
	windows = (int *)NXZoneMalloc(theZone, count*sizeof(int)+ARRBUF);
	count = safeGetWindowList(windows);

	minis = (int *)NXZoneMalloc(theZone, count*sizeof(int)+ARRBUF);

	for(i = 0; i < count; i++)	{
		if (safeGetCurrrentOwner(windows[i], &owner) < 0)
			continue;
		if (safeGetCtxtIconPidAndMenu(owner, &icon, &pid, &menu) < 0)
			continue;
		if (![pidIconTable isKey:(const void *)pid])	{
			miniCount = safeGetMiniWindows(owner, minis);
			if (pid > 0 && miniCount > 0)	{
				if (icon < 0) icon = minis[--miniCount];
				miniStore = [[Storage allocFromZone:theZone]
							              initCount:miniCount
						                elementSize:sizeof(int)
						                description:"i"];
				for(j = 0; j < miniCount; j++)
					[miniStore replaceElementAt:j with:(void *)(minis+j)];
				[pidMinisTable insertKey:(const void *)pid value:(void *)miniStore];
			}
			[pidMenuTable insertKey:(const void *)pid value:(void *)menu];
			[pidIconTable insertKey:(const void *)pid value:(void *)icon];
			[pidCtxtTable insertKey:(const void *)pid value:(void *)owner];
			[ctxtPidTable insertKey:(const void *)owner value:(void *)pid];
			if ((int)table(TBL_PROCINFO, pid, (char *)&pi, 1, sizeof pi) == 1 && pi.pi_status==PI_ACTIVE)	{
				[pidUidTable insertKey:(const void *)pid value:(void *)pi.pi_uid];
				sprintf(nameUidString, "%s::%d", pi.pi_comm, pi.pi_uid);
				[processTable insertKey:(const void *)NXUniqueString(nameUidString) value:(void *)pid];
			}
		}
	}
	NXZoneFree(theZone, minis);
	NXZoneFree(theZone, windows);
	return self;
}

+ unhideCtxt:(int)ctxt raise:(BOOL)raiseFlag activate:(BOOL)activateFlag hideOthers:(BOOL)hideFlag
{
	if (safeUnhideContext(ctxt, (int)raiseFlag, (int)activateFlag, (int)hideFlag) == 0)
		return self;
	else
		return nil;
}

+ unhidePid:(int)pid raise:(BOOL)raiseFlag activate:(BOOL)activateFlag hideOthers:(BOOL)hideFlag
{
	int		ctxt = [self getAppCtxtForPid:pid];

	if (safeUnhideContext(ctxt, (int)raiseFlag, (int)activateFlag, (int)hideFlag) == 0)
		return self;
	else
		return nil;
}

+ vanishIconForCtxt:(int)ctxt
{
	return [self vanishIconForPid:[self getPidForAppCtxt:ctxt]];
}

+ vanishIconForPid:(int)pid
{
	int		appIcon = [self getAppIconForPid:pid];

	if (appIcon > 0)	{
		DPSTraceContext(myContext, NO);
		PSWMove(NOWHERE, NOWHERE, appIcon);
		NXPing();
		return self;
	}
	else
		return nil;
}

+ unvanishIconForCtxt:(int)ctxt
{
	return [self unvanishIconForPid:[self getPidForAppCtxt:ctxt]];
}

+ unvanishIconForPid:(int)pid
{
	int		appIcon = [self getAppIconForPid:pid];

	if (appIcon > 0)	{
		DPSTraceContext(myContext, NO);
		PSWReplaceIcon(appIcon, (int)(screenSize.width)-64);
		NXPing();
		return self;
	}
	else
		return nil;
}

+ getShortName:(char *)shortName from:(const char *)theAppFileName
{
	char	*theAppName;

	if (!shortName || !theAppFileName)
		return self;

	shortName[0] = '\0';
	theAppName = strrchr(theAppFileName, '/');

	if (!theAppName)	{
		strcpy(shortName, theAppFileName);
		return self;
	}
	else
		theAppName++;

	if (!strcmp(theAppName+strlen(theAppName)-4, ".app"))	{
		strncpy(shortName, theAppName, strlen(theAppName)-4);
		shortName[strlen(theAppName)-4] = '\0';
	}
	else if (!strcmp(theAppName+strlen(theAppName)-6, ".debug"))	{
		strncpy(shortName, theAppName, strlen(theAppName)-6);
		shortName[strlen(theAppName)-6] = '\0';
	}
	else if (!strcmp(theAppName+strlen(theAppName)-8, ".profile"))	{
		strncpy(shortName, theAppName, strlen(theAppName)-8);
		shortName[strlen(theAppName)-6] = '\0';
	}
	else
		strcpy(shortName, theAppName);
	shortName[16]=0;

	return self;
}

+ (int)getPidFor:(char *)aName
{
	NXAtom	atom = NXUniqueString(aName);

	if ([processTable isKey:atom])
		return (int)[processTable valueForKey:atom];
	else
		return -1;
}

+ (int)getUidForPid:(int)aPid
{
	if ([pidUidTable isKey:(void *)aPid])
		return (int)[pidUidTable valueForKey:(void *)aPid];
	else
		return -1;
}

+ (int)getAppIconForPid:(int)aPid
{
	if ([pidIconTable isKey:(void *)aPid])
		return (int)[pidIconTable valueForKey:(void *)aPid];
	else
		return -1;
}

+ (int)getAppMenuForPid:(int)aPid
{
	if ([pidMenuTable isKey:(void *)aPid])
		return (int)[pidMenuTable valueForKey:(void *)aPid];
	else
		return -1;
}

+ (int)getAppCtxtForPid:(int)aPid
{
	if ([pidCtxtTable isKey:(void *)aPid])
		return (int)[pidCtxtTable valueForKey:(void *)aPid];
	else
		return -1;
}

+ getAppMiniWinsForPid:(int)aPid
{
	if ([pidMinisTable isKey:(void *)aPid])
		return [pidMinisTable valueForKey:(void *)aPid];
	else
		return nil;
}

+ (int)getPidForAppCtxt:(int)aCtxt
{
	if ([ctxtPidTable isKey:(void *)aCtxt])
		return (int)[ctxtPidTable valueForKey:(void *)aCtxt];
	else
		return -1;
}

+ (BOOL)pidExists:(int)aPid
{
	return [pidUidTable isKey:(void *)aPid];
}

- initForFile:(char *)theAppFileName 
{
	char		theShortName[100];

	[super init];
	[[self class] getShortName:theShortName from:theAppFileName];

	appFileName = NXUniqueString(theAppFileName);
	appShortName = NXUniqueString(theShortName);

	[self update];

	return self;
}

- update
{
	isLaunched = ((appPid = [[self class] getPidFor:(char *)appShortName]) != -1);

	if (isLaunched)	{
		appUid = (int)[pidUidTable valueForKey:(const void *)appPid];
		appCtxt = (int)[pidCtxtTable valueForKey:(const void *)appPid];
		appIconWin = (int)[pidIconTable valueForKey:(const void *)appPid];
		appMainMenu = (int)[pidMenuTable valueForKey:(const void *)appPid];
		appMiniWins = [pidMinisTable valueForKey:(const void *)appPid];
	}
	else	{
		appUid = appCtxt = appIconWin = appMainMenu = -1;
		appMiniWins = nil;
	}

	return self;
}

- activateApp:(BOOL)activateFlag raise:(BOOL)raiseFlag hideOthers:(BOOL)hideFlag
{
	return [[self class] unhideCtxt:appCtxt raise:raiseFlag activate:activateFlag hideOthers:hideFlag];
}

- vanishIcon
{
	if (appIconWin > 0)
		PSWMove(NOWHERE, NOWHERE, appIconWin);
	return self;
}

- unvanishIcon
{
	if (appIconWin > 0)	{
		DPSTraceContext(myContext, NO);
		PSWReplaceIcon(appIconWin, (int)(screenSize.width)-64);
	}
	return self;
}

- free
{
	return [super free];
}

- (BOOL)isLaunched
{
	return isLaunched;
}

- (int)appPid
{
	return appPid;
}

- (int)appUid
{
	return appUid;
}

- (int)appCtxt
{
	return appCtxt;
}

- (int)appIconWin
{
	return appIconWin;
}

- (int)appMainMenu
{
	return appMainMenu;
}

- appMiniWins
{
	return appMiniWins;
}

- (NXAtom)appFileName
{
	return appFileName;
}

- (NXAtom)appShortName
{
	return appShortName;
}

@end

#ifdef ABTEST
#include <libc.h>
#include <string.h>
#include <stdlib.h>

void
listApps()
{
	id		appMinis;
	int		i;
	int		count;
	int		appPid;
	int		appUid;
	int		appCtxt;
	int		appMenu;
	int		appIcon;
	char	theName[30];
	char	*shortName;
	NXHashState	state = [processTable initState];

	printf("%-17s%7s%7s%12s%7s%7s%10s\n", "SHORTNAME", "PID", "UID", "CTXT", "ICON", "MENU", "MINIWINS");
	while ([processTable nextState:&state key:(void *)&shortName value:(void *)&appPid])	{
		if (strstr(shortName, "kernel idle")) continue;
		strcpy(theName, shortName);
		*strstr(theName, "::") = '\0';
		appUid = (int)[pidUidTable valueForKey:(void *)appPid];
		appCtxt = (int)[pidCtxtTable valueForKey:(void *)appPid];
		appIcon = (int)[pidIconTable valueForKey:(void *)appPid];
		appMenu = (int)[pidMenuTable valueForKey:(void *)appPid];
		appMinis = [pidMinisTable valueForKey:(void *)appPid];
		printf("%-17s%7d%7d%12d%7d%7d  ", theName, appPid, appUid, appCtxt, appIcon, appMenu);
		if (appMinis != nil)	{
			count = [appMinis count];
			for(i = 0; i < count; i++)
				printf("%d ", *(int *)[appMinis elementAt:i]);
		}
		printf("\n");
	}
}

main(int argc, char **argv)
{
	id			miniWins;
	int			c;
	int			i;
	int			count;
	char		*theName;
	int			pid = -1;
	int			ctxt = -1;
	int			appUid = -1;
	int			action = 0;
	BOOL		list = NO;
	BOOL		raise = NO;
	BOOL		activate = NO;
	BOOL		hideOthers = NO;
	BOOL		vanishIcon = NO;
	BOOL		unvanishIcon = NO;
	ABAppInfo	*info;
	extern char	*optarg;

	[Application new];
	while((c = getopt(argc, argv, "ac:hHp:ruvN:")) != -1)	{
		switch(c)	{
		  case 'a': activate = YES; break;
		  case 'c': ctxt = atoi(optarg); break;
		  case 'H': hideOthers = YES; break;
		  case 'p': pid = atoi(optarg); break;
		  case 'r': raise = YES; break;
		  case 'u': unvanishIcon = YES; action++; break;
		  case 'v': vanishIcon = YES; action++; break;
		  case 'N':
			info = [[ABAppInfo alloc] initForFile:optarg];
			printf("%-17s%7s%7s%12s%7s%7s%10s\n",
				   "SHORTNAME", "PID", "UID", "CTXT", "ICON", "MENU", "MINIWINS");
			sscanf([info appShortName], "%[^:]::%d", theName, &appUid);
			printf("%-17s%7d%7d%12d%7d%7d  ", theName, [info appPid], appUid,
				   [info appCtxt], [info appIconWin], [info appMainMenu]);
			miniWins = [info appMiniWins];
			if (miniWins != nil)	{
				count = [miniWins count];
				for(i = 0; i < count; i++)
					printf("%d ", *(int *)[miniWins elementAt:i]);
			}
			printf("\n");
			exit(0);
			break;
		  case 'h':
		  case '?':
			fprintf(stderr, "Usage: ctxt-util [[-h] | [-N appName::uid] | [[-p pid|-c ctxt] -arHvu]\n");
			fprintf(stderr, "       with no args, lists all active contexts\n");
			fprintf(stderr, "       -h: show this message\n");
			fprintf(stderr, "       -N: list info for a single app name::uid (eg, 'Edit::0')\n");
			fprintf(stderr, "       In conjunction with -p or -c:\n");
			fprintf(stderr, "           -a: activate (hides if not present)\n");
			fprintf(stderr, "           -r: raise windows\n");
			fprintf(stderr, "           -H: hide other apps\n");
			fprintf(stderr, "           -v: vanish app icon\n");
			fprintf(stderr, "           -u: restore app icon\n");
			exit(c!='h');
		}
	}

	if (action != 1) list = YES;

	if (unvanishIcon && vanishIcon)	{
		fprintf(stderr, "Can only replace OR vanish an icon, not both!\n");
		exit(1);
	}

	if ((raise || activate || hideOthers || vanishIcon || unvanishIcon) && !(pid>0 || ctxt>0))	{
		fprintf(stderr, "You need a process or context id to do that!\n");
		exit(1);
	}

	[ABAppInfo initialize];

	if (raise || activate || hideOthers)	{
		if (pid > 0)
			[ABAppInfo unhidePid:pid raise:raise activate:activate hideOthers:hideOthers];
		else
			[ABAppInfo unhideCtxt:ctxt raise:raise activate:activate hideOthers:hideOthers];
	}
	else if (unvanishIcon)	{
		if (pid > 0)
			[ABAppInfo unvanishIconForPid:pid];
		else
			[ABAppInfo unvanishIconForCtxt:ctxt];
	}
	else if (vanishIcon)	{
		if (pid > 0)
			[ABAppInfo vanishIconForPid:pid];
		else
			[ABAppInfo vanishIconForCtxt:ctxt];
	}
	else
		listApps();
	exit(0);
}

#endif
