/*
 *	PDP-8/E Simulator
 *
 *	Copyright © 1994-2015 Bernhard Baehr
 *
 *	PDP8.m - The PDP-8/E Emulator Class
 *
 *	This file is part of PDP-8/E Simulator.
 *
 *	PDP-8/E Simulator is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#import <Cocoa/Cocoa.h>
#import <zlib.h>
#import <mach/mach_time.h>

#define USE_PDP8_REGISTERS_DIRECTLY	1

#import "PDP8.h"
#import "itab.h"
#import "eae.h"
#import "iot.h"
#import "opr.h"
#import "Breakpoint.h"
#import "BreakpointArray.h"
#import "Utilities.h"
#import "PluginAPI.h"
#import "NSThread+MainThread.h"


#define PDP8_PREFS_KEY		@"PDP-8/E"

#define CODER_KEY_HAS_EAE	@"EAE"
#define CODER_KEY_HAS_KM8E	@"KM8E"
#define CODER_KEY_MEMSIZE	@"MEMSIZE"
#define CODER_KEY_TIMESHARING	@"TIMESHARING"
#define CODER_KEY_SR		@"SR"
#define CODER_KEY_LAC		@"LAC"
#define CODER_KEY_PC		@"PC"
#define CODER_KEY_SC		@"SC"
#define CODER_KEY_MQ		@"MQ"
#define CODER_KEY_GTF		@"GTF"
#define CODER_KEY_IF		@"IF"
#define CODER_KEY_IB		@"IB"
#define CODER_KEY_DF		@"DF"
#define CODER_KEY_UF		@"UF"
#define CODER_KEY_UB		@"UB"
#define CODER_KEY_SF		@"SF"
#define CODER_KEY_ENABLE	@"ENABLE"
#define CODER_KEY_INHIBIT	@"INHIBIT"
#define CODER_KEY_DELAY		@"DELAY"
#define CODER_KEY_USERMASK	@"USERMASK"
#define CODER_KEY_USERFLAG	@"USERFLAG"
#define CODER_KEY_EAEMODE	@"EAEMODE"
#define CODER_KEY_MEMORY	@"MEMORY"


PDP8	*pdp8;	/* the one and only instance of the PDP8 class, only used by the opcode C functions */


@implementation PDP8


#pragma mark Hardware Configuration


- (void) mountEAEforced:(BOOL)mount	// mount or unmount anyway (used only on startup)
{
	static void (*eaetab[])(void) = {
		i7401, i7403, i7405, i7407, i7411, i7413, i7415, i7417,
		i7421, i7423, i7425, i7427, i7431, i7433, i7435, i7437,
		i7441, i7443, i7445, i7447, i7451, i7453, i7455, i7457,
		i7461, i7463, i7465, i7467, i7471, i7473, i7475, i7477,
		i7501, i7503, i7505, i7507, i7511, i7513, i7515, i7517,
		i7521, i7523, i7525, i7527, i7531, i7533, i7535, i7537,
		i7541, i7543, i7545, i7547, i7551, i7553, i7555, i7557,
		i7561, i7563, i7565, i7567, i7571, i7573, i7575, i7577,
		i7601, i7603, i7605, i7607, i7611, i7613, i7615, i7617,
		i7621, i7623, i7625, i7627, i7631, i7633, i7635, i7637,
		i7641, i7643, i7645, i7647, i7651, i7653, i7655, i7657,
		i7661, i7663, i7665, i7667, i7671, i7673, i7675, i7677,
		i7701, i7703, i7705, i7707, i7711, i7713, i7715, i7717,
		i7721, i7723, i7725, i7727, i7731, i7733, i7735, i7737,
		i7741, i7743, i7745, i7747, i7751, i7753, i7755, i7757,
		i7761, i7763, i7765, i7767, i7771, i7773, i7775, i7777
	};  
	static void (*noeaetab[])(void) = {
		i7401, i7421, i7401, i7421, i7501, i7521, i7501, i7521,
		i7601, i7621, i7601, i7621, i7701, i7721, i7701, i7721
	};
	
	int i, j;

	_hw.hasEAE = mount;
	NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];	
	if (mount) {
		for (i = 07401, j = 0; i < 010000; i += 2)
			itab[i] = eaetab[j++];
		for (i = 017401, j = 0; i < 020000; i += 2)
			itab[i] = eaetab[j++];
	} else {
		for (i = 07401, j = 0; i < 010000; i += 2)
			itab[i] = noeaetab[j++ >> 3];
		for (i = 017401, j = 0; i < 020000; i += 2)
			itab[i] = noeaetab[j++ >> 3];
		SC = 0;
		GTF = 0;
		eaeMode = EAE_MODE_A;
		if (_state.running != GOING) {
			[defaultCenter postNotificationName:SC_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:GTF_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:EAE_MODE_CHANGED_NOTIFICATION object:self];
		}
	}
	[defaultCenter postNotificationName:EAE_MOUNT_NOTIFICATION object:self];
}


- (void) mountEAE:(BOOL)mount	// mount only when needed (to avoid multiple executions due to notifications)
{
	if (_hw.hasEAE != mount)
		[self mountEAEforced:mount];;
}


- (BOOL) hasEAE
{
	return _hw.hasEAE;
}


