/*
 *	PDP-8/E Simulator
 *
 *	Copyright © 1994-2015 Bernhard Baehr
 *
 *	Disassembler.m - Disassembler for PDP-8/E instructions
 *
 *	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 "Disassembler.h"
#import "Unicode.h"
#import "PDP8.h"


@implementation Disassembler


+ (Disassembler *) sharedDisassembler
{
	static Disassembler *sharedDisassembler;

	if (! sharedDisassembler)
		sharedDisassembler = [[self alloc] init];
	return sharedDisassembler;
}


- (id) init
{
	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:
		[[NSBundle mainBundle] pathForResource:@"disassembler" ofType:@"plist"]];
		
	self = [super init];
	mriOpcodes = [[dict objectForKey:@"MRI"] retain];
	iotOpcodes = [[dict objectForKey:@"IOT"] retain];
	oprGroup1Opcodes = [[dict objectForKey:@"OPR Group 1"] retain];
	oprGroup2Opcodes = [[dict objectForKey:@"OPR Group 2"] retain];
	eaeModeAOpcodes = [[dict objectForKey:@"EAE Mode A"] retain];
	eaeModeBOpcodes = [[dict objectForKey:@"EAE Mode B"] retain];
	return self;
}


- (NSString *) disassembleOpcodeForPDP8:(PDP8 *)pdp8
	atAddress:(int)addr showOperandsAtPC:(BOOL)showOpAtPC
{
	short inst, word1, word2, ad, d, e, pc, field;
	char str[128], *p;
	
	NSCAssert1 ((addr & ~077777) == 0, @"Bad address %o", addr);
	if (addr >= [pdp8 memorySize])
		return NSLocalizedString(@"n/a", @"");
	p = str;
	pc = [pdp8 getProgramCounter];
	inst = [pdp8 memoryAt:addr];
	word1 = [pdp8 memoryAtNext:addr];	
	switch (inst & 07000) {
	case 00000 :	/* AND */
	case 01000 :	/* TAD */
	case 02000 :	/* ISZ */
	case 03000 :	/* DCA */
	case 04000 :	/* JMS */
	case 05000 :	/* JMP */
		[[mriOpcodes objectAtIndex:(inst >> 9)]
			getCString:p maxLength:(sizeof(str) - 1) encoding:NSASCIIStringEncoding];
		p += strlen(p);
		p += sprintf(p, " %s", (inst & 0400) ? "I " : "");
		ad = ((inst & 0200) ? (addr & 07600) : 0) | (inst & 0177);
		d = ad - (addr & 07777);
		if (inst >= 04000 && ! (inst & 0400)) {
			if (d == 0)
				p += sprintf(p, ".");
			else if (-5 < d && d < 5)
				p += sprintf(p, ".%+d" /* %d (not %o) to get sign */, d);
			else
				p += sprintf(p, "%o", ad);
		 } else
    			p += sprintf(p, "%o", ad);
    		if (addr == pc && showOpAtPC) {
			field = ((inst >= 04000 && ! (inst & 0400) && [pdp8 getIB] != [pdp8 getIF]) ?
				[pdp8 getIB] : [pdp8 getIF]) << 12;
			d = ((inst & 0400) && ad > 07 && ad < 020) ? 1 : 0;
				/* indirect auto index register access */
			word1 = field | ad;
			ad = ([pdp8 memoryAt:(field | ad)] + d) & 07777;
			if (inst < 05000 || inst >= 05400)
				p += sprintf(p, "  (%4.4o)", ad);
			if (inst < 05000 && (inst & 0400)) {
		    		field = ((inst < 04000) ? [pdp8 getDF] : [pdp8 getIB]) << 12;
		    		d = (d && word1 == (field | ad)) ? 1 : 0;
		    		ad = ([pdp8 memoryAt:(field | ad)] + d) & 07777;
				p += sprintf(p, " ((%4.4o))", ad);
			}
		}
		break;
	case 06000 :	/* IOT */
		[[iotOpcodes objectAtIndex:(inst & 0777)]
			getCString:p maxLength:sizeof(str) encoding:NSASCIIStringEncoding];
		p += strlen(p);
		break;
	case 07000 :	/* OPR	*/
		switch (inst & 07401) {
		case 07000 :	/* OPR Group I */
		case 07001 :
			[[oprGroup1Opcodes objectAtIndex:(inst & 0377)]
				getCString:p maxLength:sizeof(str) encoding:NSASCIIStringEncoding];
			p += strlen(p);
			break;
		case 07400 :	/* OPR Group II */
			[[oprGroup2Opcodes objectAtIndex:((inst & 0377) >> 1)]
				getCString:p maxLength:sizeof(str) encoding:NSASCIIStringEncoding];
			p += strlen(p);
			break;
/*
 *		|           |           |           |
 *		|---|---|---|---|---|---|---|---|---|
 *		| 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
 *		| 1 |CLA|MQA|SCA|MQL|   |   |   | 1 |	Group III Mode A
 *		|---|---|---|---|---|---|---|---|---|
 *	Sequence:     1   2       2  \____3____/
 *					  V
 *		0 = NOP   2 = MUY#   4 = NMI    6 = ASR#
 *		1 = SCL#  3 = DVI#   5 = SHL#   7 = LSR#
 *
 *		|           |           |           |
 *		|---|---|---|---|---|---|---|---|---|
 *		| 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
 *		| 1 |CLA|MQA|   |MQL|   |   |   | 1 |	Group III Mode B
 *		|---|---|---|---|---|---|---|---|---|
 *	Sequence:     1   2  \    2            /
 *			      \_______3_______/
 *				      V
 *		0 = NOP    4 = NMI    10 = SCA    14 = DPSZ
 *		1 = ACS    5 = SHL#   11 = DAD#   15 = DPIC*
 *		2 = MUY#   6 = ASR#   12 = DST#   16 = DCM*
 *		3 = DVI#   7 = LSR#   13 = SWBA   17 = SAM
 *		# = 2-word instructions
 *		* = (MQL & MQA must be set)
 */
		case 07401 :	/* EAE */
			d = (inst & 0377) >> 1 ;
			if ([pdp8 getEAEmode] == EAE_MODE_A) {
				[[eaeModeAOpcodes objectAtIndex:d]
					getCString:p maxLength:sizeof(str) encoding:NSASCIIStringEncoding];
				p += strlen(p);
				switch (inst & 016) {
				case 002 :	/* SCL */
				case 004 :	/* MUY */
				case 006 :	/* DVI */
				case 012 :	/* SHL */
				case 014 :	/* ASR */
				case 016 :	/* LSR */
					if (inst != 07447) { /* SWBA */
						p += sprintf(p, " %o", word1);
						/* word1 is operand */
					}
					break;
				default :
					break;
				}
			} else {	/* EAE mode B */
				[[eaeModeBOpcodes objectAtIndex:d]
					getCString:p maxLength:sizeof(str) encoding:NSASCIIStringEncoding];
				p += strlen(p);
				switch (inst & 056) {
				case 04 :	/* MUY */
				case 06 :	/* DVI */
					p += sprintf(p, " %o", word1);
					/* DF|word1 points to operand word */
					if (addr == pc && showOpAtPC) {
						ad = (addr & 070000) | ((addr + 1) & 07777);
						d = ((ad & 07770) == 010) ? 1 : 0;	/* autoindex? */
						word1 = ([pdp8 getDF] << 12) | ((word1 + d) & 07777);
						d = (d && (ad == word1)) ? 1 : 0;
							/* operand == autoindexed ptr? */
						p += sprintf(p, "  (%4.4o)",
							([pdp8 memoryAt:word1] + d) & 07777);
					}
					break;
				case 012 :	/* SHL */
				case 014 :	/* ASR */
				case 016 :	/* LSR */
					p += sprintf(p, " %o", word1);
					/* word1 is operand */
					break;
				case 042 :	/* DAD */
				case 044 :	/* DST */
					p += sprintf(p, " %o", word1);
					/* DF|word1 points to op double word */
					/* DAD adds op double word to AC'MQ */
					/* DST overwrites op dword with AC'MQ */
					if (addr == pc && showOpAtPC) {
						ad = (addr & 070000) | ((addr + 1) & 07777);
						d = ((ad & 07770) == 010) ? 1 : 0;	/* autoindex? */
						word2 = ([pdp8 getDF] << 12) | ((word1 + d + 1) & 07777);
						word1 = ([pdp8 getDF] << 12) | ((word1 + d) & 07777);
						e = (d && (ad == word2)) ? 1 : 0;
							/* operand == autoindexed ptr? */
						d = (d && (ad == word1)) ? 1 : 0;
						p += sprintf(p, "  (%4.4o%4.4o)",
							([pdp8 memoryAt:word2] + e) & 07777,
							([pdp8 memoryAt:word1] + d) & 07777);
					}
					break;
				}
			}
			break;
		}
		break;
	}
	*p = '\0';	/* to avoid Xcode analyzer warning */
	return [NSString stringWithCString:str encoding:NSASCIIStringEncoding];
}


