#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "memtype.h"
#include "mem.h"
#include "6502.h"
#include "atari.h"

/* 6502 Emulation 
 * by Frank Barrus
 */


/* OpCodes: */
enum {	xADC, xAND, xASL, xBCC, xBCS, xBEQ, xBIT, xBMI,
	xBNE, xBPL, xBRK, xBVC, xBVS, xCLC, xCLD, xCLI,
	xCLV, xCMP, xCPX, xCPY, xDEC, xDEX, xDEY, xEOR,
	xINC, xINX, xINY, xJMP, xJSR, xLDA, xLDX, xLDY,
	xLSR, xNOP, xORA, xPHA, xPHP, xPLA, xPLP, xROL,
	xROR, xRTI, xRTS, xSBC, xSEC, xSED, xSEI, xSTA,	
	xSTX, xSTY, xTAX, xTAY, xTSX, xTXA, xTXS, xTYA,
/* 65C02 opcodes: */
	xBRA, xDEA, xINA, xPHX, xPHY, xPLX, xPLY, xSTZ,
	xTRB, xTSB,
/* undocumented opcodes: (I can't remember where these came from,
	but I copied them from some magazine (Compute???) to my
	assembly book many years ago-- I don't have full info on
	them, though, so if anyone can dig up any info and mail me 
	please do so) */
	            xANX, xDCP, xISB, xLAX, xRLA, xRRA,
	xSLO, xSRO, xTTA, xTTX ,xXXX};


static char *iname[] = {"ADC","AND","ASL","BCC","BCS","BEQ","BIT","BMI",
			"BNE","BPL","BRK","BVC","BVS","CLC","CLD","CLI",
			"CLV","CMP","CPX","CPY","DEC","DEX","DEY","EOR",
			"INC","INX","INY","JMP","JSR","LDA","LDX","LDY",
			"LSR","NOP","ORA","PHA","PHP","PLA","PLP","ROL",
			"ROR","RTI","RTS","SBC","SEC","SED","SEI","STA",
			"STX","STY","TAX","TAY","TSX","TXA","TXS","TYA",
			"BRA","DEA","INA","PHX","PHY","PLX","PLY","STZ",
			"TRB","TSB","ANX","DCP","ISB","LAX","RLA","RRA",
			"SLO","SRO","TTA","TTX","???"};


/* Addressing Modes: */
enum {	ACC,	/* Accumulator 		A	*/
	IMM,	/* Immediate		#nn	*/
	ZPG,	/* Zero Page 		aa	*/
	ZPX,	/* Zero Page X		aa,X	*/
	ZPY, 	/* Zero Page Y		aa,Y	*/
	ABS,	/* Absolute		aaaa	*/
	ABX,	/* Absolute X		aaaa,X	*/
	ABY, 	/* Absolute Y		aaaa,Y	*/
	IMP,	/* Implied			*/
	REL,	/* Relative		rr	*/
	IXI,	/* Indexed Indirect	(aa,X)	*/
	IIX,	/* Indirect Indexed	(aa),Y	*/
	IND,	/* Indirect		(aaaa)	*/

/* 65C02 modes: */
	AII,	/* Absolute Indexed Indirect	(aaaa,X)	*/
	ZPI,	/* Zero Page Indirect 		(aa)		*/
  
	XXX	/* none				*/
};