- (void) mountKM8Eforced:(BOOL)mount memorySize:(unsigned)memsize timesharingEnabled:(BOOL)timesharing
// mount or unmount anyway (used only on startup)
{
	static const struct {
		ushort	addr;
		void	(*inst)(void);
	} km8etab[] = {
		{ 06201, i6201 }, { 06202, i6202 }, { 06203, i6203 }, { 06204, i6204 },
		{ 06211, i6211 }, { 06212, i6212 }, { 06213, i6213 }, { 06214, i6214 },
		{ 06221, i6221 }, { 06222, i6222 }, { 06223, i6223 }, { 06224, i6224 },
		{ 06231, i6231 }, { 06232, i6232 }, { 06233, i6233 }, { 06234, i6234 },
		{ 06241, i6241 }, { 06242, i6242 }, { 06243, i6243 }, { 06244, i6244 },
		{ 06251, i6251 }, { 06252, i6252 }, { 06253, i6253 }, { 06254, i6254 },
		{ 06261, i6261 }, { 06262, i6262 }, { 06263, i6263 }, { 06264, i6264 },
		{ 06271, i6271 }, { 06272, i6272 }, { 06273, i6273 }, { 06274, i6274 },
		{ 0, NULL }
	};

	int i;
	
	NSAssert ((memsize & ~0170000) == 0, @"Bad memory size");
	NSAssert ((mount && memsize > PDP8_FIELDSIZE) || memsize == PDP8_FIELDSIZE,
		@"Memory size does not match KM8-E state");
	NSAssert (mount || ! timesharing, @"Timesharing option does not match KM8-E state");

	_hw.hasKM8E = mount;
	_hw.hasKM8Etimesharing = timesharing;
	NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
	if (mount) {
		for (i = 0; km8etab[i].addr; i++)
			itab[km8etab[i].addr] = km8etab[i].inst;
		IMASK |= userFLAG;
	} else {
		for (i = 0; km8etab[i].addr; i++)
			itab[km8etab[i].addr] = i7000;
		DF = W_DF = 0;
		IF = IB = W_IB = 0;
		UF = UB = 0;
		SF = 0;
		IINHIBIT = 0;
		IMASK &= ~userFLAG;
		IOFLAGS &= ~userFLAG;
		if (_state.running != GOING) {
			[defaultCenter postNotificationName:DF_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:UF_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:UB_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:SF_CHANGED_NOTIFICATION object:self];
			[defaultCenter postNotificationName:INHIBIT_CHANGED_NOTIFICATION object:self];
		}
	}
	i = _hw.memsize = memsize;
	W_DF = (DF < _hw.memsize) ? DF : 0100000;
	W_IB = (IB < _hw.memsize) ? IB : 0100000;
	while (i < 0100000)
		mem[i++] = 0;
	if (_state.running != GOING) {
		[defaultCenter postNotificationName:IOFLAGS_CHANGED_NOTIFICATION object:self];
		[defaultCenter postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
	}
	[defaultCenter postNotificationName:KM8E_MOUNT_NOTIFICATION object:self];
}


- (void) mountKM8E:(BOOL)mount memorySize:(unsigned)memsize timesharingEnabled:(BOOL)timesharing
// mount only when needed (to avoid multiple executions due to notifications)
{
	if (_hw.hasKM8E != mount || _hw.hasKM8Etimesharing != timesharing || _hw.memsize != memsize)
		[self mountKM8Eforced:mount memorySize:memsize timesharingEnabled:timesharing];
}


- (BOOL) hasKM8E
{
	return _hw.hasKM8E;
}


- (BOOL) isTimesharingEnabled
{
	return _hw.hasKM8Etimesharing;
}


- (ushort) memorySize
{
	return _hw.memsize;
}


- (BOOL) isIOAddressAvailable:(int)addr
{
	int i;
	
	NSAssert1 ((addr & ~077) == 0, @"Bad I/O address %o", addr);
	addr = 06000 | (addr << 3);
	for (i = 0; i < 8; i++) {
		if (itab[addr | i] != i7000)
			return NO;
	}
	return YES;
}


- (void) setIOT:(NSValue *)iot forOpcode:(int)opcode
{
	NSAssert1 ((opcode & ~0777) == 06000, @"Invalid IOT %o", opcode);
	if (iot) {
		PDP8InstructionFunctionPointer p = [iot pointerValue];
		if (p)
			itab[opcode] = p;
	}
}


- (void) setPluginPointer:(void *)pointer forIOAddress:(int)addr
{
	NSAssert1 ((addr & ~077) == 0, @"Bad I/O address %o", addr);
	_state.pluginPointer[addr] = pointer;
}


#if ! defined(NS_BLOCK_ASSERTIONS)

- (id) pluginPointer:(Class)pluginClass;
{
	id pluginPointer = _state.pluginPointer[(_state.currInst >> 3) & 077];
	NSAssert (pluginPointer && [pluginPointer isMemberOfClass:pluginClass], @"Bad plugin pointer");
	return pluginPointer;
}

#endif


#pragma mark Initialization


- (void) notifyApplicationWillFinishLaunching:(NSNotification *)notification
{
	// NSLog (@"PDP8 notifyApplicationWillFinishLaunching");
	NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:PDP8_PREFS_KEY];
	if (data) {
		NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
		[self initWithCoder:unarchiver];
		[unarchiver finishDecoding];
		[unarchiver release];
	} else {
		[self mountEAEforced:NO];
		[self mountKM8Eforced:NO memorySize:PDP8_FIELDSIZE timesharingEnabled:NO];
		[self reset];
	}
}


