//
// Calculator.m
// Copyright (c) 1990, 1991,1992 by Jiro Nakamura 
// All rights reserved
//
// Maintains a simple 4++ function calculator in Objective-C
// Doesn't handle algebraic notation or simple ordering.
//
//	by Jiro Nakamura (jiro@shaman.com)
//
// RCS Information
// Revision Number->	$Revision: 1.8 $
// Last Revised->	$Date: 92/02/02 18:24:36 $
//
static char rcsid[] = "$Id: Calculator.m,v 1.8 92/02/02 18:24:36 jiro Exp Locker: jiro $";

#import <appkit/Application.h>
#import <appkit/Button.h>	
#import <appkit/Matrix.h>	
#import <appkit/Panel.h>
#import <appkit/Pasteboard.h>
#import <appkit/publicWraps.h>  	/* for NXBeep( )  */
#import <appkit/ScrollView.h>  
#import "appkit/TextField.h"
#import <strings.h>
#import "Calculator.h"
#import <math.h>
#import <signal.h>
#import <libc.h>

#define OP_NOOP		0
#define OP_ADD		1
#define OP_SUBTRACT	2
#define OP_MULTIPLY	3
#define	OP_DIVIDE	4
#define OP_LOGICAL_OR	5
#define OP_LOGICAL_AND	6
#define OP_LOGICAL_EOR	7	
#define OP_LOGICAL_NOR	8
#define OP_POWER	9

#define SHIFTMASK	(NX_ALTERNATEMASK | NX_SHIFTMASK)

char *OperatorDesc[10] =
	{
	"=",
	"+",
	"-",
	"x",
	"/",
	"|",
	"&",
	"eor",
	"nor",
	"^"};
	

#define CALCULATOR_ICON				"window.calculator.tiff"
#define	CALCULATOR_TITLE_SHORT			"T.I. 68040"
#define CALCULATOR_TITLE_LONG			"Tennessee Instruments"


#define DM_BINARY	2
#define DM_OCTAL	8
#define DM_DECIMAL	10
#define DM_HEXADECIMAL	16

// The minimum dimensions of the window
#define MIN_WIDTH			210.0
#define MIN_HEIGHT			350.0


// Together, these two declarations form the floating point exception
// part of Calculator
id me;		// Class global attachment to self set in +new
BOOL fpError;	// Global floating point error indicator

void floatingError(int sig)
{
	[me errorDisplay: "- Floating point error"];
	fpError = YES;
	return;
}

// Math operators
double magic( double y, int op, double x)
{
	switch( op)
		{
		case OP_ADD:
			return y + x;
		case OP_SUBTRACT:
			return y - x ;
		case OP_MULTIPLY:
			return y * x;
		case OP_DIVIDE:
			if( x == 0.0)
				fpError = YES;
			else
				return y / x;
		case OP_LOGICAL_OR:
			return (int) y | (int) x;
		case OP_LOGICAL_AND:
			return (int) y & (int) x;
		case OP_LOGICAL_EOR:
			return (int) y ^ (int) x;
		case OP_POWER:
			return pow( y, x);
		case OP_NOOP:
			return y;
		default:
			NXRunAlertPanel("Unrecognizable math.",
				"Contact Jiro: jiro@shaman.com",
				 "OK",NULL, NULL);
		}
	return 0.0;
}

// Not the most efficient (timewise) but....
// Note: x should be an integer.
double factorial(double x)
{
	double y;

	if( x <= 1.0)
		return 1.0;
	
	if( x >= 170)	// Larger than this overflows a double
		{
		[me errorDisplay: "- Overflow"];
		return -1;
		}
					
	y = rint(x);
	x = 1;
	
	while( y )
		x *= y--;

	return x;	
}

int readBinary( const char *buf )
{
	int tmp = 0;
	const char *pt = buf;
	
	for( ; *pt != '\0' && (*pt == '0' || *pt == '1'); pt ++ )
		tmp = (tmp << 1) + (*pt - '0') ;
	return tmp;
}

