/*****************************************************************************/
/*									     */
/*									     */
/*	CP/M emulator version 0.1					     */
/*									     */
/*	written by Michael Bischoff (mbi@mo.math.nat.tu-bs.de)		     */
/*	June-1994							     */
/*									     */
/*	This file is distributed under the GNU COPYRIGHT		     */
/*	see COPYRIGHT.GNU for Copyright details				     */
/*									     */
/*									     */
/*****************************************************************************/
#include "cpmemu.h"
#include <time.h>
#include <sys/time.h>

extern int slowdown, tickercnt;

static void doint(void) {
    /* execute Z80-interrupt */
  
    unsigned char rembyte;
    unsigned oldpc;
    rembyte = z80mem[oldpc = --z80regs.pc];
    z80mem[oldpc] = 0xff;	/* RST 38h */
    singlestep();		/* perform RST */
    z80mem[oldpc++] = rembyte;	/* restore old byte */
    do {
	singlestep();
	if (z80mem[z80regs.pc] == 0x76 || z80regs.pc >= BDOS)
	    break;
    } while (z80regs.pc != oldpc);
}

void run_at_2MHz(void) {
    if (slowdown) {
	struct timeval tv1;
	struct timezone tz;
	int ctr, icnt;
	ctr = icnt = 0;
	while (!(z80mem[z80regs.pc] == 0x76 && z80regs.pc < BDOS)) {
	    z80step(1);
	    ++*(char *)&z80regs.ir;
	    if (++ctr == slowdown) {
		ctr = 0;
		if (tickercnt && ++icnt == tickercnt) {
		    /* do an interrupt */
		    icnt = 0;
		    if (z80regs.pc > 0x100 && z80regs.pc < BDOS &&
			!z80regs.iff) {
			doint();
		    }
		} else
		    gettimeofday(&tv1, &tz);
	    }
	}
    } else
	z80run();	/* full speed! */
}