- (void) notifyApplicationWillTerminate:(NSNotification *)notification
{
	// NSLog (@"PDP8 notifyApplicationWillTerminate");
	NSMutableData *data = [NSMutableData data];
	NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
	[self encodeWithCoder:archiver];
	[archiver finishEncoding];
	[archiver release];
	[[NSUserDefaults standardUserDefaults] setObject:data forKey:PDP8_PREFS_KEY];
}


- (void) awakeFromNib
{
	pdp8 = self;
	[[NSNotificationCenter defaultCenter] addObserver:self
						 selector:@selector(notifyApplicationWillFinishLaunching:)
						     name:NSApplicationWillFinishLaunchingNotification object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
						 selector:@selector(notifyApplicationWillTerminate:)
						     name:NSApplicationWillTerminateNotification object:nil]; 
}


- (id) initWithCoder:(NSCoder *)coder
{
	self = [super init];
	// hardware configuration - note: redundant to prefs settings
	[self mountEAEforced:[coder decodeBoolForKey:CODER_KEY_HAS_EAE]];
	[self mountKM8Eforced:[coder decodeBoolForKey:CODER_KEY_HAS_KM8E]
		   memorySize:[coder decodeIntForKey:CODER_KEY_MEMSIZE]
	   timesharingEnabled:[coder decodeBoolForKey:CODER_KEY_TIMESHARING]];
	// registers
	[self setSR:[coder decodeIntForKey:CODER_KEY_SR]];
	[self setLAC:[coder decodeIntForKey:CODER_KEY_LAC]];
	[self setPC:[coder decodeIntForKey:CODER_KEY_PC]];
	[self setSC:[coder decodeIntForKey:CODER_KEY_SC]];
	[self setMQ:[coder decodeIntForKey:CODER_KEY_MQ]];
	[self setGTF:[coder decodeIntForKey:CODER_KEY_GTF]];
	[self setIF:[coder decodeIntForKey:CODER_KEY_IF]];
	[self setIB:[coder decodeIntForKey:CODER_KEY_IB]];
	[self setDF:[coder decodeIntForKey:CODER_KEY_DF]];
	[self setUF:[coder decodeIntForKey:CODER_KEY_UF]];
	[self setUB:[coder decodeIntForKey:CODER_KEY_UB]];
	[self setSF:[coder decodeIntForKey:CODER_KEY_SF]];
	[self setEnable:[coder decodeIntForKey:CODER_KEY_ENABLE]];
	[self setInhibit:[coder decodeIntForKey:CODER_KEY_INHIBIT]];
	[self setDelay:[coder decodeIntForKey:CODER_KEY_DELAY]];
	[coder decodeBoolForKey:CODER_KEY_USERMASK] ?
	[self setInterruptMaskBits:userFLAG] : [self clearInterruptMaskBits:userFLAG];
	[coder decodeBoolForKey:CODER_KEY_USERFLAG] ?
	[self setIOFlagBits:userFLAG] : [self clearIOFlagBits:userFLAG];
	[self setEAEmode:[coder decodeIntForKey:CODER_KEY_EAEMODE]];
	// memory
	NSData *data = [coder decodeObjectForKey:CODER_KEY_MEMORY];
	unsigned long memsize = _hw.memsize * sizeof(mem[0]);
	int err = uncompress((void *) mem, &memsize, [data bytes], [data length]);
	if (err)
		NSLog (@"PDP8 decodeWithCoder memory uncompress error %d", err);
	return self;
}


- (void) encodeWithCoder:(NSCoder *)coder
{
	// hardware configuration - note: redundant to prefs settings
	[coder encodeBool:[self hasEAE] forKey:CODER_KEY_HAS_EAE];
	[coder encodeBool:[self hasKM8E] forKey:CODER_KEY_HAS_KM8E];
	[coder encodeInt:[self memorySize] forKey:CODER_KEY_MEMSIZE];
	[coder encodeBool:[self isTimesharingEnabled] forKey:CODER_KEY_TIMESHARING];
	// registers
	[coder encodeInt:[self getSR] forKey:CODER_KEY_SR];
	[coder encodeInt:[self getLAC] forKey:CODER_KEY_LAC];
	[coder encodeInt:[self getPC] forKey:CODER_KEY_PC];
	[coder encodeInt:[self getSC] forKey:CODER_KEY_SC];
	[coder encodeInt:[self getMQ] forKey:CODER_KEY_MQ];
	[coder encodeInt:[self getGTF] forKey:CODER_KEY_GTF];
	[coder encodeInt:[self getIF] forKey:CODER_KEY_IF];
	[coder encodeInt:[self getIB] forKey:CODER_KEY_IB];
	[coder encodeInt:[self getDF] forKey:CODER_KEY_DF];
	[coder encodeInt:[self getUF] forKey:CODER_KEY_UF];
	[coder encodeInt:[self getUB] forKey:CODER_KEY_UB];
	[coder encodeInt:[self getSF] forKey:CODER_KEY_SF];
	[coder encodeInt:[self getEnable] forKey:CODER_KEY_ENABLE];
	[coder encodeInt:[self getInhibit] forKey:CODER_KEY_INHIBIT];
	[coder encodeInt:[self getDelay] forKey:CODER_KEY_DELAY];
	[coder encodeBool:[self getInterruptMaskBits:userFLAG] ? YES : NO forKey:CODER_KEY_USERMASK];
	[coder encodeBool:[self getIOFlagBits:userFLAG] ? YES : NO forKey:CODER_KEY_USERFLAG];
	[coder encodeInt:[self getEAEmode] forKey:CODER_KEY_EAEMODE];
	// memory
	unsigned long datasize = compressBound(_hw.memsize * sizeof(mem[0]));
	NSMutableData *data = [NSMutableData dataWithLength:datasize];
	int err = compress2([data mutableBytes], &datasize, (void *) mem,
			    _hw.memsize * sizeof(mem[0]), Z_BEST_COMPRESSION);
	if (err)
		NSLog (@"PDP8 encodeWithCoder memory compress2 error %d", err);
	[data setLength:datasize];
	[coder encodeObject:data forKey:CODER_KEY_MEMORY];
}


