
/*
 * This class is in charge of application-wide activity such as handling the
 * main menu commands, enabUU and disabling controls and managing the tick
 * counter display.  It is also responsible for receiving drawing requests 
 * from the sort threads.  When any thread needs to draw, it conjures up a 
 * simple Mach message which it sends to the drawing port that the SortApp 
 * monitors.  The SortApp will have the appropriate view do the necessary
 * drawing.
 *
 * Author: Julie Zelenski, NeXT Developer Support
 * 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 <appkit/appkit.h>
#import "SortApp.h"
#import "SortController.h"
#import "GenericSort.h"
#import "SortView.h"
#import <mach/cthreads.h>
#import <string.h>


/* Global variables shared across several class/threads. */

BOOL Abort;			// global variable to signal abort 
int TickCount;              	// global variable of current tick count 
mutex_t TickLock;		// mutual exclusion to protect TickCount 
condition_t TickUpdated;      	// signaled when TickCount is increased 


@implementation SortApp


+ initialize;
/* +initialize is sent to the factory object for SortApp before any 
 * other messages are sent.  This is a good place to allocate and initialize 
 * the global variables.
 */
{
    if (self == [SortApp class]) {
	Abort = NO;
	TickCount = 0;
	TickLock = mutex_alloc();
	TickUpdated = condition_alloc();
    }
    return self;
}


+ new;
{  
    self = [super new];
    teNum = (DPSTimedEntry)0;
    return self;
}



/* TARGET-ACTION METHODS */

- info:sender
/*
 * The info panel is a separate nib module and only loaded on demand.
 */
{
    if (!infoPanel) {
	if (![self loadNibSection:"InfoPanel.nib" owner:self withNames:NO]) {
	    NXLogError ("Could not load InfoPanel.nib");
	}
    }
    [infoPanel makeKeyAndOrderFront:self];
    return self;
}


- help:sender
/*
 * The help panel is a separate nib module and only loaded on demand.
 */
{
    if (!helpPanel) {
	if (![self loadNibSection:"HelpPanel.nib" owner:self withNames:NO]) {
	    NXLogError ("Could not load HelpPanel.nib");
	}
    }
    [helpPanel makeKeyAndOrderFront:self];
    return self;
}


- go:sender
/*
 * This button gets the whole thing going.  It disables the controls that 
 * will be inactive while the sort is running, resets the tick countUVand 
 * asks the SortController to start the trial.  The SortController will launch 
 * a thread to generate the data and run the sorts, so this method will return 
 * almost immediately.  That way the user interface of the program remains 
 * responsive.  The user can stop the sort, change the speed, go look at the 
 * Info panel, whatever!  When a sort is running, this button becomes the Stop 
 * button, used to cancel a sort in progress.
 */
{
    if (!strcmp([sender icon],"Stop")) // if sorting, this is "Stop" button
        return [self stop:self];
    if ([[goButton window] makeFirstResponder:[goButton window]]) {
    	if ([sortController startSort]) { // if trial got started ok
	    [self setControlsEnabled:NO];
	    [messageField setStringValue:
	    		[stringTable valueForStringKey:"Data"]];
	    [tickField setIntValue:0];
	}
    } 
    return self;
}


- stop:sender
/*
 * This method cancels a sort in progress the whole thing going.  It stops 
 * the tick counter and sets the global variable Abort to YES.  The sorting 
 * threads continually check this variable and when the recognize it they 
 * send one last message to the drawPort which will display the canceled 
 * sort. When they have all check in, the method allFinished will do any 
 * necessary clean up. 
 */
{
    Abort = YES;    // threads will pick up on this & report back when killed
    [messageField setStringValue:[stringTable valueForStringKey:"Canceled"]];
    condition_broadcast(TickUpdated); 
    [self stopTickCounter];
    return self;
}


- setControlsEnabled:(BOOL)value;
/* This method is called to disable all the inactive controls in the parameter 
 * window while a sort is in progress (the data set size, percent sorted, etc).  
 * This method is also called to enable those controls when the sorts have
 * finished.
 */
{
    [[performRadios window] disableFlushWindow];
    [performRadios setEnabled:value];
    [dataSetMatrix setEnabled:value];
    [algorithmMatrix setEnabled:value];
    [tickValueMatrix setEnabled:value];
    [tickMatrix setEnabled:value];
    if (value) 
    	[goButton setIcon:"Go!"];
    else 
    	[goButton setIcon:"Stop"];
    [[[performRadios window] reenableFlushWindow] flushWindow];
    return self;
}


