/*
 *	PDP-8/E Simulator
 *
 *	Copyright © 1994-2015 Bernhard Baehr
 *
 *	KC8EA.m - KC8-EA Programmer’s Console for the PDP-8/E Simulator
 *
 *	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 "PluginFramework/PluginAPI.h"
#import "PluginFramework/PDP8.h"
#import "PluginFramework/KeepInMenuWindow.h"
#import "PluginFramework/Utilities.h"

#import "KC8EA.h"
#import "StateMachine.h"
#import "BackgroundView.h"


// notifications
#define KC8EA_UPDATE_DISPLAY_NOTIFICATION	@"kc8eaUpdateDisplayNotification"
#define KC8EA_SW_CHANGED_NOTIFICATION		@"kc8eaSWChangedNotification"
	/* Notification posted when the SW switch changes; notification object is a NSNumber
	   with the new state of SW (0 or 1) */

// coder keys
#define CODER_KEY_POWER_KEY			@"power"
#define CODER_KEY_DISPLAY_SELECTOR_KNOB		@"knob"
#define CODER_KEY_SW				@"sw"
#define CODER_KEY_HALT				@"halt"
#define CODER_KEY_SINGSTEP			@"singstep"

// tag values of the display selection knob
#define DISPLAY_STATE	0
#define DISPLAY_STATUS	1
#define DISPLAY_AC	2
#define DISPLAY_MD	3
#define DISPLAY_MQ	4
#define DISPLAY_BUS	5

// tag values of the power key
#define KEY_OFF		0
#define KEY_POWER	1
#define KEY_PANEL_LOCK	2

// states of the console switches
#define UP		1
#define DOWN		0

// states of the lights
#define OFF		0
#define ON		1


@implementation KC8EA


API_VERSION


#pragma mark GUI


- (void) updateDisplay
{
	unsigned short val = 0;
	if ([key tag] == KEY_POWER) {
		switch ([knob tag]) {
		case DISPLAY_STATE :
			val = [stateMachine state:! [sw state]];
			break;
		case DISPLAY_STATUS :
			val = [stateMachine status];
			break;
		case DISPLAY_AC :
			val = [pdp8 getAC];
			break;
		case DISPLAY_MD :
			val = [stateMachine md];
			break;
		case DISPLAY_MQ :
			val = [pdp8 getMQ];
			break;
		case DISPLAY_BUS :
			val = [stateMachine bus];
			break;
		default :
			NSAssert (FALSE, @"Invalid display selection knob tag");
			break;
		}
	}
	if (displayValue != val) {	// optimization
		[display0  setState:val & 00001];
		[display1  setState:val & 00002];
		[display2  setState:val & 00004];
		[display3  setState:val & 00010];
		[display4  setState:val & 00020];
		[display5  setState:val & 00040];
		[display6  setState:val & 00100];
		[display7  setState:val & 00200];
		[display8  setState:val & 00400];
		[display9  setState:val & 01000];
		[display10 setState:val & 02000];
		[display11 setState:val & 04000];
		displayValue = val;
	}
}


- (void) updateAddress
{
	unsigned short addr = 0;
	if ([key tag] == KEY_POWER)
		addr = [stateMachine cpma];
	if (addressValue != addr) {	// optimization
		[addr0  setState:addr & 000001];
		[addr1  setState:addr & 000002];
		[addr2  setState:addr & 000004];
		[addr3  setState:addr & 000010];
		[addr4  setState:addr & 000020];
		[addr5  setState:addr & 000040];
		[addr6  setState:addr & 000100];
		[addr7  setState:addr & 000200];
		[addr8  setState:addr & 000400];
		[addr9  setState:addr & 001000];
		[addr10 setState:addr & 002000];
		[addr11 setState:addr & 004000];
		[addr12 setState:addr & 010000];
		[addr13 setState:addr & 020000];
		[addr14 setState:addr & 040000];
		addressValue = addr;
	}
}


- (void) update
{
	[self updateDisplay];
	[self updateAddress];
}


- (IBAction) srClicked:(id)sender
{
	[pdp8 setSR:[pdp8 getSR] ^ (04000 >> [sender tag])];
}


- (IBAction) swClicked:(id)sender
{
	[self updateDisplay];
	/* The SW switch is not used by the simulator, but plugins that want to know about the
	   corresponding OMNIBUS signal can listen for this notification */
	[[NSNotificationCenter defaultCenter] postNotificationName:KC8EA_SW_CHANGED_NOTIFICATION
		object:[NSNumber numberWithInt:[sender state] == UP]];
}


- (IBAction) addrloadClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK) {
		[stateMachine loadAddress];
		[self update];
	}
}


- (IBAction) extdaddrloadClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK) {
		[stateMachine loadExtendedAddress];
		[self update];
	}
}


- (IBAction) clearClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK)
		[pdp8 clearAllFlags];
}