char * writeBinary(char *buf, int val )
{
	int tmp;
	
	for( tmp = 15; tmp >= 0; tmp -- )
		{
		buf[15 - tmp] = (val & (1 << tmp) ) ? '1' : '0';
		}
	buf[16] = '\0';

	return buf;
}


@implementation Calculator

/* The main reason for the following method is just to avoid warnings
 *     about rcsid being unreferenced...   Garance/Jul 28/94
 */
- (const char *) returnRcsid
{
     return rcsid;
}

- initCalc
{	
	#ifdef DEBUG
		fprintf(stderr,"Initing calculator object\n");
	#endif

	if( calcDidInit == YES)
		return self;
		
	m = 0.0;
	x = 0.0;
	y = 0.0;
	x_isNew = YES;
	displayMode = DM_DECIMAL;
	x_hasDecimal = NO;
	currentOperation = OP_NOOP;
	[self updateOperationMarker];
	fpError = NO;
	signal( SIGFPE, floatingError);
	me = self;
	calcDidInit = YES;
	scrollText = [scrollDisplay docView];
	[scrollText setAlignment: NX_RIGHTALIGNED];
	
	return self;
}

- key_add:sender
{
	[self processPrevious];
	currentOperation = OP_ADD;
	[self updateOperationMarker];
	return self;
}
	
- key_subtract:sender
{
	[self processPrevious];
	currentOperation = OP_SUBTRACT;
	[self updateOperationMarker];
	return self;
}


- key_multiply:sender
{
	[self processPrevious];		
	currentOperation = OP_MULTIPLY;
	[self updateOperationMarker];
	return self;
}

- key_divide:sender
{
	[self processPrevious];
	currentOperation = OP_DIVIDE;
	[self updateOperationMarker];
	return self;
}

- key_logicalOr: sender
{
	[self processPrevious];
	currentOperation = OP_LOGICAL_OR;
	[self updateOperationMarker];
	return self;
}

- key_logicalAnd: sender
{
	[self processPrevious];
	currentOperation = OP_LOGICAL_AND;
	[self updateOperationMarker];
	return self;
}

- key_logicalEor: sender
{
	[self processPrevious];
	currentOperation = OP_LOGICAL_EOR;
	[self updateOperationMarker];
	return self;
}



- key_clear:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	x = 0;
	x_hasDecimal = NO;
	x_isNew = YES;
	[self setDisplay];
	return self;
}

- key_number:sender
{
	char key;
	static char tmp[80];

	// We cannot do this from IB and we do not have a 
	// appDidInit method
	[invisibleEnterKey setKeyEquivalent:3];
	
	if( fpError)
		{
		NXBeep();
		return self;
		}

	if( x_isNew)
		{
		[self clearDisplay];
		x_isNew = NO;
		}
		
	key =   *[[sender  selectedCell]title];
	
	#ifdef DEBUG
		fprintf(stderr,"Key = %c\n", key);
	#endif
	
	if( key == '.')	// Decimal point
		if( x_hasDecimal || displayMode != DM_DECIMAL )
			{
			NXBeep();
			return self;
			}
		else
			x_hasDecimal = YES;
	
	if( key == '0' && *([display stringValue]) == '0'
		&& x_hasDecimal == NO && displayMode == DM_DECIMAL)
		{
		NXBeep();
		return self;
		}
					
	strcpy( tmp, [display stringValue]);
	strcat( tmp, [[sender  selectedCell]title]);
	[display setStringValue: tmp];
	[self getDisplay];
	
    return self;
}

- key_log:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	[self getDisplay];

	if( ([NXApp currentEvent]->flags) & SHIFTMASK)
		{
		[self setScrollOperation: "Ln" andNumber: x];
		x = log( x );
		}	
	else
		{
		[self setScrollOperation: "Log" andNumber: x];
		x = log10( x );
		}
	[self setDisplay];
	[self setScrollOperation: "=" andNumber: x];
		
    return self;
}

