/*
 * Winfo
 * by Paul S. Kleppner
 * modified by Ali Ozer 12/92 for 3.0 and to use dumpwindow operator for info.
 *
 * This program may be freely distributed, but not sold.
 * It is provided without warranty of any kind, expressed or
 * implied, as to its fitness for any particular use.
 */

/*
 * WinInfo class.  This is the controlling object of the
 * application -- it manages the report panel, and does
 * all the work.
 */

#import <appkit/appkit.h>
#import "WinInfo.h"
#include "wwrap.h"
#include <stdlib.h>

#import "WView.h"
#import "WinApp.h"

#define DISPLAYWINDOWSLEVEL NX_NORMALLEVEL
#define REPORTPANELLEVEL 1000

// Values for selected context...
#define ALLCONTEXTS -2
#define NOCONTEXT -1

// Display modes
#define CONTENTSMODE 1
#define TRANSPARENTMODE 2
#define OUTLINEMODE 3

extern BOOL getWindowBytes (int windowNum, int *bytes, int *type);

@implementation WinInfo

+ new
{
  self = [super new];
  windowList = NULL;
  wpList = NULL;
  onScreenList = NULL;
  displayWin = nil;
  activeFlag = NO;

  myFont = [Font newFont:"Helvetica" size:10.0 style:0
		  matrix:NX_IDENTITYMATRIX];
  if( [myFont screenFont] )
	myFont = [myFont screenFont];

  [NXApp setDelegate:self];

  displayMode = OUTLINEMODE;
  offScreenMode = NO;
  highlightAlpha = highlightDepth = YES;
  {
	const char *tmp = NXGetDefaultValue([NXApp appName],"ShowNonRetained");
	showNonretained = (tmp && tmp[0] == 'Y');
  }
  allContexts = NO;
 
  return self;
}

// target of the start/stop button.
- startStop:sender
{
  activeFlag = !activeFlag;

  if( activeFlag ) 
	[self start:sender];
  else
	[self stop:sender];

  [timeDelay setEnabled:!activeFlag];

  return self;
}

// Start things going.  Messaged when the user
// has presed the "start" button.
// sender is the start/stop button.
- start:sender
{
  id vw;
  NXRect r;
  struct winfo *wp;
  int i;

  int secs, osecs;
  NXTrackingTimer myTimer;

  selectedContext = allContexts ? ALLCONTEXTS : NOCONTEXT;

  // Delay, if user requested
  osecs = secs = [timeDelay intValue];

  if( secs > 0 ){
	NXBeginTimer(&myTimer, 0.0, 1.0);
	for( ; secs > 0; --secs ){
	  [NXApp getNextEvent:NX_TIMERMASK];
	  [timeDelay setIntValue:secs-1];
	  NXPing();
	}
	NXEndTimer(&myTimer);
  }

  [self clearReport];

  // Get information on all windows on the system.
  [self buildWindowList];

  // And get numbers of on-screen windows.
  [self buildOnScreenList];

  // Look through the on-screen list, and set the winfo struct
  // to record that each of these windows is on screen.
  for( i = 0; i < numOnScreen; ++i ){
	wp = [self findWinfoByNumber:onScreenList[i]];
	if( wp != NULL )
	  wp->origOnScreen = wp->onScreenNow = YES;
  }

  // Set up displayWin.  This is a window the size of the full screen,
  // into which we draw the outlines or contents of all other windows.
  // displayWin gets set in front of all other windows on the system
  // (except the Winfo report panel), and hides them all.
  // displayWin is a panel, so that we can avoid making it key
  [NXApp getScreenSize:&r.size];
  
  //Initialize the origin of the rectangle
  r.origin.x = r.origin.y = 0.0;
  
  displayWin = [Panel newContent:&r style:NX_PLAINSTYLE backing:NX_BUFFERED
		buttonMask:0 defer:NO];
  [displayWin setBecomeKeyOnlyIfNeeded:YES];		// it will never be key
  vw = [WView newForClient:self];
  
  [displayWin setContentView:vw];

  // Put the report panel at the top-most level
  [sender lockFocus];
  mySetCurrentWindowLevel(REPORTPANELLEVEL);
  [[sender window] orderFront:self];
  [sender unlockFocus];

  if (selectedContext != NOCONTEXT) {
	[self moveOnScreen:offScreenMode forContext:selectedContext];
  }

  // Fill in display window with images of all other windows
  [self showAllWindows];

  // Summarize info for all windows...
  [self showReportSummaryForAll];

  // Put the display window one level beneath the report panel
  // (but in front of everybody else)
  [vw lockFocus];
  mySetCurrentWindowLevel(REPORTPANELLEVEL-1);
  [vw unlockFocus];

  [displayWin orderFront:self];

  [timeDelay setIntValue:osecs];

  // If there was a timer, we may not be active at this point;
  // so activate us.
  [NXApp activateSelf:YES];

  NXPing();

  return self;
}