- (IBAction) contClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
		[run setState:ON];
		[window display];
		if ([singstep state] == DOWN) {
			[stateMachine executeSingleCycle];
			[self update];
			[run setState:OFF];
		} else if ([halt state] == DOWN) {
			do {
				[stateMachine executeSingleCycle];
				[self update];
				[window display];
			} while (! [stateMachine isInFetchCycle]);
			[run setState:OFF];
		} else {
			while (! [stateMachine isInFetchCycle])
				[stateMachine executeSingleCycle];
			[[NSApp delegate] performSelector:@selector(go:) withObject:self];

		}
	}
}


- (IBAction) examClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
		[run setState:ON];
		[window display];
		[stateMachine examine];
		[self update];
		[run setState:OFF];
	}
}


- (IBAction) haltClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK) {
		[pdp8 setHalt:[sender state] == DOWN || [singstep state] == DOWN];
		[NSApp setWindowsNeedUpdate:YES];	// cause Step/Trace/Go CPU window toolbar items update
	}
}


- (IBAction) stingstepClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK) {
		[pdp8 setHalt:[sender state] == DOWN || [halt state] == DOWN];
		[NSApp setWindowsNeedUpdate:YES];	// cause Step/Trace/Go CPU window toolbar items update
	}
}


- (IBAction) depClicked:(id)sender
{
	if (powerKeyPosition != KEY_PANEL_LOCK && [pdp8 isStopped]) {
		[run setState:ON];
		[window display];
		[stateMachine deposit];
		[self update];
		[run setState:OFF];
	}
}


- (IBAction) knobClicked:(id)sender
{
	[self updateDisplay];
}


- (IBAction) keyClicked:(id)sender
{
	NSEventType eventType = [[NSApp currentEvent] type];
	switch ([sender tag]) {
	case KEY_OFF :
		if (eventType == NSLeftMouseUp || eventType == NSKeyDown) {
			NSAlert *alert = [[NSAlert alloc] init];
			NSBundle *bundle = [NSBundle bundleForClass:[self class]];
			[alert setMessageText:NSLocalizedStringFromTableInBundle(
				@"Do you really want to power off the\nPDP-8/E Simulator?", nil, bundle, @"")];
			[alert addButtonWithTitle:
				NSLocalizedStringFromTableInBundle(@"No", nil, bundle, @"")];
			[alert addButtonWithTitle:
				NSLocalizedStringFromTableInBundle(@"Yes", nil, bundle, @"")];
			if ([alert runModal] ==  NSAlertFirstButtonReturn) {
				[[sender cell] setTag:eventType == NSKeyDown ? KEY_POWER : powerKeyPosition];
				[self update];
			} else
				[NSApp terminate:self];
			[alert release];
		}
		break;
	case KEY_POWER :
		powerKeyPosition = KEY_POWER;
		[self update];
		[pdp8 setHalt:[halt state] == DOWN || [singstep state] == DOWN];
		[NSApp setWindowsNeedUpdate:YES];	// cause Step/Trace/Go CPU window toolbar items update
		break;
	case KEY_PANEL_LOCK :
		powerKeyPosition = KEY_PANEL_LOCK;
		[self update];
		[pdp8 setHalt:NO];
		[NSApp setWindowsNeedUpdate:YES];	// cause Step/Trace/Go CPU window toolbar items update
		break;
	default :
		NSAssert (FALSE, @"Invalid power key tag");
		break;
	}
}


#pragma mark Notifications


- (void) goTimerFireMethod:(NSTimer *)timer
{
	[stateMachine updateState:NO];
	[self update];
}


- (void) notifyGo:(NSNotification *)notification
{
	// hardcoded refresh rate at 60 Hz
	goTimer = [NSTimer scheduledTimerWithTimeInterval:1/60.0 target:self
		selector:@selector(goTimerFireMethod:) userInfo:nil repeats:YES];
	[run setState:ON];
}


- (void) notifyStep:(NSNotification *)notification
{
	[stateMachine updateState:NO];
	[self update];
}


- (void) notifyTrace:(NSNotification *)notification
{
	[run setState:YES];
}


- (void) notifyStop:(NSNotification *)notification
{
	[goTimer invalidate];
	goTimer = nil;
	[run setState:OFF];
	[stateMachine updateState:YES];
	[self update];
}