#pragma mark Utilities


- (NSString *) loadPaperTape:(NSString *)filename toField:(ushort)field
/* this code is based on a very careful analysis of the PDP-8 BIN loader */
{
	ushort c, x, oldchecksum;
	
	NSAssert1 ((field & ~07) == 0, @"Bad memory field 0%o", field);
	x = oldchecksum = 0;	/* to avoid compiler "used uninitialized" warning */
	field <<= 12;
	if (field >= _hw.memsize)
		return NSLocalizedString(@"Cannot load code to non-existing memory field.", @"");
	FILE *fp = fopen([filename cStringUsingEncoding:NSASCIIStringEncoding], "r");	
	if (! fp)
		return NSLocalizedString(@"Cannot open the paper tape for reading.", @"");
	ushort origin = 0;
	ushort checksum = 0;
	BOOL isRimTape = TRUE;
	BOOL lastWordWasOrigin = FALSE;
	for (c = 0200; c == 0200 && ! feof(fp); c = getc(fp))		/* leader */
		;
	while (c != 0200 && ! feof(fp)) {
		if ((c & 0300) == 0300) {		/* field setting */
			if (c != 0377) {		/* all holes punched are ignored */
				isRimTape = FALSE;	/* RIM cannot have field setting */
				field = ((ushort) (c & 070)) << 9;
				if (field >= _hw.memsize)
					field = 0100000u;
				/* field setting are not included in the checksum */
			}
			c = getc(fp);
		} else if ((c & 0300) == 0100) {	/* origin */
			lastWordWasOrigin = TRUE;
			origin = (c & 077) << 6;
			oldchecksum = checksum;
			checksum += c;
			c = getc(fp);
			origin += c;
			checksum += c;
			c = getc(fp);
		} else {				/* value to deposit in memory */
			if (! lastWordWasOrigin)
				isRimTape = FALSE;
			lastWordWasOrigin = FALSE;
			x = (c & 077) << 6;
			oldchecksum = checksum;
			checksum += c;
			c = getc(fp);
			x += c;
			checksum += c;
			c = getc(fp);
			if ((c != 0200 && ! feof(fp)) || isRimTape) {
				mem[field | origin] = x;
				origin = (origin + 1) & 07777;
			}
		}
	}
	[[NSNotificationCenter defaultCenter]
		postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
	while (c == 0200 && ! feof(fp))
		c = getc(fp);
	if (! feof(fp)) {
		fclose (fp);
		return NSLocalizedString(@"There is something wrong with the paper tape, "
			@"maybe garbage before leader or after trailer.", @"");
	}
	fclose (fp);
	if (! isRimTape && (oldchecksum & 07777) != (lastWordWasOrigin ? origin : x))
		return NSLocalizedString(@"The paper tape contains a checksum error.", @"");
	return nil;
}


- (void) notifyEverythingChanged
{
	NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
	
	[defaultCenter postNotificationName:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:ACCUMULATOR_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:SR_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:SC_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:GTF_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:MQ_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:EAE_MODE_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:DF_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:UF_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:UB_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:SF_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:ENABLE_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:DELAY_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:INHIBIT_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:IOFLAGS_CHANGED_NOTIFICATION object:self];
	[defaultCenter postNotificationName:MEMORY_CHANGED_NOTIFICATION object:self];
}


- (void) clearAllFlags
{
	[self setLAC:0];
	[self setEnable:0];
	[self setInhibit:0];
	[self setDelay:0];
	[self clearInterruptMaskBits:~userFLAG];
	[self clearIOFlagBits:~0];
	[self setEAEmode:EAE_MODE_A];
	[self setGTF:0];
	int i;
	for (i = 0; i < PDP8_IOADDRS; i++) {
		if (_state.pluginPointer[i])
			[(PDP8Plugin *) _state.pluginPointer[i] clearAllFlags:i];
	}
	[[NSNotificationCenter defaultCenter] postNotificationName:CLEAR_ALL_FLAGS_NOTIFICATION object:self];
}


- (void) loadExtendedAddress
{
	if (_hw.hasKM8E) {
		[self setIB:(SR & 070) >> 3];
		[self setIF:(SR & 070) >> 3];
		[self setDF:SR & 007];
		[self setUB:0];
		[self setUF:0];
		[self clearIOFlagBits:userFLAG];
	}
}


- (void) reset
{
	[self setProgramCounter:0200];
	[self setLAC:0];
	[self setSR:0];
	[self setSC:0];
	[self setMQ:0];
	[self setGTF:0];
	[self setDF:0];
	[self setUF:0];
	[self setUB:0];
	[self setSF:0];
	[self setEAEmode:EAE_MODE_A];
	[self setEnable:0];
	[self setDelay:0];
	[self setInhibit:0];
	[self clearInterruptMaskBits:~userFLAG];
	[self clearIOFlagBits:~0];
	[self clearMemory];
}


#pragma mark Step - Trace - Go


- (void) setTraceSpeed:(double)speed
{
	_state.usecTraceDelay = (unsigned) (speed * 1000000.0);
}


- (void) setGoSpeed:(int)goSpeed
{
	NSAssert (goSpeed == GO_AS_FAST_AS_POSSIBLE || goSpeed == GO_WITH_PDP8_SPEED ||
		goSpeed == GO_WITH_PDP8_SPEED_PRECISE, @"Illegal go speed specififed");
	/* reset the timer - required when the PDP-8 runs while this method is called */
	_state.executionTime = 0;
	_state.absoluteTime = mach_absolute_time();
	_state.goSpeed = goSpeed;
}


- (int) getGoSpeed
{
	return _state.goSpeed;
}


- (BOOL) isStopped
{
	return _state.running == STOPPED;
}


- (BOOL) isTracing
{
	return _state.running == TRACING;
}


- (BOOL) isGoing;
{
	return _state.running == GOING;
}


- (BOOL) isRunning
{
	return _state.running != STOPPED;
}


- (BOOL) isHalted
{
	return _state.halted;
}


- (void) setHalt:(BOOL)halt
{
	if (halt)
		[self stop];
	_state.halted = halt;
}


static void breakInstruction (void)		/* used for break opcodes */
{
	pdp8->_state.running = STOPPED;
	pdp8->PC--;
}


- (void) setupBreakpoints:(int)stopAddress
{
	NSEnumerator	*enumerator;
	Breakpoint	*next;
	
	NSAssert (stopAddress == -1 || (stopAddress & ~077777) == 0, @"Invalid stop address");

	// breakpoints
	enumerator = [breakpoints enumerator];
	bzero (bp, sizeof(bp));
	while ((next = (Breakpoint *)[enumerator nextObject]))
		bp[[next identifier]] = [next value];
	if (stopAddress >= 0)
		bp[stopAddress] = BREAKPOINT;

	// break opcodes
	NSAssert (saveopcodes == nil, @"saveopcodes not nil");
	saveopcodes = [[NSMutableDictionary alloc] init];
	enumerator = [breakopcodes enumerator];
	while ((next = (Breakpoint *)[enumerator nextObject])) {
		unsigned opcode = [next identifier];
		if ([next value] & USERMODE_BREAKOPCODE) {
			[saveopcodes setObject:[NSValue valueWithPointer:itab[opcode]]
				forKey:[NSNumber numberWithInt:opcode]];
			itab[opcode] = breakInstruction;
		}
		if ([next value] & SYSTEMMODE_BREAKOPCODE) {
			opcode |= 010000;
			[saveopcodes setObject:[NSValue valueWithPointer:itab[opcode]]
				forKey:[NSNumber numberWithInt:opcode]];
			itab[opcode] = breakInstruction;
		}
	}
}


- (void) resetBreakpoints
{
	NSNumber *next;
	
	NSEnumerator *enumerator = [saveopcodes keyEnumerator];	
	while ((next = (NSNumber *)[enumerator nextObject]))
		itab[[next intValue]] = [[saveopcodes objectForKey:next] pointerValue];
	[saveopcodes release];
	saveopcodes = nil;
}


- (void) sendStopNotification		// must run on main thread
{
	NSAssertRunningOnMainThread ();
	[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_STOP_NOTIFICATION object:self];
	[self notifyEverythingChanged];
}


- (void) oneRunLoopPass
{
	NSAssertRunningOnMainThread ();
	[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}


- (void) pdp8Step
{
	/* Check for interrupts */
	if (IENABLE && (IOFLAGS & IMASK) && ! (IDELAY || IINHIBIT)) {
		mem[0] = PC;
		PC = 1;
		SF = (UF >> 6) | (IF >> 9) | (DF >> 12);
		W_IB = IB = IF = W_DF = DF = UB = UF = IENABLE = 0;
		EXECUTION_TIME (14);	/* count the execute cycle of JMS 0 */
	} else {
		IDELAY = 0;
		/* Fetch and execute an instruction */
		itab[UF | (_state.currInst = mem[IF | PC])]();
		/* Update the program counter */
		if (++PC & 010000)
			PC &= 07777;
	}
}


- (void) sendStepNotifications
{
	NSAssertRunningOnMainThread ();
	[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_STEP_NOTIFICATION object:self];
	[self notifyEverythingChanged];
}

	
- (void) step
{
	NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
	[self pdp8Step];
	[self sendStepNotifications];
}


- (void) traceThread:(NSNumber *)stopAddress
{
	struct timeval tv0, tv1;
	struct timezone tz;
	
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	BOOL installBreakpoints = TRUE;
	[NSThread setThreadPriority:0];
	// low priority for the trace thread, so the main thread can process user interactions
	_state.running = TRACING;
	while (_state.running) {
		gettimeofday (&tv0, &tz);
		[self pdp8Step];
		[self performSelectorOnMainThread:@selector(sendStepNotifications)
			withObject:nil waitUntilDone:YES];
		if (installBreakpoints) {	// one step without breakpoints
			[self setupBreakpoints:[stopAddress intValue]];
			installBreakpoints = FALSE;
		}
		if (bp[IF | PC])
			_state.running = STOPPED;
		else if (_state.running) {
			/* Give the main thread the chance to handle events. When tracing with full
			   speed (state.usecTraceDelay == 0), on dual core machines, the trace thread
			   calls the step method so fast on the main thread that it can't handle user
			   events. */
			[self performSelectorOnMainThread:@selector(oneRunLoopPass)
				withObject:nil waitUntilDone:YES];
			gettimeofday (&tv1, &tz);
			timersub (&tv1, &tv0, &tv0);
			if (_state.usecTraceDelay > (ulong) tv0.tv_usec)
				usleep (_state.usecTraceDelay - tv0.tv_usec);
		}
	}
	[self resetBreakpoints];
	[self performSelectorOnMainThread:@selector(sendStopNotification)
		withObject:nil waitUntilDone:YES];
	[pool release];
}


- (void) trace:(int)stopAddress
{
	NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
	NSAssert (! _state.halted, @"PDP8 trace while halted");
	if (_state.halted)
		[self step];
	else {
		[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_TRACE_NOTIFICATION object:self];
		[NSThread detachNewThreadSelector:@selector(traceThread:) toTarget:self
			withObject:[NSNumber numberWithInt:stopAddress]];
	}
}


- (void) goThread:(id)object
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	// _state.running = GOING;		// already set, see comment below in the go method
	_state.executionTime = 0;
	_state.absoluteTime = mach_absolute_time();
	while (_state.running) {
		[self pdp8Step];
		/* Check for breakpoints */
		if (bp[IF | PC])
			_state.running = STOPPED;
		/* Realtime speed */
		if (_state.goSpeed != GO_AS_FAST_AS_POSSIBLE) {
			uint64_t delayUntilAbsolute = _state.absoluteTime +
				nanoseconds2absolute(_state.executionTime * 100l);
			if (_state.goSpeed == GO_WITH_PDP8_SPEED_PRECISE) {
				// precise timing: delay after each PDP-8 instruction using busy waiting
				// this delay takes about 0.1 microseconds longer than required
				// (precision in the order of 0.1 PDP-8 instructions)
				while (mach_absolute_time() < delayUntilAbsolute)
					;
			} else if (mach_absolute_time() + nanoseconds2absolute(15000l) < delayUntilAbsolute) {
				// PDP-8 speed without precise timing: delay when a forerun of at least
				// 15 microseconds has accumulated (to avoid high kernel load on slower Macs)
				// mach_wait_until() delays about 10 microseconds longer than required
				// (precision in the order of about some dozen PDP-8 instructions)
				mach_wait_until (delayUntilAbsolute);
			}
			// consider the time lag due to too long delays for the next loop
			_state.absoluteTime = delayUntilAbsolute;
			_state.executionTime = 0;
		}
	}
	[self resetBreakpoints];
	[self performSelectorOnMainThread:@selector(sendStopNotification)
		withObject:nil waitUntilDone:YES];
	[pool release];
}


- (void) go:(int)stopAddress
{
	NSAssert (_state.running == STOPPED, @"PDP-8 not stopped");
	NSAssert (! _state.halted, @"PDP-8 trace while halted");
	if (_state.halted)
		[self step];
	else {
		/* set GOING right now, otherwise [self pdp8Step] might post GUI notifications
		   (e. g. IOFLAGS_CHANGED_NOTIFICATION via the CAF instruction) that are processed later
		   when the CPU is already running, and that can cause a crash, e. g. with the skip
		   indicator update */
		_state.running = GOING;
		[self pdp8Step];	// one step without breakpoints and without GUI notifications
		[self setupBreakpoints:stopAddress];
		[[NSNotificationCenter defaultCenter] postNotificationName:PDP8_GO_NOTIFICATION object:self];
		[NSThread detachNewThreadSelector:@selector(goThread:) toTarget:self withObject:nil];
	}
}


- (void) stop
{
	_state.running = STOPPED;
}


#pragma mark Register Access


#define NOTIFY(notification)	if (_state.running != GOING && [NSThread isMainThread]) {		\
					[[NSNotificationCenter defaultCenter]				\
						postNotificationName:(notification) object:self];	\
				}