// Called when the stop button is pressed.  sender is that button.
// Free up data structures, and the displayWin window.

- stop:sender
{
  [self clearOffScreen];

  if( windowList != NULL )
	free(windowList);
  if( wpList != NULL )
	free(wpList);
  [displayWin free];
  windowList = NULL;
  wpList = NULL;
  displayWin = nil;

  [[sender window] orderFront:self];
  mySetCurrentWindowLevel(NX_NORMALLEVEL);

  return self;
}

// Mouse down proxy.  Called by the WView when a mouse down occurs
// on the full-screen display window.  WView masks all other
// windows (except the Winfo report panel), so it receives all mouse hits.
// We figure out what window was at that point, and bring that window
// to front (or send it to back if a command-click).  Then we
// update the report panel appropriately.
- mouseDownProxy:(NXEvent *)event
{
  struct winfo *wp;

  // Get the winfo for the window at the mouse-hit.
  wp = [self mapPointToWindow:&event->location];

  if( wp != NULL ){
	if( event->flags & NX_COMMANDMASK ){
	  // command-click: send to back
	  myOrderWindow(NX_BELOW, 0, wp->num);
	} else if( event->flags & NX_ALTERNATEMASK ){
	  // alt-click: send to front
	  myOrderWindow(NX_ABOVE, 0, wp->num);
	} else {
	  // regular click: send to front
	  if( offScreenMode && !allContexts && (wp->context != selectedContext )) {
		[self moveOnScreen:NO forContext:selectedContext];
		[self moveOnScreen:YES forContext:wp->context];
		selectedContext = wp->context;
	  }
	  myOrderWindow(NX_ABOVE, 0, wp->num);
	  // Update report info for windows
	  [self showReportFor:wp];
	}

	// Redisplay image of all windows, to reflect new order
	[self showAllWindows];
  }
  return self;
}


// showAllWindows.  This contructs an image of all windows
// on the displayWin which hides them all.  If outlineMode is set,
// then we just draw outlines; otherwise, we fill in the images.
- showAllWindows
{
  struct winfo *wp;
  NXRect r;
  int i;

  [[displayWin contentView] lockFocus];
  [myFont set];

  [[displayWin contentView] getBounds:&r];

  if( displayMode != CONTENTSMODE ){
	// clear background to dark gray
	PSsetgray(NX_DKGRAY);
	NXRectFill(&r);

	[self buildOnScreenList];
	for( i = numOnScreen-1; i >= 0; --i ){
	  if( wp = [self findWinfoByNumber:onScreenList[i]] ){
		if( !wp->isMe )
		  [self showOutlineWindow:wp];
	  }
	}
  } else {
	fillBelowWin([self trueWinNumberFor:reportPanel],
				 [self trueWinNumberFor:displayWin]);
  }

  [[displayWin contentView] unlockFocus];

  [displayWin flushWindow];
  NXPing();

  return self;
}