- (void) notifyPDP8Changed:(NSNotification *)notification
{
	// NSLog (@"KC8EA notifyPDP8Changed");
	[[NSNotificationQueue defaultQueue] enqueueNotification:
		[NSNotification notificationWithName:KC8EA_UPDATE_DISPLAY_NOTIFICATION object:self]
		postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
}


- (void) notifyUpdateDisplay:(NSNotification *) notification
{
	// NSLog (@"KC8EA notifyUpdateDisplay");
	[stateMachine updateState:NO];
	[self update];
}


- (void) notifySRChanged:(NSNotification *)notification
{
	unsigned short sreg = [pdp8 getSR];
	[sr0  setState:sreg & 04000 ? 1 : 0];
	[sr1  setState:sreg & 02000 ? 1 : 0];
	[sr2  setState:sreg & 01000 ? 1 : 0];
	[sr3  setState:sreg & 00400 ? 1 : 0];
	[sr4  setState:sreg & 00200 ? 1 : 0];
	[sr5  setState:sreg & 00100 ? 1 : 0];
	[sr6  setState:sreg & 00040 ? 1 : 0];
	[sr7  setState:sreg & 00020 ? 1 : 0];
	[sr8  setState:sreg & 00010 ? 1 : 0];
	[sr9  setState:sreg & 00004 ? 1 : 0];
	[sr10 setState:sreg & 00002 ? 1 : 0];
	[sr11 setState:sreg & 00001 ? 1 : 0];
}


- (void) notifyClearAllFlags:(NSNotification *)notification
{
	[clear highlight:UP];
	[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
		selector:@selector(unhighliteClearTimerFireMethod:) userInfo:nil repeats:NO];
}


- (void) unhighliteClearTimerFireMethod:(NSTimer *)timer
{
	[clear highlight:DOWN];
}


#pragma mark Initialization


- (void) resetDevice
{
	[[key cell] setTag:KEY_POWER];
	[[knob cell] setTag:DISPLAY_AC];
	[sw setState:UP];
	[halt setState:UP];
	[singstep setState:UP];
	[stateMachine updateState:NO];
	[self update];
}


- (id) init
{
	self = [super init];
	powerKeyPosition = KEY_POWER;
	[[key cell] setTag:powerKeyPosition];
	[[knob cell] setTag:DISPLAY_AC];
	[sw setState:UP];
	addressValue = displayValue = -1;
	/* dummy assignments to suppress Analyzer warning "never used" for the GUI elements not
	   accessed by source code; for the sake of completeness, they are connected in Interface Builder */
	addrload = nil;
	extdaddrload = nil;
	cont = nil;
	dep = nil;
	exam = nil;
	return self;	
}


- (id) initWithCoder:(NSCoder *)coder
{
	self = [super init];
	powerKeyPosition = [coder decodeIntForKey:CODER_KEY_POWER_KEY];
	[[key cell] setTag:powerKeyPosition];
	[[knob cell] setTag:[coder decodeIntForKey:CODER_KEY_DISPLAY_SELECTOR_KNOB]];
	[sw setState:[coder decodeBoolForKey:CODER_KEY_SW]];
	[halt setState:[coder decodeBoolForKey:CODER_KEY_HALT]];
	[singstep setState:DOWN];	// to cause the HALT cell update shaddow to be called
	[singstep setState:[coder decodeBoolForKey:CODER_KEY_SINGSTEP]];
	[pdp8 setHalt:[key tag] == KEY_PANEL_LOCK ? NO : ([halt state] == DOWN || [singstep state] == DOWN)];
	addressValue = displayValue = -1;
	return self;
}


- (void) encodeWithCoder:(NSCoder *)coder
{
	[coder encodeInt:powerKeyPosition forKey:CODER_KEY_POWER_KEY];
	[coder encodeInt:[knob tag] forKey:CODER_KEY_DISPLAY_SELECTOR_KNOB];
	[coder encodeBool:[sw state] forKey:CODER_KEY_SW];
	[coder encodeBool:[halt state] forKey:CODER_KEY_HALT];
	[coder encodeBool:[singstep state] forKey:CODER_KEY_SINGSTEP];
}


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


- (void) notifyPluginsLoaded:(NSNotification *)notification
{
	[self notifySRChanged:nil];
	[self swClicked:sw];
	[self notifyUpdateDisplay:nil];
	[window orderBackFromDefaults:self];
}


- (void) pluginDidLoad
{
	NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:[self pluginName]];
	if (data) {
		NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
		self = [self initWithCoder:unarchiver];
		stateMachine = [[StateMachine alloc] initWithCoder:unarchiver pdp8:pdp8];
		[unarchiver finishDecoding];
		[unarchiver release];
	} else {
		self = [self init];
		stateMachine = [[StateMachine alloc] initWithPDP8:pdp8];
	}
	NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
	[defaultCenter addObserver:self selector:@selector(notifyApplicationWillTerminate:)
		name:NSApplicationWillTerminateNotification object:nil]; 
	[defaultCenter addObserver:self selector:@selector(notifyPluginsLoaded:)
		name:PLUGINS_LOADED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyGo:) name:PDP8_GO_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyStop:) name:PDP8_STOP_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyStep:) name:PDP8_STEP_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyTrace:) name:PDP8_TRACE_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifySRChanged:) name:SR_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:MEMORY_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:PROGRAM_COUNTER_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:ACCUMULATOR_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:SC_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:GTF_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:MQ_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:EAE_MODE_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:DF_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:UF_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:UB_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:SF_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:ENABLE_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:DELAY_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:INHIBIT_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyPDP8Changed:) name:IOFLAGS_CHANGED_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyUpdateDisplay:) name:KC8EA_UPDATE_DISPLAY_NOTIFICATION object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(notifyClearAllFlags:) name:CLEAR_ALL_FLAGS_NOTIFICATION object:nil];
	[(BackgroundView *) [window contentView] setBackgroundImage:@"background" ofType:@"png"];
}


@end