- (ushort) getPC
{
	return PC;
}


- (void) setPC:(ushort)pc
{
	NSAssert1 ((pc & ~07777) == 0, @"Bad PC: 0%o", pc);
	PC = pc;
	NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}


- (ushort) getProgramCounter
{
	return IF | PC;
}


- (void) setProgramCounter:(ushort)programCounter
{
	NSAssert1 ((programCounter & ~077777) == 0, @"Bad IF|PC: 0%o", programCounter);
	NSAssert1 (programCounter < 010000 || _hw.hasKM8E, @"Can't set IF|PC to field %o without KM8-E",
		programCounter >> 12);
	IB = IF = programCounter & 070000;
	PC = programCounter & 007777;
	NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}


- (ushort) getIF
{
	return IF >> 12;
}


- (void) setIF:(ushort)_if
{
	NSAssert1 ((_if & ~07) == 0, @"Bad IF: 0%o", _if);
	IF = _if << 12;
	NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}	


- (ushort) getIB
{
	return IB >> 12;
}


- (void) setIB:(ushort)ib
{
	NSAssert1 ((ib & ~07) == 0, @"Bad IB: 0%o", ib);
	W_IB = IB = ib << 12;
	if (W_IB >= _hw.memsize)
		W_IB = 0100000;
	NOTIFY (PROGRAM_COUNTER_CHANGED_NOTIFICATION);
}	