- setReportPanel:anObject
{
    reportPanel = anObject;
    [reportPanel setFrameAutosaveName:"Report Panel"];
    return self;
}

- (void)getDepthName:(NXWindowDepth)depth into:(char *)buffer
{
    switch (depth) {
	case NX_TwoBitGrayDepth: sprintf (buffer, "2 bit gray"); break;
	case NX_EightBitGrayDepth: sprintf (buffer, "8 bit gray"); break;
	case NX_TwelveBitRGBDepth: sprintf (buffer, "12 bit RGB"); break;
	case NX_TwentyFourBitRGBDepth: sprintf (buffer, "24 bit RGB"); break;
	default: sprintf (buffer, "%d spp, %d bps", NXNumberOfColorComponents(NXColorSpaceFromDepth(depth)), NXBPSFromDepth(depth)); break;
    }
}

- changeHighlightOptions:sender
{
  highlightAlpha = [[sender findCellWithTag:0] state];
  highlightDepth = [[sender findCellWithTag:1] state];
  if( activeFlag ){
	[self showAllWindows];
  }
  return self;
}

- changeDisplayOptions:sender
{
  displayMode = [sender selectedTag];
  if( activeFlag ){
	[self showAllWindows];
  }
  return self;
}

- changeOffScreenOptions:sender
{
  switch ([sender selectedTag]) {
	case 1: offScreenMode = allContexts = NO; break;
	case 2:	offScreenMode = YES; allContexts = NO; break;
	case 3: offScreenMode = allContexts = YES; break;
  }
  selectedContext = allContexts ? ALLCONTEXTS : (reportedWindow ? reportedWindow->context : NOCONTEXT);
  if( activeFlag ){
	[self moveOnScreen:offScreenMode forContext:selectedContext];
	[self showAllWindows];
  }
  return self;
}

// Draw the outline for the given window.
//
- showOutlineWindow:(struct winfo *)wp
{
  char cbuf[128];
  float borderWidth = (wp == reportedWindow) ? 5.0 : ((reportedWindow && (wp->context == reportedWindow->context)) ? 3.0 : 1.0);
  
  PSsetgray(NX_BLACK);

  if (displayMode == OUTLINEMODE) {
	BOOL highlight = 
		((highlightAlpha && wp->hasAlpha ) ||
		 (highlightDepth && (wp->depth != NX_TwoBitGrayDepth)));
	PSsetgray(highlight ? NX_LTGRAY : NX_WHITE );
	NXRectFill (&wp->place);
  } else {
	PSsetgray(NX_WHITE );
	PSsetalpha (0.16666667);
	PScompositerect(NX_X(&wp->place),NX_Y(&wp->place),NX_WIDTH(&wp->place),NX_HEIGHT(&wp->place),NX_SOVER);
	PSsetalpha (1.0);
  }	
  PSsetgray(NX_BLACK);
  NXFrameRectWithWidth(&wp->place, borderWidth);

  PSmoveto(wp->place.origin.x + 5, wp->place.origin.y + wp->place.size.height - 13);
  sprintf(cbuf, "%d", wp->num);
  PSshow(cbuf);

  PSmoveto(wp->place.origin.x + 5, wp->place.origin.y + wp->place.size.height - 23);
  if (wp->bytes >= 0) sprintf(cbuf, "%.1fkb", ((float) wp->bytes) / 1024);
  else sprintf(cbuf, "?kb");
  PSshow(cbuf);
  
  return self;
}

// Given a point, find the winfo struct for the top-most window at
// that point.  Returns NULL if not found.
- (struct winfo *) mapPointToWindow:(NXPoint *)p
{
  int wnum;
  float wx, wy;
  int found;

  myFindWindow(p->x, p->y, NX_BELOW, [self trueWinNumberFor:displayWin],
			   &wx, &wy, &wnum, &found);
  if( found )
	return [self findWinfoByNumber:wnum];
  else
	return NULL;
}