static byte itype[256] = {
	xBRK, xORA, xXXX, xXXX, xTSB, xORA, xASL, xSLO,
	xPHP, xORA, xASL, xXXX, xTSB, xORA, xASL, xSLO,
	xBPL, xORA, xORA, xXXX, xTRB, xORA, xASL, xSLO,
	xCLC, xORA, xINA, xXXX, xTRB, xORA, xASL, xSLO,

	xJSR, xAND, xXXX, xXXX, xBIT, xAND, xROL, xRLA,
	xPLP, xAND, xROL, xXXX, xBIT, xAND, xROL, xRLA,
	xBMI, xAND, xAND, xXXX, xBIT, xAND, xROL, xRLA,
	xSEC, xAND, xDEA, xXXX, xBIT, xAND, xROL, xRLA,

	xRTI, xEOR, xXXX, xXXX, xXXX, xEOR, xLSR, xSRO,
	xPHA, xEOR, xLSR, xXXX, xJMP, xEOR, xLSR, xSRO,
	xBVC, xEOR, xEOR, xXXX, xXXX, xEOR, xLSR, xSRO,
	xCLI, xEOR, xPHY, xXXX, xXXX, xEOR, xLSR, xSRO,

	xRTS, xADC, xXXX, xXXX, xSTZ, xADC, xROR, xRRA,
	xPLA, xADC, xROR, xXXX, xJMP, xADC, xROR, xRRA,
	xBVS, xADC, xADC, xXXX, xSTZ, xADC, xROR, xRRA,
	xSEI, xADC, xPLY, xXXX, xJMP, xADC, xROR, xRRA,

	xBRA, xSTA, xXXX, xXXX, xSTY, xSTA, xSTX, xANX,
	xDEY, xBIT, xTXA, xXXX, xSTY, xSTA, xSTX, xANX,
	xBCC, xSTA, xXXX, xXXX, xSTY, xSTA, xSTX, xANX,
	xTYA, xSTA, xTXS, xXXX, xSTZ, xSTA, xSTZ, xTTA,

	xLDY, xLDA, xLDX, xXXX, xLDY, xLDA, xLDX, xLAX,
	xTAY, xLDA, xTAX, xXXX, xLDY, xLDA, xLDX, xLAX,
	xBCS, xLDA, xLDA, xXXX, xLDY, xLDA, xLDX, xLAX,
	xCLV, xLDA, xTSX, xXXX, xLDY, xLDA, xLDX, xLAX,

	xCPY, xCMP, xXXX, xXXX, xCPY, xCMP, xDEC, xDCP,
	xINY, xCMP, xDEX, xXXX, xCPY, xCMP, xDEC, xDCP,
	xBNE, xCMP, xCMP, xXXX, xXXX, xCMP, xDEC, xDCP,
	xCLD, xCMP, xPHX, xXXX, xXXX, xCMP, xDEC, xDCP,

	xCPX, xSBC, xCMP, xXXX, xCPX, xSBC, xINC, xISB,
	xINX, xSBC, xNOP, xXXX, xCPX, xSBC, xINC, xISB,
	xBEQ, xSBC, xSBC, xXXX, xXXX, xSBC, xINC, xISB,
	xSED, xSBC, xPLX, xXXX, xXXX, xSBC, xINC, xISB,
};

static byte amode[256] = {
	IMP, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, ACC, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, ZPG, ZPX, ZPX, XXX,
	IMP, ABY, ACC, XXX, ABS, ABX, ABX, XXX,

	ABS, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, ACC, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, ZPX, ZPX, ZPX, XXX,
	IMP, ABY, ACC, XXX, ABX, ABX, ABX, XXX,

	IMP, IXI, XXX, XXX, XXX, ZPG, ZPG, XXX,
	IMP, IMM, ACC, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, XXX, XXX, XXX, ZPX, ZPX, XXX,
	IMP, ABY, ACC, XXX, XXX, ABX, ABX, XXX,

	IMP, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, ACC, XXX, IND, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, ZPX, ZPX, ZPX, XXX,
	IMP, ABY, IMP, XXX, AII, ABX, ABX, XXX,

	REL, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, IMP, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, ZPX, ZPX, ZPX, XXX,
	IMP, ABY, IMP, XXX, ABS, ABX, ABX, XXX,

	IMM, IXI, IMM, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, IMP, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, ZPX, ZPX, ZPY, XXX,
	IMP, ABY, IMP, XXX, ABX, ABX, ABY, XXX,

	IMM, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, IMP, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, XXX, ZPX, ZPX, XXX,
	IMP, ABY, IMP, XXX, XXX, ABX, ABX, XXX,

	IMM, IXI, XXX, XXX, ZPG, ZPG, ZPG, XXX,
	IMP, IMM, IMP, XXX, ABS, ABS, ABS, XXX,
	REL, IIX, ZPG, XXX, XXX, ZPX, ZPX, XXX,
	IMP, ABY, IMP, XXX, XXX, ABX, ABX, XXX,
};



int showtime = False;