- key_squareRoot:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	[self getDisplay];

	// alternate key pressed
	if( ([NXApp currentEvent]->flags) & SHIFTMASK)
		{
		[self setScrollOperation: "square" andNumber: x];
		x = pow( x, 2);
		}	
	else
		{
		[self setScrollOperation: "root" andNumber: x];
		if( x >= 0.0)
			x = sqrt(x);
		else
			{
			[self	errorDisplay: "- Root of negative"];
			return self;
			}
		}

	[self setDisplay];
	[self setScrollOperation: "=" andNumber: x];
		
    return self;
}

- key_memory:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	[self getDisplay];
	#ifdef DEBUG
		fprintf(stderr, "Memory: Tag = %d, m = %f, x = %f\n",
			[sender tag], m, x);
	#endif
	
	switch( [[sender selectedCell] tag])
		{
		case 0:		// M+
			m += x;
			[self setScrollOperation: "M+" andNumber: x];
			break;
		case 1:		// M-
			m -= x;
			[self setScrollOperation: "M-" andNumber: x];
			break;
		case 2:		// M=
			m = x;
			[self setScrollOperation: "M=" andNumber: x];
			break;
		case 3:		// MR
			x = m;
			x_isNew = YES;
			x_hasDecimal = NO;
			[self setDisplay];
			[self setScrollOperation: "MR" andNumber: x];
			break;
		case 4:		// MC
			m = 0;
			break;
		default:
			NXRunAlertPanel("Dumb Error", 
				"Key_memory returns false case. Why? "
				"Ask Jiro.", "OK", NULL, NULL);
			break;
		}
	[self	updateMemoryMarker];
	return self;
}

- key_factorial:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	[self getDisplay];
	
	if( ([NXApp currentEvent]->flags) & SHIFTMASK)
		{
		[self setScrollOperation: "1/" andNumber: x];

		if( x != 0.0)
			x = 1/x;
		else
			{
			[self errorDisplay: "- Division by zero"];
			return self;
			}
		}
	else
		{
	
		[self setScrollOperation: "factorial" andNumber: x];
		if( x != rint(x) )	// if x is a non-integer
			{
			[self errorDisplay: "- Non-integer !"]; 
			return self;
			}
		
		if( x < 0.0 )	// if x is  negative
			{
			[self errorDisplay: "- Negative !"]; 
			return self;
			}				
		x = factorial(x);
		if( fpError )
			{
			[self errorDisplay];
			return nil;
			}
		}	

	[self setDisplay];
	[self setScrollOperation: "=" andNumber: x];

	return self;
}

	

- key_enter:sender
{
	#ifdef DEBUG
		fprintf(stderr, "Enter key hit. y = %f. Op = %d.\n", y,
			currentOperation);
	#endif
	
	if( fpError)
		{
		NXBeep();
		return self;
		}
	
	[self getDisplay];

		
	if( currentOperation != OP_NOOP)
		{
		[self setScrollOperation: OperatorDesc[currentOperation] 
			andNumber: x];
		x = magic(y, currentOperation, x);
		if( fpError )
			{
			[self errorDisplay];
			return nil;
			}
		currentOperation = OP_NOOP;
		[self updateOperationMarker];
		}
	[self setDisplay];

	[self setScrollOperation: "=" 
		andNumber: x];

    return self;
}

- key_allClear:sender
{
	#ifdef DEBUG
		fprintf(stderr,"All Clear\n");	
	#endif
	
	x = y = 0.0;
	x_isNew = YES;
	x_hasDecimal = NO;
	currentOperation = OP_NOOP;
	[self updateOperationMarker];
	fpError = NO;
	[self setDisplay];


	[scrollText setSel: [scrollText textLength] 
		: [scrollText textLength]];
	[scrollText replaceSel: "\nAC\n"];
	[scrollText scrollSelToVisible];
	[scrollText hideCaret];
	[self makeFirstResponder: self];

    return self;
}