- (NSString *) operandInfoForPDP8:(PDP8 *)pdp8 atAddress:(int)addr
{
	NSMutableString *string;
	int inst, op, ad, oldad, field, d, d1, mark, i;

	inst = [pdp8 memoryAt:addr];
	if (inst >= 06000)
		return nil;		/* no MRI */
	if (05000 <= inst && inst < 05400)
		return nil;		/* direct JMP */
	ad = ((inst & 0200) ? (addr & 07600) : 0) | (inst & 0177);
	d = ((inst & 0400) && ad > 07 && ad < 020) ? 1 : 0;	/* indirect auto index register access */
	ad |= addr & 070000;
	op = ([pdp8 memoryAt:ad] + d) & 07777;
	string = [NSMutableString stringWithFormat:@"%5.5o: %4.4o", ad, op];
	if (inst < 05000 && (inst & 0400)) {
		[string appendString:@"\n\n"];
		oldad = ad;
		ad = op;
		mark = ((inst < 04000) ? [pdp8 getDF] : [pdp8 getIB]) << 12;
		for (i = 0; i < 4; i++) {
			field = i << 12;
		part2 :
		    	d1 = (d && oldad == (field | ad)) ? 1 : 0;
		    	op = ([pdp8 memoryAt:field | ad] + d1) & 07777;
			[string appendFormat:@"%5.5o: %4.4o", field | ad, op];
			if (field == mark)
				[string appendFormat:@"%C", UNICODE_DIAMOND];
		    	if (field & 040000)
		    		[string appendString:@"\n"];
		    	else {
		    		[string appendString:@"\t"];
		    		field |= 040000;
		    		goto part2;
		    	}
		}
	}
	return string;
}


- (void) addMnemonic:(NSString *)mnemonic forIOT:(int)opcode
{
	NSAssert1 ((opcode & ~00777) == 06000, @"Invalid IOT %o", opcode);
	[iotOpcodes replaceObjectAtIndex:opcode & 0777 withObject:mnemonic];
}


@end