// Clear the report panel
- clearReport
{
  [repBox setTitle:"No Window Selected"];
  [repBox display];

  [repContext setStringValue:""];
  [repSize setStringValue:""];
  [repAlpha setStringValue:""];
  [repDepth setStringValue:""];
  [repDepthLimit setStringValue:""];
  [repBackingStore setStringValue:""];
  [repType setStringValue:""];
  [repTotal setStringValue:""];
  [repTotalBackingStore setStringValue:""];
  [repAllTotal setStringValue:""];
  [repAllTotalBackingStore setStringValue:""];

  reportedWindow = NULL;

  return self;
}

// Display in the report panel a report for the 
// window with the given winfo struct.
- showReportFor:(struct winfo *)wp
{
    NXRect *rp;
    char cbuf[128];
    int i, tback, non, noff;
    struct winfo *wp2;
    
    reportedWindow = wp;

    rp = &wp->place;

    // Set the selected report box title to specify this window's number
    sprintf(cbuf, "Window #%d", wp->num);
    [repBox setTitle:cbuf];
    [repBox display];

    // Show context
    sprintf(cbuf, "%u", (unsigned int)(wp->context));
    [repContext setStringValue:cbuf];

    // Show size
    sprintf(cbuf, "%.0f x %.0f", rp->size.width, rp->size.height);
    [repSize setStringValue:cbuf];

    // Show alpha
    if( wp->hasAlpha )
	[repAlpha setStringValue:"Yes"];
    else
	[repAlpha setStringValue:"No"];

    // Show depth and colors
    {
	char buffer[100];
	[self getDepthName:wp->depth into:buffer];
	[repDepth setStringValue:buffer];
	[self getDepthName:wp->depthLimit into:buffer];
	[repDepthLimit setStringValue:buffer];
    }

    // Show backing store
    if (wp->bytes >= 0) [repBackingStore setIntValue:wp->bytes];
    else [repBackingStore setStringValue:"Unknown"];

    switch (wp->type) {
	case NX_NONRETAINED: [repType setStringValue:"Nonretained"]; break;
	case NX_RETAINED: [repType setStringValue:"Retained"]; break;
	case NX_BUFFERED: [repType setStringValue:"Buffered"]; break;
	default: [repBackingStore setStringValue:"Unknown"]; break;
    }

    // For all windows in same context, count number
    // on and off-screen, and total their backing store

    non = noff = 0;
    tback = 0;

    for( i = 0; i < numWins; ++i ){
	wp2 = wpList + i;
	if( wp2->context == wp->context ){
	    if (wp2->bytes >= 0) tback += wp2->bytes;
	    if( wp2->origOnScreen )
		++non;
	    else
		++noff;
	}
    }

    sprintf (cbuf, "%d windows (%d on, %d off)", non+noff, non, noff);	
    [repTotal setStringValue:cbuf];
    [repTotalBackingStore setIntValue:tback];
  
  return self;
}

// The appkit assigns logical window number to each window, apparantly
// for its convenience.  This function returns the "true" window
// number assigned by the window server, given an Appkig window number.
- (int) trueWinNumberFor:win
{
  int n;
  id vw;

  vw = [win contentView];
  [vw lockFocus];
  PScurrentwindow(&n);
  [vw unlockFocus];
  return n;
}

- clearOffScreen
{
  [self moveOnScreen:NO forContext:selectedContext];
  [self showAllWindows];
  return self;
}