- (ushort) getDF
{
	return DF >> 12;
}


- (void) setDF:(ushort)df
{
	NSAssert1 ((df & ~07) == 0, @"Bad DF: 0%o", df);
	W_DF = DF = df << 12;
	if (W_DF >= _hw.memsize)
		W_DF = 0100000;
	NOTIFY (DF_CHANGED_NOTIFICATION);
}	


- (ushort) getUF
{
	return UF >> 12;
}


- (void) setUF:(ushort)uf
{
	NSAssert1 ((uf & ~01) == 0, @"Bad UF: 0%o", uf);
	UF = uf << 12;
	NOTIFY (UF_CHANGED_NOTIFICATION);
}	


- (ushort) getUB
{
	return UB >> 12;
}


- (void) setUB:(ushort)ub
{
	NSAssert1 ((ub & ~01) == 0, @"Bad UB: %o", ub);
	UB = ub << 12;
	NOTIFY (UB_CHANGED_NOTIFICATION);
}	


- (ushort) getSF
{
	return SF;
}


- (void) setSF:(ushort)sf
{
	NSAssert1 ((sf & ~0177) == 0, @"Bad SF: 0%o", sf);
	SF = sf;
	NOTIFY (SF_CHANGED_NOTIFICATION);
}	


- (ushort) getSR
{
	return SR;
}