- key_power:sender
{
	if( fpError)
		{
		NXBeep();
		return self;
		}

	// alternate key pressed
	if( ([NXApp currentEvent]->flags) & SHIFTMASK)
		{
		[self setScrollOperation: "exp" andNumber: x];
		[self getDisplay];
		x = exp(x);
		[self setDisplay];
		[self setScrollOperation: "=" andNumber: x];
		}	
	else
		{
		[self processPrevious];
		currentOperation = OP_POWER;
		[self updateOperationMarker];
		}
		
    return self;
}

- key_negate:sender
{
	if( fpError || displayMode == DM_OCTAL || displayMode == DM_BINARY)
		{
		NXBeep();
		return self;
		}


	[self getDisplay];

	if( ([NXApp currentEvent]->flags) & SHIFTMASK)
		{
		[self setScrollOperation: "abs" andNumber: x];
		x = fabs(x);
		}
	else
		{
		[self setScrollOperation: "+/-" andNumber: x];
		x = -x;
		}
		
		
	[self setDisplay];
	[self setScrollOperation: "=" andNumber: x];
	return self;
}

- key_baseChanged: sender
{
	static char buf[30];
	
	switch( atoi( [[sender selectedCell]title]) )
		{
		case 2:	// Previous was base 2, so new is base 8
			displayMode = DM_OCTAL;
			[hexadecimalKeyMatrix setEnabled: NO];
			[[sender selectedCell] setTitle: "8"];
			
			[decimalKeyMatrix setEnabled: YES];
			[[decimalKeyMatrix findCellWithTag: 8] setEnabled: NO];
			[[decimalKeyMatrix findCellWithTag: 9] setEnabled: NO];
			[decimalPointKey setEnabled: NO];
			break;
		case 8:	// Previous was base 8, so new is base 10
			displayMode = DM_DECIMAL;
			[[sender selectedCell] setTitle: "10"];
			[hexadecimalKeyMatrix setEnabled: NO];
			[[decimalKeyMatrix findCellWithTag: 8] 
				setEnabled: YES];
			[[decimalKeyMatrix findCellWithTag: 9] setEnabled: 
				YES];
			[decimalPointKey setEnabled: YES];
			break;
		case 10: // Previous was base 10, so new is base 16
			displayMode = DM_HEXADECIMAL;
			[hexadecimalKeyMatrix setEnabled: YES];
			[[sender selectedCell] setTitle: "16"];
			[decimalPointKey setEnabled: NO];
			break;
		case 16: // Previous was base 16, so new is base 2			
			displayMode = DM_BINARY;
			[hexadecimalKeyMatrix setEnabled: NO];
			[[sender selectedCell] setTitle: "2"];

			[decimalKeyMatrix setEnabled: NO];
			[[decimalKeyMatrix findCellWithTag: 0] 
				setEnabled: YES];
			[[decimalKeyMatrix findCellWithTag: 1] 
				setEnabled: YES];

			[decimalPointKey setEnabled: NO];
			break;
		default:
			NXBeep();
			return nil;
		}
	[self setDisplay];

	sprintf(buf, "\nChange to base %d", displayMode );
	[scrollText setSel: [scrollText textLength] 
		: [scrollText textLength]];
	[scrollText replaceSel: buf];
	[self setScrollOperation: "=" andNumber: x];

	[self getDisplay];
	return self;
}



- setDisplay
{
	static char displayBuf[20];
	#ifdef DEBUG
		fprintf(stderr,"Display is set to %f.\n", x);
	#endif
	
	switch (displayMode )
		{
		case DM_BINARY:
			writeBinary(displayBuf, x);
			[display setStringValue: displayBuf];
			break;
		case DM_OCTAL:
			sprintf( displayBuf, "%o", (unsigned int) x);
			[display setStringValue: displayBuf];
			break;
		case DM_HEXADECIMAL:
			sprintf( displayBuf, "%x", (unsigned int) x);
			[display setStringValue: displayBuf];
			break;
		case DM_DECIMAL:
		default:
			[display	setDoubleValue: x];
			break;
		}
		
	x_isNew = YES;
	[self	updateMemoryMarker];
	[self makeFirstResponder: self];
	return self;
}

- clearDisplay
{
	[display	setStringValue: ""];
	x_hasDecimal = NO;
	x_isNew = YES;
	return self;
}