void print_6502(word pc, byte opcode, word operand)
{
byte hi,lo;
char *name;
	name = iname[itype[opcode]];
	hi = HI(operand);
	lo = LO(operand);
	switch(amode[opcode]) {
	case ACC: printf("%02x        %s A      ", opcode, name); break;
	case IMP: printf("%02x        %s        ", opcode, name); break;
	case IMM: printf("%02x %02x     %s #%02x    ",
			opcode, lo, name, operand); break;
	case ZPG: printf("%02x %02x     %s %02x     ",
			opcode, lo, name, operand); break; 
	case ZPX: printf("%02x %02x     %s %02x,x   ",
			opcode, lo, name, operand); break;
	case ZPY: printf("%02x %02x     %s %02x,y   ",
			opcode, lo, name, operand); break;
	case ABS: printf("%02x %02x %02x  %s %04x   ",
			opcode, lo, hi, name, operand); break; 
	case ABX: printf("%02x %02x %02x  %s %04x,x ",
			opcode, lo, hi, name, operand); break; 
	case ABY: printf("%02x %02x %02x  %s %04x,y ",
			opcode, lo, hi, name, operand); break; 
	case REL: printf("%02x %02x     %s %04x",
			opcode, lo, name, pc+(sbyte)lo); break;
	case IXI: printf("%02x %02x     %s (%02x,x) ",
			opcode, lo, name, operand); break;
	case IIX: printf("%02x %02x     %s (%02x),y ",
			opcode, lo, name, operand); break;
	case IND: printf("%02x %02x %02x  %s (%04x) ",
			opcode, lo, hi, name, operand); break;
	default:
	       	printf("\007%02x       %s ???\n", opcode, name);
	}
}


word dis_6502(word pc)
{
word oldpc;
byte opcode;
word operand;
	oldpc = pc;
	MEM_READ(pc,opcode); INCW(pc);
	switch(amode[opcode]) {
	case IMM: case ZPG: case ZPX: case ZPY:
	case REL: case IXI: case IIX:
		MEM_READ(pc, operand); INCW(pc); break;
	case ABS: case ABX: case ABY: case IND:
		MEM_WREAD(pc, operand); ADDW(pc,2); break;
	default:
		operand = 0;
	}
	print_6502(oldpc,opcode,operand);
	return(pc);
}


char *flagstr(byte f)
{
static char flagstr[9];
int n;
	strcpy(flagstr,"NV?BDIZC");
	for(n=0; n<8; n++)
	if(!(f & (1<<(7-n))))
		flagstr[n] = '-';
	return flagstr;
}


void showtrace_6502(int ninst)
{
int pos;
struct trace6502 *t;
int inst, mode, store;
	if(!trace) {
		printf("trace size is 0 (see -trace option)\n");
		return;
	}
	pos = (tracepos - ninst + tracesize) % tracesize;
	while(ninst--) {
		t = &trace[pos];
		if(showtime) {
			int x,y;
			int frame,sec;

			sec = t->cycle/CPUHZ;
			frame = (t->cycle%CPUHZ)/(NCYCLES*NSCAN);
			y = (t->cycle%(NCYCLES*NSCAN))/NCYCLES;
			x = t->cycle%NCYCLES;
			printf("%8d [%04d.%02d:%03d,%03d]\n", t->cycle,
				sec, frame, x, y);
		}		
		printf("pc=%04x sp=%02x a=%02x x=%02x y=%02x %s  ",
			t->pc, t->sp, t->a, t->x, t->y, flagstr(t->flags));
		print_6502(t->pc, t->opcode, t->operand);
		mode = amode[t->opcode];
		inst = itype[t->opcode];
		store = (inst==xSTA || inst==xSTX || inst==xSTY
			|| inst==xSTZ || inst==xINC || inst == xDEC
			|| inst==xASL || inst==xLSR || inst == xROL
			|| inst==xROR);
		if(store && mode!=ACC)
			printf("  %02x->%04x", t->data, t->addr);
		if(!store && mode != ACC && mode != IMM
		  && mode != REL && mode != IMP	
		  && inst != xJMP && inst != xJSR)
			printf("  %04x=%02x", t->addr, t->data);
		printf("\n");
		pos = (pos+1) % tracesize;
	}
}