- (void) setSR:(ushort)sr
{
	NSAssert1 ((sr & ~077777) == 0, @"Bad SR: 0%o", sr);
	SR = sr;
	NOTIFY (SR_CHANGED_NOTIFICATION);
}


- (ushort) getL
{
	return AC >> 12;
}


- (void) setL:(ushort)l
{
	NSAssert1 ((l & ~01) == 0, @"Bad Link: 0%o", l);
	AC = (l << 12) | (AC & 07777);
	NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);
}


- (ushort) getAC
{
	return AC & 07777;
}


- (void) setAC:(ushort)ac
{
	NSAssert1 ((ac & ~07777) == 0, @"Bad AC: 0%o", ac);
	AC = (AC & 010000) | ac;
	NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);
}


- (ushort) getLAC
{
	return AC;
}


- (void) setLAC:(ushort)lac
{
	NSAssert1 ((lac & ~017777) == 0, @"Bad L|AC: 0%o", lac);
	AC = lac;
	NOTIFY (ACCUMULATOR_CHANGED_NOTIFICATION);	
}


- (ushort) getSC
{
	return SC;
}


- (void) setSC:(ushort)sc
{
	NSAssert1 ((sc & ~037) == 0, @"Bad SC: 0%o", sc);
	SC = sc;
	NOTIFY (SC_CHANGED_NOTIFICATION);
}

	
- (ushort) getGTF
{
	return GTF;
}


- (void) setGTF:(ushort)gtf
{
	NSAssert1 ((gtf & ~01) == 0, @"Bad GTF: 0%o", gtf);
	GTF = gtf;
	NOTIFY (GTF_CHANGED_NOTIFICATION);
}


- (ushort) getMQ
{
	return MQ;
}


- (void) setMQ:(ushort)mq
{
	NSAssert1 ((mq & ~07777) == 0, @"Bad MQ: 0%o", mq);
	MQ = mq;
	NOTIFY (MQ_CHANGED_NOTIFICATION);
}


- (char) getEAEmode
{
	return eaeMode;
}


- (void) setEAEmode:(char)mode
{
	NSAssert1 (mode == 'A' || mode == 'B', @"Bad EAE mode: %c", mode);
	eaeMode = mode;
	NOTIFY (EAE_MODE_CHANGED_NOTIFICATION);
}


- (ushort) getEnable
{
	return IENABLE;
}


- (void) setEnable:(ushort)enable
{
	NSAssert1 ((enable & ~01) == 0, @"Bad Interrupt Enable Flag: 0%o", enable);
	IENABLE = enable;
	NOTIFY (ENABLE_CHANGED_NOTIFICATION);
}


- (ushort) getDelay
{
	return IDELAY;
}


- (void) setDelay:(ushort)delay
{
	NSAssert1 ((delay & ~01) == 0, @"Bad Interrupt Delay Flag: 0%o", delay);
	IDELAY = delay;
	NOTIFY (DELAY_CHANGED_NOTIFICATION);
}


- (ushort) getInhibit
{
	return IINHIBIT;
}


- (void) setInhibit:(ushort)inhibit
{
	NSAssert1 ((inhibit & ~01) == 0, @"Bad Interrupt Inhibit Flag: 0%o", inhibit);
	IINHIBIT = inhibit;
	NOTIFY (INHIBIT_CHANGED_NOTIFICATION);
}


- (ulong) getInterruptMaskBits:(ulong)bitmask
{
	return IMASK & bitmask;
}


- (void) setInterruptMaskBits:(ulong)bitmask
{
	IMASK |= bitmask;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}


- (void) clearInterruptMaskBits:(ulong)bitmask
{
	IMASK &= ~bitmask;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}


- (ulong) getIOFlagBits:(ulong)bitmask
{
	return IOFLAGS & bitmask;
}


- (void) setIOFlagBits:(ulong)bitmask
{
	IOFLAGS |= bitmask;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}


- (void) clearIOFlagBits:(ulong)bitmask
{
	IOFLAGS &= ~bitmask;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);
}


- (BOOL) interruptRequest
{
	return (IOFLAGS & IMASK) ? YES : NO;
}