- allFinished;
/* This method is sent by the SortController when it has verified that 
 * all sorts haUWinished in the current trial.  It will stop the tick 
 * counter, re-enable the controls, and reset Abort to NO.
 */
{
    [self stopTickCounter];
    [self setControlsEnabled:YES];
    if (!Abort) 
        [messageField setStringValue:[stringTable valueForStringKey:"Done"]];
    Abort = NO;
    return self;
}


- setTickField:anObject
/*
 * The tickField is a text field on the animation window that displays the
 * tick count.  When a sort is running, a timed entry is called every second 
 * to update the tick field.  The timed entry runs at a priority one higher
 * than NXMODALRESPTHRESHOLD.  Because of this, the tick field is updated even 
 * during a modal response loop (such as changing the speed slider).
 */
{ 
    tickField = anObject;     
    [tickField allocateGState];	// for more efficient lock/unlock focus
    return self; 
}


static void updateTick (teNum,now,self)
  DPSTimedEntry teNum;
  double now;
  SortApp *self;
{
    mutex_lock(TickLock);
    [self->tickField setIntValue:TickCount];	// display current tick count
    mutex_unlock(TickLock);
}
 
    
- startTickCounter;
/* This method is called to reset the tick count to zero and to kick off the 
 * timed entry that will update the tick field display every second.
 */
{
    TickCount = 0;
    if (teNum) 
        DPSRemoveTimedEntry(teNum);
    teNum = DPSAddTimedEntry(1.0, &updateTick, self, NX_MODALRESPTHRESHOLD +1);
    return self;
}


- stopTickCounter;
/* This method is called to remove the timed entry that has been updating the 
 * tick field every second.  The tick field will display the current tick 
 * count.
 */
{
    if (teNum) 
        DPSRemoveTimedEntry(teNum);
    teNum = (DPSTimedEntry)0;
    [tickField setIntValue:TickCount];
    return self;
}


- stringTable;
/* This method simply returns the string table for other objects which need to 
 * access strings.
 */
{
    return stringTable;
}

- draw:(simpleMsg *)msg;
/* This is the method called by the message handler to do draw requests from 
 * the sorting threads.  The messages are configured so that the msg_id field 
 * of the message header indicates what type of message it is (swap, move, 
 * compare, or finished).  The first integer in the message is the sortNum 
 * which is the unique integer for the sort sending the message. This integer 
 * can be passed to the SorUXtroller to get the Sort object for that sort, 
 * which can be used to get its SortView.   The other integers passed in the 
 * message are the parameters for the different draw messages (the values to 
 * swap, the positions to compare, etc).  The SortApp then messages the 
 * appropriate SortView, passing the parameters, and the SortView will do the 
 * drawing.
 */
{
    id view;
    
    view = [[sortController findSortWithNum:msg->sortNum] view];
    switch(msg->h.msg_id) {
        case SORTING_MSG:   if (!Abort) [messageField setStringValue:
			    	[stringTable valueForStringKey:"Sorting"]];
    			    break;
	case COMPARE_MSG:   [view compare:msg->position[0] value:msg->value[0]
			    with:msg->position[1] value:msg->value[1]];
			    break;
        case SWAP_MSG:      [view swap:msg->position[0] value:msg->value[0]
			    with:msg->position[1] value:msg->value[1]];
		      	    break;
        case MOVE_MSG:      [view moveValue:msg->value[0] to:msg->position[0] 
			    oldValue:msg->value[1]];
	           	    break;
        case FINISHED_MSG:  [view displayFinished];
    			    [sortController sortFinished:msg->sortNum];
			    break;
    }
    return self;
}

static void msgHandler(msg,self)
  simpleMsg *msg;
  id self;
{    
    [self draw:msg];
}


- (port_t)drawPort;
/* This method returns the port allocated by the main thread.  All drawing 
 * messages are sent to this port. 
 */
{
   return drawPort;
}


/* DELEGATE METHODS */

- appDidInit:sender
/* This method allocates the port for the main thread where all drawing 
 * messages are sent.  Then, it adds the port to those monitored by the 
 * application.  Whenever a message is sent to this port, the handler 
 * function is called to draw the message.  This port is registered at a 
 * priority of one greater than NX_MODAL RESPTHRESHOLD.  Because of this, 
 * the drawing messages will continue to be processed, even during a modal 
 * response loop (such as changing the speed slider).  The speed slider sends 
 * message continuously so you can dynamically change the speed and immediately 
 * see the effects of those changes.
 */
{      
    port_allocate(task_self(), &drawPort);
    DPSAddPort(drawPort,msgHandler,sizeof(simpleMsg),self,NX_MODALRESPTHRESHOLD +1);
    return self;
}

@end