// how == YES to move off-screen windows on, else moves them back off
// context is what context's windows to change
- moveOnScreen:(BOOL)how forContext:(int)context
{
  int i;
  struct winfo *wp;

  for( i = 0; i < numWins; ++i ){
	wp = wpList + i;

	if( !wp->origOnScreen ){
	  BOOL show = ((wp->type != NX_NONRETAINED) || showNonretained) && ((context == wp->context) || (context == ALLCONTEXTS));
	  if( how && show ){
		if( !wp->onScreenNow ){
		  if (wp->level > DISPLAYWINDOWSLEVEL) {
		    mySetCurrentWindowLevelOfWindow(wp->num, DISPLAYWINDOWSLEVEL);
		  }
		  myOrderWindow(NX_ABOVE, 0, wp->num);
		  wp->onScreenNow = YES;
		}
	  } else {
		if( wp->onScreenNow ){
		  myOrderWindow(NX_OUT, 0, wp->num);
		  if (wp->level > DISPLAYWINDOWSLEVEL) {
		    mySetCurrentWindowLevelOfWindow(wp->num, wp->level);
		  }
		  wp->onScreenNow = NO;
		}
	  }
	}
  }
  return self;
}

- appWillTerminate:sender
{
  if( wpList != NULL )
	[self clearOffScreen];

  return self;
}

// Given a DPS window number, return the winfo struct that
// describes that window.  We just do a linear scan; this is fine
// given the relatively small number of windows we expect.
- (struct winfo *) findWinfoByNumber:(int)wnum
{
  struct winfo *wp;
  int i;

  wp = wpList;
  for( wp = wpList, i = numWins; i > 0; --i, ++wp )
	if( wp->num == wnum )
	  return wp;
  return NULL;
}

- (void)showReportSummaryForAll
{
    struct winfo *wp2;
    char buf[100];
    int tback = 0, non = 0, noff= 0, i;
    
    for( i = 0; i < numWins; ++i ){
	wp2 = wpList + i;
	if (wp2->bytes >= 0) tback += wp2->bytes;
	if( wp2->origOnScreen )
	    ++non;
	else
	    ++noff;
    }

    sprintf (buf, "%d windows (%d on, %d off)", non+noff, non, noff);	
    [repAllTotal setStringValue:buf];
    [repAllTotalBackingStore setIntValue:tback];
}

// Build the complete window list.  This will calculate numWins,
// create wpList, and fill in a wpList with info on all existing windows.
- buildWindowList
{
  int i;
  int alpha;
  struct winfo *wp;
  int myContext;


  myContext = [NXApp contextNumber];

  // Get an array of DPS window numbers
  PScountwindowlist(0, &numWins);
  windowList = malloc(sizeof(int) * numWins);
  PSwindowlist(0, numWins, windowList);

  wpList = malloc(sizeof(struct winfo) * numWins);

  --numWins;					// skip back-most window

  // For each window, get info for winfo struct describing window
  for( i = 0; i < numWins; ++i ){
	wp = wpList + i;
	wp->num = windowList[i];
	myCurrentWindowBounds(windowList[i],
						  &wp->place.origin.x, &wp->place.origin.y,
						  &wp->place.size.width, &wp->place.size.height);
	myCurrentWindowAlpha(windowList[i], &alpha);
	wp->hasAlpha = (alpha != 2);

	myCurrentWindowDepth(windowList[i], &wp->depth);
	myCurrentWindowDepthLimit(windowList[i], &wp->depthLimit);

	if (!getWindowBytes (windowList[i], &wp->bytes, &wp->type)) {
	    wp->bytes = wp->type = -1;
	}

	myCurrentOwner(windowList[i], &wp->context);
	myCurrentWindowLevel(windowList[i], &wp->level);
	
	if( wp->context == myContext )
	  wp->isMe = YES;
	else
	  wp->isMe = NO;

	wp->origOnScreen = wp->onScreenNow = NO;
  }

  return self;
}

// Build the on-screen list of windows.
- buildOnScreenList
{
  // Find the on-screen windows
  // with their order.
  PScountscreenlist(0, &numOnScreen);
  if( onScreenList )
	free(onScreenList);
  onScreenList = malloc(sizeof(int) * numOnScreen);
  PSscreenlist(0, numOnScreen, onScreenList);

  return self;
}

@end