void commandloop(void) {
    /* forever cycle in command loop or enter GO mode */
    static int lastkey = 0;
    long memcnt = 0, currmem = 0;
    struct z80regs mem[256];
    for (;;) {
	int c;
	unsigned num, addr;
	dispregs(z80regs.pc);
    nodisplay:
	if (prompt)
	    printf("Z80> "); fflush(stdout);
	while (!(c = conin()))
	    ;		/* c is command character */
	if (prompt)
	    putchar('\r');
	if (c == '\r' || c == '\n') {
	    switch (lastkey) {
	    case 'l': case 'u':
		disassem(&addr, 16);
		break;
	    case 'd':
		memdump(addr+=0x80, 8);
		break;
	    }
	} else
	    lastkey = c;

        switch (c) {
	case '?':
	    printf("Z80 debug quick help:\n"
		   "B (n) (loc)   set breakpoint (n) to (loc)\n"
		   "L (n) (loc)   set listpoint (n) to (loc)\n"
		   "N (n) (cnt)   set ignore count for break/listpoint (n)\n"
		   "S             estimate emulation speed\n"
		   "R             run (continue)\n"
		   "x             display Z80 registers\n"
		   "l (loc)       disassemble memory\n"
		   "d (loc)       dump memory\n"
		   "g             go (continue)\n"
		   "j (loc)       jump to (loc)\n"
		   "e (loc)       edit byte (loc)\n"
		   "; (loc)       run until (loc)\n"
		   "t             single step one statement\n"
		   "c             single step; but run through subroutines\n"
		   "q             exit cpm emulator\n"
		   "b             run until next BIOS call\n"
		   "i             info about breakpoints\n"
		   "w (loc)       watch memory cell (step until it changes)\n"
		   "r             step until SP is incremented (RET, POP)\n"
		   "all data is hexadecimal; pressing ENTER at (loc) input\n"
		   "will return current PC\n");
	    break;
	case 'B':		/* set Breakpoint (n) */
	    printf("set breakpoint ");
	    num = getdig('1') - '1';
	    printf(" to address ");
	    breakpoint[num].where = gethex();
	    breakpoint[num].action = AC_BREAK;
	    breakpoint[num].maxcnt = 1;
	    breakpoint[num].curcnt = 0;
	    break;
	case 'L':		/* set listpoint (n) */
	    printf("set listpoint ");
	    num = getdig('1') - '1';
	    printf(" to address ");
	    breakpoint[num].where = gethex();
	    breakpoint[num].action = AC_LIST;
	    breakpoint[num].maxcnt = 1;
	    breakpoint[num].curcnt = 0;
	    break;
	case 'N':		/* number break- or listpoint (n) */
	    printf("Number break/listpoint ");
	    num = getdig('1') - '1';
	    printf(" to maxcount ");
	    breakpoint[num].maxcnt = getdig(0);
	    breakpoint[num].curcnt = 0;
	    if (!breakpoint[num].maxcnt)
		breakpoint[num].action = AC_NONE;
	    break;
	case 'S':		/* measure speed */
	    printf("Estimating speed... "); fflush(stdout);
	    {   struct z80regs saveregs;
		clock_t t;
		long cycles;
		static unsigned char benchmark[24] = {
		    0x1e, 0x06,		/* ld e,6 */
		    0x01, 0x00, 0x00,	/* ld bc,0 */
		    0x0a,		/* 7T:	ld a,(bc) */
		    0x65,		/* 4T:	ld h,l */
		    0x54,		/* 4T:	ld d,h */
		    0x21, 0x33, 0x33,	/* 10T:	ld hl,3333 */
		    0x0b,	        /* 6T:	dec bc */
		    0x78,		/* 4T:	ld a,b */
		    0xb1,		/* 4T:	or c */
		    0x38, 0xef,		/* 5T:	jr false */
		    0xc2, 0x05, 0x01,	/* 10T: jp true */
		    0x1d,		/* dec e */
		    0x20, 0xec,		/* jr nz,102 */
		    0x76		/* HALT */
		    }, savemem[24];
		memcpy(savemem, z80mem+0x100, 24);
		memcpy(z80mem+0x100, benchmark, 24);
		saveregs = z80regs;
		z80regs.pc = 0x100;
		t = clock();
		z80run();
		t = clock() - t;
		cycles = 6L * 65536L * 54L;/* should be 54T on a genuine Z80 */
#if 1
		z80regs = saveregs;
		memcpy(z80mem+0x100, savemem, 24);
#endif
		printf("Your Z80 roams at %f MHz\n",
		       (double)cycles * 1e-6 / t * CLOCKS_PER_SEC);
	    }
	    break;
	case 'R':		/* run the program */
	    z80run();
	    break;
        case 'x':
	    /* dispregs(z80regs.pc); */
	    dispregs2();
            break;
        case 'l':
	    printf("list from loc: ");
	    addr = gethex();
	    disassem(&addr, 16);
	    goto nodisplay;
        case 'd':
	    /* display memory */
	    printf("dump memory at: ");
	    addr = gethex();
	    memdump(addr, 8);
	    goto nodisplay;
        case 'g':	/* GO */
	    debug = 0;
	    return;
        case 'j':
	    printf("jump to: ");
	    z80regs.pc = gethex();
	    break;
        case 'e':
	    printf("edit byte: ");	/* abort with ctrl-@ */
	    addr = gethex();
	    for (;;) {
		printf("%04x %02x ", addr, z80mem[addr]);
		num = gethex();
		z80mem[addr++] = num;
	    }
	    /* never reaches this */
        case ';':
	    printf("run until ");
	    breakpoint[NBREAKS].where = gethex();
	xcall:
	    breakpoint[NBREAKS].action = AC_BREAK;
	    breakpoint[NBREAKS].maxcnt = 1;
	    breakpoint[NBREAKS].curcnt = 0;
	    z80run();
	    breakpoint[NBREAKS].action = AC_NONE;
	    break;
        case 'c':	/* trace over CALLs and RSTs */
	    switch (z80mem[z80regs.pc]) {
	    case 0xcd:
	    case 0xc4: case 0xcc: case 0xd4: case 0xdc:
	    case 0xe4: case 0xec: case 0xf4: case 0xfc:
		breakpoint[NBREAKS].where = z80regs.pc + 3;
		goto xcall;
	    case 0xf7:              /* short BDOS call */
		breakpoint[NBREAKS].where = z80regs.pc + 2;
		goto xcall;
	    case 0xc7: case 0xcf: case 0xd7: case 0xdf:
	    case 0xe7: case 0xef: case 0xff:
		breakpoint[NBREAKS].where = z80regs.pc + 1;
		goto xcall;
	    } /* else fall thru */
        case 't':
	    z80step(1);
	    break;
        case 'q':
	    exit(0);
	case 'b':
	    break_at_BIOS = 1;
	    z80run();
	    break;
	case 'i':	/* info */
	    {   struct breakpoint *bp;
		int i;
		bp = breakpoint;
		for (i = 0; i < NBREAKS; ++i, ++bp)
		    if (bp->action)
			printf("BP %d (count %d of %d) is set at %04x, type %s\n", i, bp->curcnt, bp->maxcnt, bp->where, bp->action == AC_BREAK ? "BREAK" : "LIST");
		printf("last location of 'c' breakpoint: %04x\n", bp->where);
	    }
	    break;
        case 'w':
	    printf("watch mem cell: ");
	    addr = gethex();
	    num = z80mem[addr];
	    do {
		unsigned oldpc;
		oldpc = z80regs.pc;
		z80step(1);
		if (z80regs.pc == oldpc && z80mem[z80regs.pc] != 0x10)
		    break;	/* break, but not on djnz $ */
	    } while (z80mem[addr] == num);
	    break;
        case 'r':
	    printf("running...\n");
	    addr = z80regs.sp + 2;
	    do {
		z80step(1);
		if (z80mem[z80regs.pc] == 0x76 && z80regs.pc < BDOS)
		    break;
	    } while (z80regs.sp != addr);
	    break;
	case 'T':	/* trace... */
	    printf("Trace until ");
	    addr = gethex();
	    do {
		dispregs(z80regs.pc);
		z80step(1);
		if (z80mem[z80regs.pc] == 0x76 && z80regs.pc < BDOS)
		    break;
	    } while (z80regs.pc != addr);
	    break;
	case 'M':	/* memorize... */
	    printf("Memorize until: ");
	    addr = gethex();
	    memcnt = 0;
	    do {
		mem[memcnt & 255] = z80regs;
		z80step(1);
		if (z80mem[z80regs.pc] == 0x76 && z80regs.pc < BDOS)
		    break;
		++memcnt;
	    } while (z80regs.pc != addr);
	    currmem = memcnt & 255;
	    break;
	case 'U':	/* memorize... */
	    printf("Watch: ");
	    memcnt = 0;
	    do {
		mem[memcnt & 255] = z80regs;
		z80step(1);
		if (z80mem[z80regs.pc] == 0x76 && z80regs.pc < BDOS)
		    break;
		++memcnt;
	    } while (z80mem[0x4d04] != 0xee && z80mem[0x4d04] != 0xbc);
	    currmem = memcnt & 255;
	    break;
	case '+':
	    z80regs = mem[currmem = (currmem+1) & 255];
	    printf("die - %ld:\n", (memcnt-currmem+256) & 255);
	    break;
	case '-':
	    z80regs = mem[currmem = (currmem+255) & 255];
	    printf("die - %ld:\n", (memcnt-currmem+256) & 255);
	    break;
        case '2':	/* run at 2 MHz */
	    printf("running at 2 MHz\n");
	    run_at_2MHz();
	    break;
	default:
	    goto nodisplay;
	}
    }
}