- errorDisplay
{
	[display	setStringValue: "Error"];
	fpError = YES;
	return self;
}

- errorDisplay: (char *) errorString
{
	static char buf[80];
	
	fpError = YES;
	sprintf(buf, "Error %s", errorString);
	[display	setStringValue: buf];

	
	sprintf(buf, "\nError %s", errorString);
	[scrollText setSel: [scrollText textLength] 
		: [scrollText textLength]];
	[scrollText replaceSel: buf];
	[scrollText scrollSelToVisible];
	[scrollText hideCaret];
	[self makeFirstResponder: self];

	return self;
}

- (double) getDisplay
{
	unsigned int utmp;
	
	if( !calcDidInit )
		return( x = 0.0);
		
	switch (displayMode)
		{
		case DM_BINARY:
			x = readBinary([display stringValue] );
			break; 
		case DM_OCTAL:
			sscanf([display stringValue], "%o", &utmp);
			x = utmp;
			break;
		case DM_HEXADECIMAL:
			sscanf([display stringValue], "%x", &utmp);
			x = utmp;
			break;
		case DM_DECIMAL:
		default:
			x = [display doubleValue];
			break;
		}
					
	return( x );
}

- updateMemoryMarker
{
	if( m == 0.0 )
		[memoryDisplay setStringValue: ""];
	else
		[memoryDisplay setStringValue: "M"];
	return self;
}

- updateOperationMarker
{
	const char* operation;
		
	switch( currentOperation )
		{
		case OP_ADD:
			operation = "+";
			break;
		case OP_SUBTRACT:
			operation = "-";
			break;
		case OP_MULTIPLY:
			operation = "x";
			break;
		case OP_DIVIDE:
			operation = "/";
			break;
		case OP_LOGICAL_OR:
			operation = "OR";
			break;
		case OP_LOGICAL_AND:
			operation = "AND";
			break;
		case OP_LOGICAL_EOR:
			operation = "EOR";
			break;
		case OP_LOGICAL_NOR:
			operation = "NOR";
			break;
		case OP_NOOP:
		default:
			operation = "  ";
			break;
		}
	[operationDisplay setStringValue: operation];
	return self;
}
			
		

// Delegate stuff to ensure proper miniaturization behaviour
- windowWillMiniaturize: sender
	toMiniwindow: mini
{
	[self	setTitle:	CALCULATOR_TITLE_SHORT];
	return self;
}

- windowDidUpdate: sender
{
	if( calcDidInit != YES)
		[self initCalc];
	[self	setMiniwindowIcon: CALCULATOR_ICON];
	[self makeFirstResponder: self];
	return self;
}
	
- windowDidDeminiaturize: sender
{
	[self	setTitle:	CALCULATOR_TITLE_LONG];
	[self makeFirstResponder: self];
	return self;
}


- processPrevious
{


	if( fpError)
		{
		NXBeep();
		return self;
		}

		
	if( currentOperation != OP_NOOP)
		{
		[self getDisplay];

		[self setScrollOperation: OperatorDesc[currentOperation] 
			andNumber: x];
		
		y = magic(y, currentOperation, x);
		if( fpError )
			{
			[self errorDisplay];
			return nil;
			}
			
		x = y;
		[self setDisplay];
		x_isNew = YES;

		}
	else
		{
		[self getDisplay];
		y = x;
		x_isNew = YES;
		}

	[self setScrollOperation: "=" andNumber: x];
	
	return self;
}	

- copy: sender
{
	static char pasteBuffer[80];
	const char *types[] = {NXAsciiPboardType,NULL};
	id pasteBoard;
	
	if( fpError)
		{
		NXBeep();
		return self;
		}

	strcpy( pasteBuffer, [display stringValue]);
	
	pasteBoard = [Pasteboard newName: NXGeneralPboard];
	
	[pasteBoard		declareTypes:	types
				num:		1
				owner:		NULL];
				
	[pasteBoard 		writeType: NXAsciiPboardType
				data:	 pasteBuffer
				length:	(int) strlen(pasteBuffer)];
				
	#ifdef DEBUG
		fprintf(stderr, "Copied <%s> to the Pasteboard.\n",
			pasteBuffer);
	#endif
	
	fprintf(stderr, "Copy!\n");
	return self;
}