void trace_6502(int ninst)
{
struct trace6502 tracebuf;
struct trace6502 *savetrace = trace;
int savetracesize = tracesize;
int savetracepos = tracepos;
	trace = &tracebuf;
	tracesize = 1;
	tracepos = 0;
	while(ninst--) {
		run_6502(1);
		showtrace_6502(1);
		if(cpu.sync)
			printf("SYNC!\n");
		if(cpu.halt)
			printf("HALT!\n");
		cpu.sync = 0;
		cpu.halt = 0;
	}	
	tracepos = savetracepos;
	tracesize = savetracesize;
	trace = savetrace;
}



void cpu_reset(void)
{
	cpu.flags = (cpu.flags&~(FLAG_D|FLAG_B))|FLAG_I|0x20;
	cpu.irq = False;
	MEM_WREAD(VEC_RESET, cpu.pc);
	cpu.tcycles += 7;
}

void cpu_nmi(void)
{
	PUSHW(cpu.pc);
	PUSH(cpu.flags);
	MEM_WREAD(VEC_NMI, cpu.pc);
	cpu.flags |= FLAG_I;
	cpu.tcycles += 7;
}

void cpu_irq(void)
{
	if(cpu.flags&FLAG_I) 
		cpu.irq = True;
	else {
		cpu.irq = False;
		PUSHW(cpu.pc);
		PUSH(cpu.flags);
		MEM_WREAD(VEC_IRQ, cpu.pc);
		cpu.flags = (cpu.flags&~FLAG_B)|FLAG_I;
		cpu.tcycles += 7;
	}
}


void cpu_schedule(int n, int t, void (*s)(int), int p)
{
struct sched *sp;
int i;
	sp = &cpu.event[n];
	sp->func = s;
	sp->param = p;
	if(t)
		t += cpu.tcycles;
	sp->time = t;
	sp = NULL;
	for(i=0; i<NSCHED; i++)
		if(cpu.event[i].time && (!sp
		  ||(cpu.event[i].time-cpu.tcycles) < (sp->time-cpu.tcycles)))
			sp = &cpu.event[i];
	cpu.sched = sp;
}


int cpu_init(int argc, char **argv)
{
static int init = True;
int i,n;
int narg = 0;
char **argp = argv;
extern byte flags_set[512];
extern int dobrk;	
	if(init) {
		trace = NULL;
		tracesize = 0;
	}
	while(argc--) {
		if(!strcmp(*argv,"-trace")) {
			if(trace)
				free(trace);
			tracesize = atoi(*(++argv)); argc--;
			tracepos = 0;
			if(tracesize)
				trace = (struct trace6502 *)calloc(
					tracesize, sizeof(struct trace6502));
			else
				trace = NULL;
		} else if(!strcmp(*argv,"-brk")) {
			dobrk = True;
		} else if(!strcmp(*argv,"-nobrk")) {
			dobrk = False;
		} else if(!strcmp(*argv,"-showtime")) {
			showtime = True;
		} else if(!strcmp(*argv,"-noshowtime")) {
			showtime = False;
		} else if(!strcmp(*argv,"-reset")) {
			cpu.halt = cpu.sync = 0;
			cpu_reset();		
			cpu.sched = NULL;
		} else {
			*argp++ = *argv; narg++;
		}
		if(!strcmp(*argv++, "-h")) {
			printf("6502 CPU options:\n");
			printf("\t-trace <n>\t\tsave last <n> instructions\n");
			printf("\t-[no]brk\t\tenable/disable BRK opcode\n");
			printf("\t-[no]showtime\t\tshow cycle times in trace\n");
			printf("\t-reset\t\t\treset cpu\n");
			return narg;
		}
	}
	if(!init)
		return narg;
	init = False;

	cpu.halt = cpu.sync = 0;
	cpu.tcycles = 0;
	cpu_reset();
	cpu.sched = NULL;
	for(i=0; i<NSCHED; i++)
		cpu.event[i].time = 0;
	for(i=0; i<512; i++) {
		n = 0;
		if(i&0x100)
			n |= FLAG_C;
		if(i&0x80)
			n |= FLAG_N;
		if((i&0xff) == 0)
			n |= FLAG_Z;
		flags_set[i] = n;
	}
	return narg;
}