#pragma mark Memory Access


- (ushort) memoryAt:(int)address
{
	NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
	NSAssert2 ((mem[address] & ~07777) == 0,
		@"Bad memory content: pdp8.mem[%5.5o] = 0%o", address, mem[address]);
	return mem[address];
}


- (ushort) memoryAtNext:(int)address
{
	NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
	NSAssert2 ((mem[(address & 070000) | ((address + 1) &  07777)] & ~07777) == 0,
		@"Bad memory content: pdp8.mem[%5.5o] = 0%o",
		(address & 070000) | ((address + 1) &  07777),
		mem[(address & 070000) | ((address + 1) &  07777)]);
	return mem[(address & 070000) | ((address + 1) &  07777)];
}


- (ushort *) directMemoryAccess
{
	return mem;
}


- (void) notifyMemoryChanged
{
	NSAssertRunningOnMainThread ();
	NSAssert (_state.running != GOING, @"PDP-8 is running");
	[[NSNotificationCenter defaultCenter] postNotificationName:(MEMORY_CHANGED_NOTIFICATION) object:self];
}


- (void) directMemoryWriteFinished
{
	if (_state.running != GOING)
		[self performSelectorOnMainThread:@selector(notifyMemoryChanged)
			withObject:nil waitUntilDone:YES];
}


- (void) setMemoryAtAddress:(int)address toValue:(int)value
{
	NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
	NSAssert1 (address < _hw.memsize, @"Address out of available memory: 0%o", address);
	NSAssert2 ((value & ~07777) == 0, @"Bad value 0%o for pdp8.mem[%5.5o]", value, address);	
	mem[address] = value;
	NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}


- (void) setMemoryAtNextAddress:(int)address toValue:(int)value
{
	NSAssert1 ((address & ~077777) == 0, @"Bad address: 0%o", address);
	NSAssert2 ((value & ~07777) == 0, @"Bad value 0%o for pdp8.mem[%5.5o]",
		value, (address & 070000) | ((address + 1) &  07777));	
	NSAssert1 (address < _hw.memsize, @"Address out of available memory: 0%o", address);
	mem[(address & 070000) | ((address + 1) &  07777)] = value;
	NOTIFY (MEMORY_CHANGED_NOTIFICATION);
}


- (void) setMemoryAtAddress:(int)address toValues:(NSArray *)values withMask:(BOOL)withMask
{
	int i;
	
	int count = [values count];
	NSAssert (values, @"values is nil");
	NSAssert1 ((address & ~077777) == 0, @"Bad start address: 0%o", address);
	NSAssert1 (((address + count - 1) & ~077777) == 0, @"Bad end address: 0%o", address + count - 1);
	NSAssert1 ((address + count - 1) < _hw.memsize,
		@"End address out of available memory: 0%o", address);
	for (i = 0; i < count; i++) {
		int value = [[values objectAtIndex:i] intValue];
		NSAssert2 ((value & ~(withMask ? 077777777 : 07777)) == 0,
			@"Bad mask/value 0%o for pdp8.mem[%5.5o]", value, address + i);
		int mask = withMask ? (value >> 12) : 07777;
		mem[address + i] = (mem[address + i] & ~mask) | (value & mask);
	}
	NOTIFY (MEMORY_CHANGED_NOTIFICATION);	
}


- (void) clearMemory
{
	bzero (mem, sizeof(mem));
	NOTIFY (MEMORY_CHANGED_NOTIFICATION);	
}


- (ushort) getCurrentOpcode
{
	return _state.currInst;
}


- (PDP8InstructionFunctionPointer) getNextInstruction
{
	NSAssert (_state.running != GOING, @"PDP-8 is running");
	_state.currInst = mem[IF | PC];		// the skiptest function may use the PLUGIN_POINTER() macro
	return itab[UF | mem[IF | PC]];
}


#pragma mark TSC8-75


- (ushort) getTSC8ertb
{
	return _tsc8.ertb;
}


- (void) setTSC8ertb:(ushort)ertb
{
	NSAssert1 ((ertb & ~07777) == 0, @"Bad ERTB: %o", ertb);
	_tsc8.ertb = ertb;
}


- (ushort) getTSC8eriot
{
	return _tsc8.eriot;
}


- (void) setTSC8eriot:(ushort)eriot
{
	NSAssert1 ((eriot & ~07777) == 0, @"Bad ERIOT: %o", eriot);
	_tsc8.eriot = eriot;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);		// ESME might change its skip behaviour on new ERIOT
}


- (ushort) getTSC8ecdf
{
	return _tsc8.ecdf;
}


- (void) setTSC8ecdf:(ushort)ecdf
{
	NSAssert1 ((ecdf & ~01) == 0, @"Bad ECDF: %o", ecdf);
	_tsc8.ecdf = ecdf;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);		// ESME might change its skip behaviour on new ECDF
}

- (ulong) getTSC8flag
{
	return _tsc8.flag;
}


- (void) setTSC8flag:(ulong)flag
{
	_tsc8.flag = flag;
}


- (BOOL) getTSC8esmeEnabled
{
	return _tsc8.esmeEnabled;
}


- (void) setTSC8esmeEnabled:(BOOL)enabled
{
	_tsc8.esmeEnabled = enabled;
	NOTIFY (IOFLAGS_CHANGED_NOTIFICATION);		// ESME might change its skip behaviour when enabled
}


@end