- paste: sender
{
	static char *bufPt;
	int len;
	int tmp;
	const NXAtom *types;
	id pasteBoard;
	
	if( fpError)
		{
		NXBeep();
		return self;
		}
	
	pasteBoard = [Pasteboard newName: NXGeneralPboard];
	
	types = 	[pasteBoard	types]; 
	for( tmp = 0; types[tmp] != NULL; tmp ++ )
		if( strcmp( types[tmp], NXAsciiPboardType ) == 0 )
			{
			[pasteBoard 	readType: NXAsciiPboardType
				data:	 &bufPt
				length:	&len];		
			
			[display setStringValue: bufPt];
			[self getDisplay];
			[self setDisplay];
			[self setScrollOperation: "Paste: " andNumber: x];
			
			#ifdef DEBUG
				fprintf(stderr, "Pasted <%s> (%d) from "
					"the Pasteboard.\n", bufPt,
						len);
			#endif
			
			break;
			}		
	return self;
}

- setScrollOperation: (char *) op andNumber: (double) val
{
	static char buffer[30], buf2[30];

	switch (displayMode )
		{
		case DM_BINARY:
			writeBinary( buf2, val);
			sprintf(buffer, "\n%s %s", op, buf2);
			break;

		case DM_OCTAL:
			sprintf( buffer, "\n%s %o", 
				op, (unsigned int) val);
			break;
		case DM_HEXADECIMAL:
			sprintf( buffer, "\n%s %x", 		
				op, (unsigned int) val);
			break;
		case DM_DECIMAL:
		default:
			sprintf( buffer, "\n%s %g", 
				op, val);
			break;
		}

	[scrollText setSel: [scrollText textLength] 
		: [scrollText textLength]];
	[scrollText replaceSel: buffer];
	[scrollText hideCaret];
	[scrollText scrollSelToVisible];
	[self makeFirstResponder: self];

	return self;
}


- windowWillResize: (id) sender toSize: (NXSize *) size
{
	#ifdef DEBUG
		fprintf(stderr,"Window would have resized to %f x %f.\n", 
			size->width, size->height);
	#endif
	
	if( size->width < MIN_WIDTH)
		size->width = MIN_WIDTH;
	if( size->height < MIN_HEIGHT)
		size->height = MIN_HEIGHT;
		
	#ifdef DEBUG
		fprintf(stderr,"Window will resize to %f x %f.\n", 
			size->width, size->height);
	#endif
	return self;
}

- (BOOL)	acceptsFirstResponder	{	return YES; }

- printPSCode: sender	{ [scrollText  printPSCode: sender]; return self;}

// From CalculatorLab/MinusPanel.m
// When the user presses the minus sign on the keypad, the resulting character
// is number 45 from the Symbol character set, not a minus sign from the ASCII
// character set (which you would get by pressing the key to the right of the 
// zero key).  The Panel class ignores commandKeys from all sets except the 
// ASCII set, but we want the user to be able to depress the minus key on the
// keypad and have it act like the real minus key.  We can get around this
// problem by checking for this special minus sign and converting it into an
// ASCII minus sign before it reaches the regular commandKey: method
- (BOOL)commandKey:(NXEvent *)theEvent
{
    NXEvent localEvent;
    BOOL    symbolSet, minusSign;
    
    symbolSet = theEvent->data.key.charSet == NX_SYMBOLSET;
    minusSign = theEvent->data.key.charCode == 45;

    if (symbolSet && minusSign) {  /* check for minus */
	localEvent = *theEvent;
	localEvent.data.key.charSet = NX_ASCIISET;
	localEvent.data.key.charCode = '-';
	return [super commandKey:&localEvent];
    } else {
        return [super commandKey:theEvent];
    }
}


@end
