/*****************************************************************************
 * pce                                                                       *
 *****************************************************************************/

/*****************************************************************************
 * File name:   src/arch/atarist/cmd.c                                       *
 * Created:     2011-03-17 by Hampa Hug <hampa@hampa.ch>                     *
 * Copyright:   (C) 2011-2019 Hampa Hug <hampa@hampa.ch>                     *
 *****************************************************************************/

/*****************************************************************************
 * This program is free software. You can redistribute it and / or modify it *
 * under the terms of the GNU General Public License version 2 as  published *
 * by the Free Software Foundation.                                          *
 *                                                                           *
 * 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.                                          *
 *****************************************************************************/


#include "main.h"
#include "atarist.h"
#include "cmd.h"
#include "dma.h"

#include <string.h>

#include <lib/console.h>
#include <lib/log.h>
#include <lib/monitor.h>
#include <lib/msgdsk.h>
#include <lib/sysdep.h>


#ifndef DEBUG_BIOS
#define DEBUG_BIOS 0
#endif


mon_cmd_t par_cmd[] = {
	{ "c", "[cnt]", "clock" },
	{ "gb", "[addr..]", "run with breakpoints at addr" },
	{ "ge", "[exception]", "run until exception" },
	{ "g", "", "run" },
	{ "halt", "[val]", "set halt state [2]" },
	{ "hm", "", "print help on messages" },
	{ "p", "[cnt]", "execute cnt instructions, skip calls [1]" },
	{ "reset", "", "reset" },
	{ "rte", "", "execute to next rte" },
	{ "r", "reg [val]", "get or set a register" },
	{ "s", "[what]", "print status (acia0|acia1|cpu|disks|dma|mem|mfp|psg|video)" },
	{ "t", "[cnt]", "execute cnt instructions [1]" },
	{ "u", "[w][[-]addr [cnt]]", "disassemble" },
	{ "uw", "[addr [cnt]]", "disassemble as constant words" }
};

unsigned par_cmd_cnt = sizeof (par_cmd) / sizeof (par_cmd[0]);


static
void st_dasm_str (char *dst, e68_dasm_t *op, int opcode)
{
	unsigned   i, n;
	char       tmp[272];
	const char *ins;

	strcpy (dst, "");

	if (opcode) {
		for (i = 0; i < op->irn; i++) {
			sprintf (tmp, "%04X ", (unsigned) op->ir[i]);
			strcat (dst, tmp);
		}

		for (i = op->irn; i < 4; i++) {
			strcat (dst, "     ");
		}
	}

	if (op->flags & E68_DFLAG_PRIV) {
		strcat (dst, "*");
	}
	else if (op->flags & E68_DFLAG_CALL) {
		strcat (dst, ">");
	}
	else if (op->flags & E68_DFLAG_RTS) {
		strcat (dst, "<");
	}
	else {
		strcat (dst, " ");
	}

	ins = op->op;

	switch (op->argn) {
	case 0:
		sprintf (tmp, "%s", ins);
		break;

	case 1:
		sprintf (tmp, "%-8s %s", ins, op->arg1);
		break;

	case 2:
		sprintf (tmp, "%-8s %s, %s", ins, op->arg1, op->arg2);
		break;

	case 3:
		sprintf (tmp, "%-8s %s, %s, %s",
			ins, op->arg1, op->arg2, op->arg3
		);
		break;

	default:
		strcpy (tmp, "---");
		break;
	}

	strcat (dst, tmp);

	if (op->comm[0] != 0) {
		n = strlen (dst);

		dst += n;

		while (n < 50) {
			*(dst++) = ' ';
			n += 1;
		}

		strcpy (dst, "; ");
		strcat (dst, op->comm);
	}
}

void st_print_state_cpu (atari_st_t *sim)
{
	e68000_t   *c;
	e68_dasm_t op;
	char       str[256];

	pce_prt_sep ("68000");

	c = sim->cpu;

	pce_printf ("SR=%04X      [%c%c %c%c%c%c%c]   EX=%02X(%-4s)  TA=%04X       IL=%X/%X\n",
		(unsigned) e68_get_sr (c),
		(e68_get_sr_t (c)) ? 'T' : '-',
		(e68_get_sr_s (c)) ? 'S' : '-',
		(e68_get_sr_x (c)) ? 'X' : '-',
		(e68_get_sr_n (c)) ? 'N' : '-',
		(e68_get_sr_z (c)) ? 'Z' : '-',
		(e68_get_sr_v (c)) ? 'V' : '-',
		(e68_get_sr_c (c)) ? 'C' : '-',
		e68_get_exception (c),
		e68_get_exception_name (c),
		e68_get_last_trap_a (c),
		e68_get_ipl (c),
		e68_get_iml (c)
	);

	pce_printf ("D0=%08lX  D4=%08lX  A0=%08lX  A4=%08lX   PC=%08lX  PC1=%08lX\n",
		(unsigned long) e68_get_dreg32 (c, 0),
		(unsigned long) e68_get_dreg32 (c, 4),
		(unsigned long) e68_get_areg32 (c, 0),
		(unsigned long) e68_get_areg32 (c, 4),
		(unsigned long) e68_get_pc (c),
		(unsigned long) e68_get_last_pc (c, 0)
	);

	pce_printf ("D1=%08lX  D5=%08lX  A1=%08lX  A5=%08lX  PPC=%08lX  PC2=%08lX\n",
		(unsigned long) e68_get_dreg32 (c, 1),
		(unsigned long) e68_get_dreg32 (c, 5),
		(unsigned long) e68_get_areg32 (c, 1),
		(unsigned long) e68_get_areg32 (c, 5),
		e68_get_ir_pc (c),
		(unsigned long) e68_get_last_pc (c, 1)
	);

	pce_printf ("D2=%08lX  D6=%08lX  A2=%08lX  A6=%08lX  USP=%08lX  PC3=%08lX\n",
		(unsigned long) e68_get_dreg32 (c, 2),
		(unsigned long) e68_get_dreg32 (c, 6),
		(unsigned long) e68_get_areg32 (c, 2),
		(unsigned long) e68_get_areg32 (c, 6),
		(unsigned long) e68_get_usp (c),
		(unsigned long) e68_get_last_pc (c, 2)
	);

	pce_printf ("D3=%08lX  D7=%08lX  A3=%08lX  A7=%08lX  SSP=%08lX  PC4=%08lX\n",
		(unsigned long) e68_get_dreg32 (c, 3),
		(unsigned long) e68_get_dreg32 (c, 7),
		(unsigned long) e68_get_areg32 (c, 3),
		(unsigned long) e68_get_areg32 (c, 7),
		(unsigned long) e68_get_ssp (c),
		(unsigned long) e68_get_last_pc (c, 3)
	);

	e68_dasm_mem (c, &op, e68_get_pc (c));
	st_dasm_str (str, &op, 1);

	pce_printf ("%08lX  %s\n", (unsigned long) e68_get_pc (c), str);
}

static
void st_print_state_cpu_short (e68000_t *c)
{
	e68_dasm_t op;
	char       str[256];

	e68_dasm_mem (c, &op, e68_get_pc (c));
	st_dasm_str (str, &op, 0);

	pce_printf ("D0=%08lX A0=%08lX A6=%08lX A7=%08lX SR=%04X[%c%c%c%c%c%c%c] %06lX %s\n",
		(unsigned long) e68_get_dreg32 (c, 0),
		(unsigned long) e68_get_areg32 (c, 0),
		(unsigned long) e68_get_areg32 (c, 6),
		(unsigned long) e68_get_areg32 (c, 7),
		(unsigned) e68_get_sr (c),
		(e68_get_sr_t (c)) ? 'T' : '-',
		(e68_get_sr_s (c)) ? 'S' : '-',
		(e68_get_sr_c (c)) ? 'C' : '-',
		(e68_get_sr_v (c)) ? 'V' : '-',
		(e68_get_sr_z (c)) ? 'Z' : '-',
		(e68_get_sr_n (c)) ? 'N' : '-',
		(e68_get_sr_x (c)) ? 'X' : '-',
		(unsigned long) e68_get_pc (c), str
	);
}

static
void st_print_state_mfp_timer (e68901_timer_t *tmr, const char *label)
{
	pce_printf ("TM-%s: CR=%02X DR=%02X/%02X DIV=%04X CLK=%04X OUT=%d (%u/%u)\n",
		label,
		tmr->cr, tmr->dr[0], tmr->dr[1],
		tmr->clk_div_set, tmr->clk_val / tmr->clk_div_inp,
		tmr->out != 0, tmr->clk_val, tmr->clk_div
	);
}

static
void st_print_state_mfp (atari_st_t *sim)
{
	e68901_t *mfp;

	static char parity[4] = { 'N', 'N', 'O', 'E' };

	mfp = &sim->mfp;

	pce_prt_sep ("68901-MFP");

	pce_printf ("GPIP: INP=%02X/%02X OUT=%02X AER=%02X DDR=%02X\n",
		mfp->gpip_inp, mfp->gpip_xor, mfp->gpip_val, mfp->gpip_aer, mfp->gpip_ddr
	);

	pce_printf ("INTR: IRR=%04X/%04X IER=%04X IPR=%04X IMR=%04X ISR=%04X IVR=%02X VEC=%02X IRQ=%d\n",
		mfp->irr1, mfp->irr2, mfp->ier, mfp->ipr, mfp->imr, mfp->isr, mfp->ivr, mfp->vec, mfp->irq_val
	);

	pce_printf ("UART: PAR=%u%c%u UCR=%02X RSR=%02X RDR=%02X TSR=%02X TDR=%02X RCLK=%u/%u SCLK=%u/%u\n",
		8 - ((mfp->ucr >> 5) & 3),
		parity[(mfp->ucr >> 1) & 3],
		(((mfp->ucr >> 3) & 3) + 1) / 2,
		mfp->ucr, mfp->rsr[0], mfp->rdr[0], mfp->tsr[0], mfp->tdr[0],
		mfp->recv_clk_cnt, mfp->recv_clk_max,
		mfp->send_clk_cnt, mfp->send_clk_max
	);

	st_print_state_mfp_timer (mfp->timer + 0, "A");
	st_print_state_mfp_timer (mfp->timer + 1, "B");
	st_print_state_mfp_timer (mfp->timer + 2, "C");
	st_print_state_mfp_timer (mfp->timer + 3, "D");
}

static
void st_print_state_acia (atari_st_t *sim, unsigned idx)
{
	e6850_t *acia;

	if (idx == 0) {
		acia = &sim->acia0;
		pce_prt_sep ("6850-ACIA-0");
	}
	else {
		acia = &sim->acia1;
		pce_prt_sep ("6850-ACIA-1");
	}

	pce_printf ("CR=%02X  SR=%02X  TDR=%02X  RDR=%02X  IRQ=%d\n",
		acia->cr, acia->sr, acia->tdr, acia->rdr, acia->irq_val
	);
}

static
void st_print_state_dma (atari_st_t *sim)
{
	st_dma_t *dma;

	dma = &sim->dma;

	pce_prt_sep ("DMA");

	pce_printf ("MODE=%04X  STATUS=%04X  ADDR=%06lX  MASK=%06lX\n",
		dma->mode, dma->status, dma->addr, dma->mask
	);
}

static
void st_print_state_mem (atari_st_t *sim)
{
	pce_prt_sep ("MEM");
	mem_prt_state (sim->mem, stdout);
}

static
void st_print_state_psg (atari_st_t *sim)
{
	st_psg_t *psg;

	pce_prt_sep ("PSG");

	psg = &sim->psg;

	pce_printf ("R%02X=%02X  PA=%u\n", 0, psg->reg[0],
		((psg->reg[1] << 8) | (psg->reg[0])) & 0x3ff
	);

	pce_printf ("R%02X=%02X\n", 1, psg->reg[1]);

	pce_printf ("R%02X=%02X  PB=%u\n", 2, psg->reg[2],
		((psg->reg[3] << 8) | (psg->reg[2])) & 0x3ff
	);

	pce_printf ("R%02X=%02X\n", 3, psg->reg[3]);

	pce_printf ("R%02X=%02X  PC=%u\n", 4, psg->reg[4],
		((psg->reg[5] << 8) | (psg->reg[4])) & 0x3ff
	);

	pce_printf ("R%02X=%02X\n", 5, psg->reg[5]);

	pce_printf ("R%02X=%02X  PN=%u\n", 6, psg->reg[6],
		psg->reg[6] & 0x1f
	);

	pce_printf ("R%02X=%02X  N=%c%c%c  T=%c%c%c\n", 7, psg->reg[7],
		(psg->reg[7] & 8) ? 'a' : 'A',
		(psg->reg[7] & 16) ? 'b' : 'B',
		(psg->reg[7] & 32) ? 'c' : 'C',
		(psg->reg[7] & 1) ? 'a' : 'A',
		(psg->reg[7] & 2) ? 'b' : 'B',
		(psg->reg[7] & 4) ? 'c' : 'C'
	);

	pce_printf ("R%02X=%02X  VA=%u  %c\n", 8, psg->reg[8],
		psg->reg[8] & 0x0f,
		(psg->reg[8] & 0x10) ? 'M' : '-'
	);

	pce_printf ("R%02X=%02X  VB=%u  %c\n", 9, psg->reg[9],
		psg->reg[9] & 0x0f,
		(psg->reg[9] & 0x10) ? 'M' : '-'
	);

	pce_printf ("R%02X=%02X  VC=%u  %c\n", 10, psg->reg[10],
		psg->reg[10] & 0x0f,
		(psg->reg[10] & 0x10) ? 'M' : '-'
	);

	pce_printf ("R%02X=%02X  PE=%u\n", 11, psg->reg[11],
		((psg->reg[12] << 8) | (psg->reg[11])) & 0xffff
	);

	pce_printf ("R%02X=%02X\n", 12, psg->reg[12]);

	pce_printf ("R%02X=%02X  ENV=%X\n", 13, psg->reg[13],
		psg->reg[13] & 0x0f
	);

	pce_printf ("R%02X=%02X\n", 14, psg->reg[14]);
	pce_printf ("R%02X=%02X\n", 15, psg->reg[15]);
}

static
void st_print_state_video (atari_st_t *sim)
{
	unsigned   i;
	st_video_t *vid;

	vid = sim->video;

	pce_prt_sep ("VIDEO");

	pce_printf ("B=%06lX  A=%06lX  HB=%-3d  VB=%-3d\n",
		vid->base, vid->addr, vid->hb_val != 0, vid->vb_val != 0
	);

	pce_printf ("X=%-3u  HB1=%-3u  HB2=%-3u  %lu Hz\n",
		vid->clk, vid->hb1, vid->hb2,
		(8000000UL + vid->hb2 / 2) / vid->hb2
	);

	pce_printf ("Y=%-3u  VB1=%-3u  VB2=%-3u  %lu Hz\n",
		vid->line, vid->vb1, vid->vb2,
		(8000000UL + (vid->hb2 * vid->vb2) / 2) / (vid->hb2 * vid->vb2)
	);

	for (i = 0; i < 8; i++) {
		pce_printf ("PAL%X=%04X [%02X %02X %02X]  PAL%X=%04X [%02X %02X %02X]\n",
			i, vid->palette[i],
			vid->pal_col[i][0], vid->pal_col[i][1], vid->pal_col[i][2],
			i + 8, vid->palette[i + 8],
			vid->pal_col[i + 8][0], vid->pal_col[i + 8][1], vid->pal_col[i + 8][2]
		);
	}
}

static
void st_print_state (atari_st_t *sim, const char *str)
{
	cmd_t cmd;

	cmd_set_str (&cmd, str);

	if (cmd_match_eol (&cmd)) {
		return;
	}

	while (!cmd_match_eol (&cmd)) {
		if (cmd_match (&cmd, "cpu")) {
			st_print_state_cpu (sim);
		}
		else if (cmd_match (&cmd, "disks")) {
			dsks_print_info (sim->dsks);
		}
		else if (cmd_match (&cmd, "dma")) {
			st_print_state_dma (sim);
		}
		else if (cmd_match (&cmd, "mem")) {
			st_print_state_mem (sim);
		}
		else if (cmd_match (&cmd, "acia0")) {
			st_print_state_acia (sim, 0);
		}
		else if (cmd_match (&cmd, "acia1")) {
			st_print_state_acia (sim, 1);
		}
		else if (cmd_match (&cmd, "mfp")) {
			st_print_state_mfp (sim);
		}
		else if (cmd_match (&cmd, "psg")) {
			st_print_state_psg (sim);
		}
		else if (cmd_match (&cmd, "video")) {
			st_print_state_video (sim);
		}
		else {
			pce_printf ("unknown component (%s)\n", cmd_get_str (&cmd));
			return;
		}
	}
}


/*
 * Check if a breakpoint has triggered
 */
static
int st_check_break (atari_st_t *sim)
{
	unsigned long pc;

	pc = e68_get_pc (sim->cpu) & 0x00ffffff;

	if (bps_check (&sim->bps, 0, pc, stdout)) {
		return (1);
	}

	if (sim->brk) {
		return (1);
	}

	return (0);
}

/*
 * Execute one instruction
 */
static
int st_exec (atari_st_t *sim)
{
	unsigned long old;

	old = e68_get_opcnt (sim->cpu);

	while (e68_get_opcnt (sim->cpu) == old) {
		st_clock (sim, 1);

		if (st_check_break (sim)) {
			return (1);
		}
	}

	return (0);
}

/*
 * Execute until a specific PC is reached
 */
static
int st_exec_to (atari_st_t *sim, unsigned long addr)
{
	while (e68_get_pc (sim->cpu) != addr) {
		st_clock (sim, 1);

		if (st_check_break (sim)) {
			return (1);
		}
	}

	return (0);
}

/*
 * Run the simulation
 */
void st_run (atari_st_t *sim)
{
	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	while (1) {
		st_clock (par_sim, 0);

		if (sim->brk) {
			break;
		}

		while (sim->pause) {
			pce_usleep (50UL * 1000UL);
			trm_check (sim->trm);
		}
	}

	pce_stop();
}


static
void st_log_trap_bios (atari_st_t *sim, unsigned iw)
{
	unsigned       i;
	unsigned short par[8];

#if (DEBUG_BIOS == 0)
	return;
#endif

	for (i = 0; i < 8; i++) {
		par[i] = mem_get_uint16_be (sim->mem, e68_get_areg32 (sim->cpu, 7) + 2 * i);
	}

	switch (par[0]) {
	case 1:
		//st_log_deb ("bios_constat (%u)\n", par[1]);
		break;

	case 2:
		st_log_deb ("bios_conin (%u)\n", par[1]);
		break;

	case 3:
		st_log_deb ("bios_conout (%u, %u)\n", par[1], par[2]);
		break;

	case 4:
		st_log_deb ("bios_rwabs (%u, 0x%08lx, %u, %u, %u)\n",
			par[1], ((unsigned long) par[2] << 16) | par[3],
			par[4], par[5], par[6]
		);
		break;

	case 5:
		st_log_deb ("bios_setexc (%u, %lu)\n",
			par[1], ((unsigned long) par[2] << 16) | par[3]
		);
		break;

	case 9:
		st_log_deb ("bios_media_change (%u)\n", par[1]);
		break;

	default:
		st_log_deb ("bios (%u, %u, %u, %u)\n", par[0], par[1], par[2], par[3]);
		break;
	}
}

static
void st_log_exception (void *ext, unsigned tn)
{
	unsigned  iw;
	atari_st_t *sim = ext;

	iw = e68_get_mem16 (sim->cpu, e68_get_last_pc (sim->cpu, 0));

	switch (tn) {
	case 0x00:
		st_reset (sim);
		return;

	case 0x02: /* BUSE */
		return;

	case 0x04: /* ILLG */
	case 0x09: /* TRACE */
	case 0x0a: /* AXXX */
	case 0x0b: /* FXXX */
		return;

	case 0x1a: /* AVEC 2 */
	case 0x1c: /* AVEC 4 */
	case 0x1e: /* AVEC 6 */
		return;

	case 0x20: /* trap */
	case 0x21:
	case 0x22:
	case 0x27:
	case 0x28:
	case 0x29:
	case 0x2a:
	case 0x2b:
	case 0x2c:
	case 0x2e:
	case 0x2f:
		return;

	case 0x2d:
		st_log_trap_bios (sim, iw);
		return;

	case 0x06:
	case 0x08:
	case 0x42: /* MFP RS232 CTS */
	case 0x44: /* MFP Timer D */
	case 0x45: /* MFP Timer C */
	case 0x46: /* MFP I4 (ACIA0) */
	case 0x48: /* HB counter */
	case 0x49: /* USART send data */
	case 0x4a: /* USART send error */
	case 0x4b: /* USART recv data */
	case 0x4c: /* USART recv error */
	case 0x4d: /* MFP Timer A */
		return;
	}

	pce_log (MSG_DEB,
		"%08lX: exception %02X (%s) IW=%04X\n",
		(unsigned long) e68_get_last_pc (sim->cpu, 0),
		tn, e68_get_exception_name (sim->cpu), iw
	);
}


/*
 * c - clock
 */
static
void st_cmd_c (cmd_t *cmd, atari_st_t *sim)
{
	unsigned long cnt;

	cnt = 1;

	cmd_match_uint32 (cmd, &cnt);

	if (!cmd_match_end (cmd)) {
		return;
	}

	while (cnt > 0) {
		st_clock (sim, 1);
		cnt -= 1;
	}

	st_print_state_cpu (sim);
}

/*
 * gb - run with breakpoints
 */
static
void st_cmd_g_b (cmd_t *cmd, atari_st_t *sim)
{
	unsigned long addr;
	breakpoint_t  *bp;

	while (cmd_match_uint32 (cmd, &addr)) {
		bp = bp_addr_new (addr);
		bps_bp_add (&sim->bps, bp);
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	while (1) {
		if (st_exec (sim)) {
			break;
		}
	}

	pce_stop();
}

/*
 * ge - run until an exception is raised
 */
static
void st_cmd_g_e (cmd_t *cmd, atari_st_t *sim)
{
	unsigned short tn;
	unsigned       cnt;

	if (!cmd_match_uint16 (cmd, &tn)) {
		tn = 0xffff;
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	cnt = e68_get_exception_cnt (sim->cpu);

	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	while (1) {
		st_exec (sim);

		if (st_check_break (sim)) {
			break;
		}

		if (e68_get_exception_cnt (sim->cpu) == cnt) {
			continue;
		}

		if (tn == 0xffff) {
			pce_printf ("exception %02X (%s)\n",
				e68_get_exception (sim->cpu),
				e68_get_exception_name (sim->cpu)
			);
			break;
		}
		else {
			if (e68_get_exception (sim->cpu) == tn) {
				pce_printf ("exception %02X (%s)\n",
					e68_get_exception (sim->cpu),
					e68_get_exception_name (sim->cpu)
				);
				break;
			}
		}

	}

	pce_stop();
}

/*
 * g - run
 */
static
void st_cmd_g (cmd_t *cmd, atari_st_t *sim)
{
	if (cmd_match (cmd, "b")) {
		st_cmd_g_b (cmd, sim);
		return;
	}
	else if (cmd_match (cmd, "e")) {
		st_cmd_g_e (cmd, sim);
		return;
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	st_run (sim);
}

/*
 * halt - halt the cpu
 */
static
void st_cmd_halt (cmd_t *cmd, atari_st_t *sim)
{
	unsigned short val;

	if (cmd_match_uint16 (cmd, &val) == 0) {
		val = 2;
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	e68_set_halt (sim->cpu, val);

	st_print_state_cpu (sim);
}

/*
 * hm - help on messages
 */
static
void st_cmd_hm (cmd_t *cmd)
{
	pce_puts (
		"emu.exit\n"
		"emu.stop\n"
		"emu.pause            \"0\" | \"1\"\n"
		"emu.pause.toggle\n"
		"emu.reset\n"
		"\n"
		"emu.cpu.model        \"68000\" | \"68010\" | \"68020\"\n"
		"emu.cpu.speed        <factor>\n"
		"emu.cpu.speed.step   <adjustment>\n"
		"\n"
		"emu.midi.file        <fname>\n"
		"\n"
		"emu.fdc.ro           <drive>\n"
		"emu.fdc.rw           <drive>\n"
		"\n"
		"emu.par.driver       <driver>\n"
		"emu.par.file         <filename>\n"
		"\n"
		"emu.psg.aym.file     <filename>\n"
		"emu.psg.aym.res      <usec>\n"
		"emu.psg.driver       <driver>\n"
		"emu.psg.lowpass      <freq>\n"
		"\n"
		"emu.ser.driver       <driver>\n"
		"emu.ser.file         <filename>\n"
		"\n"
		"emu.viking           \"0\" | \"1\"\n"
		"emu.viking.toggle\n"
		"\n"
		"term.fullscreen      \"0\" | \"1\"\n"
		"term.fullscreen.toggle\n"
		"term.grab\n"
		"term.release\n"
		"term.screenshot      [<filename>]\n"
		"term.title           <title>\n"
		"\n"
	);

	msg_dsk_print_help();
}

/*
 * p - step
 */
static
void st_cmd_p (cmd_t *cmd, atari_st_t *sim)
{
	unsigned      ecnt;
	unsigned long cnt;
	e68_dasm_t    da;

	cnt = 1;

	while (cmd_match (cmd, "p")) {
		cnt += 1;
	}

	cmd_match_uint32 (cmd, &cnt);

	if (!cmd_match_end (cmd)) {
		return;
	}

	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	while (cnt > 0) {
		e68_dasm_mem (sim->cpu, &da, e68_get_pc (sim->cpu));

		if (da.flags & E68_DFLAG_CALL) {
			if (st_exec_to (sim, e68_get_pc (sim->cpu) + 2 * da.irn)) {
				break;
			}
		}
		else {
			ecnt = e68_get_exception_cnt (sim->cpu);

			if (st_exec (sim)) {
				break;
			}

			if (e68_get_exception_cnt (sim->cpu) != ecnt) {
				if (st_exec_to (sim, sim->cpu->except_addr)) {
					break;
				}
			}
		}

		cnt -= 1;
	}

	pce_stop();

	st_print_state_cpu (sim);
}

/*
 * n - run to following instruction
 */
static
void st_cmd_n (cmd_t *cmd, atari_st_t *sim)
{
	unsigned long cnt;
	e68_dasm_t    da;

	cnt = 1;

	while (cmd_match (cmd, "n")) {
		cnt += 1;
	}

	cmd_match_uint32 (cmd, &cnt);

	if (!cmd_match_end (cmd)) {
		return;
	}

	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	while (cnt > 0) {
		e68_dasm_mem (sim->cpu, &da, e68_get_pc (sim->cpu));

		if (st_exec_to (sim, e68_get_pc (sim->cpu) + 2 * da.irn)) {
			break;
		}

		cnt -= 1;
	}

	pce_stop();

	st_print_state_cpu (sim);
}

/*
 * reset - reset the simulation
 */
static
void st_cmd_reset (cmd_t *cmd, atari_st_t *sim)
{
	if (!cmd_match_end (cmd)) {
		return;
	}

	st_reset (sim);

	st_print_state_cpu (sim);
}

/*
 * rte - execute until rte
 */
static
void st_cmd_rte (cmd_t *cmd, atari_st_t *sim)
{
	e68_dasm_t dis;

	if (!cmd_match_end (cmd)) {
		return;
	}

	pce_start (&sim->brk);

	while (1) {
		st_exec (sim);

		if (st_check_break (sim)) {
			break;
		}

		e68_dasm_mem (sim->cpu, &dis, e68_get_pc (sim->cpu));

		if (dis.flags & E68_DFLAG_RTE) {
			st_print_state_cpu (sim);
			break;
		}
	}

	pce_stop();
}

/*
 * r - display or set register contents
 */
static
void st_cmd_r (cmd_t *cmd, atari_st_t *sim)
{
	unsigned long val;
	char          sym[256];

	if (cmd_match_eol (cmd)) {
		st_print_state_cpu (sim);
		return;
	}

	if (!cmd_match_ident (cmd, sym, 256)) {
		cmd_error (cmd, "missing register\n");
		return;
	}

	if (e68_get_reg (sim->cpu, sym, &val)) {
		pce_printf ("bad register (%s)\n", sym);
		return;
	}

	if (cmd_match_eol (cmd)) {
		pce_printf ("%08lX\n", val);
		return;
	}

	if (!cmd_match_uint32 (cmd, &val)) {
		cmd_error (cmd, "missing value\n");
		return;
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	e68_set_reg (sim->cpu, sym, val);

	st_print_state_cpu (sim);
}

/*
 * s - print state
 */
static
void st_cmd_s (cmd_t *cmd, atari_st_t *sim)
{
	if (cmd_match_eol (cmd)) {
		st_print_state_cpu (sim);
		return;
	}

	st_print_state (sim, cmd_get_str (cmd));
}

/*
 * t - execute one instruction
 */
static
void st_cmd_t (cmd_t *cmd, atari_st_t *sim)
{
	unsigned long i, n, k;

	n = 1;
	k = 0;

	while (cmd_match (cmd, "t")) {
		n += 1;
	}

	cmd_match_uint32 (cmd, &n);
	cmd_match_uint32 (cmd, &k);

	if (!cmd_match_end (cmd)) {
		return;
	}

	pce_start (&sim->brk);

	st_clock_discontinuity (sim);

	for (i = 0; i < n; i++) {
		if ((n > 1) && (i >= k)) {
			st_print_state_cpu_short (sim->cpu);
		}

		st_exec (sim);
	}

	pce_stop();

	st_print_state_cpu (sim);
}

/*
 * u- - disassemble to address
 */
static
void st_cmd_u_to (cmd_t *cmd, atari_st_t *sim, unsigned long addr)
{
	unsigned long cnt;
	unsigned      ins_i, ins_j, ins_n;
	unsigned      sync;
	unsigned long pc;
	unsigned long insn[256];
	e68_dasm_t    op;
	char          str[256];

	cnt = 16;

	if (cmd_match_uint32 (cmd, &addr)) {
		cmd_match_uint32 (cmd, &cnt);
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	if (cnt > 256) {
		cnt = 256;
	}

	ins_i = 0;
	ins_j = 0;
	ins_n = 0;
	sync = 5;

	pc = (addr - 12 * cnt) & ~1UL;

	while (pc <= addr) {
		if (sync == 0) {
			insn[ins_j] = pc;
			ins_j = (ins_j + 1) % 256;
			if (ins_j == ins_i) {
				ins_i = (ins_i + 1) % 256;
			}
			else {
				ins_n += 1;
			}
		}
		else {
			sync -= 1;
		}

		e68_dasm_mem (sim->cpu, &op, pc);

		pc += 2 * op.irn;
	}

	if (ins_n > cnt) {
		ins_i = (ins_i + (ins_n - cnt)) % 256;
	}

	while (ins_i != ins_j) {
		pc = insn[ins_i];

		e68_dasm_mem (sim->cpu, &op, pc);
		st_dasm_str (str, &op, 1);

		pce_printf ("%08lX  %s\n", pc, str);

		ins_i = (ins_i + 1) % 256;
	}


}

/*
 * uw - disassemble as constant words
 */
static
void st_cmd_u_w (cmd_t *cmd, atari_st_t *sim)
{
	unsigned             i, col;
	unsigned long        addr, cnt;
	e68_dasm_t           op;
	char                 str[256];

	if (cmd_match_uint32 (cmd, &addr) == 0) {
		addr = 0;
	}

	if (cmd_match_uint32 (cmd, &cnt) == 0) {
		cnt = 256;
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	while (1) {
		e68_dasm_mem (sim->cpu, &op, addr);
		st_dasm_str (str, &op, 0);

		pce_printf (".word 0x%04x", op.ir[0]);

		for (i = 1; i < op.irn; i++) {
			pce_printf (", 0x%04x", op.ir[i]);
		}

		col = 4 + 8 * op.irn;

		while (col < 32) {
			pce_printf ("\t");
			col = (col + 8) & ~7;
		}

		pce_printf ("\t/* %06lX   %s */\n", addr, str);

		if (op.flags & E68_DFLAG_RTS) {
			pce_printf ("\n");
		}

		if (cnt <= (2 * op.irn)) {
			break;
		}

		addr += 2 * op.irn;
		cnt -= 2 * op.irn;
	}
}

/*
 * u - disassemble
 */
static
void st_cmd_u (cmd_t *cmd, atari_st_t *sim)
{
	unsigned             i;
	unsigned long        addr, cnt;
	static unsigned int  first = 1;
	static unsigned long saddr = 0;
	e68_dasm_t           op;
	char                 str[256];

	if (cmd_match (cmd, "w")) {
		st_cmd_u_w (cmd, sim);
		return;
	}

	if (first) {
		first = 0;
		saddr = e68_get_pc (sim->cpu);
	}

	addr = saddr;
	cnt = 16;

	if (cmd_match (cmd, "-")) {
		st_cmd_u_to (cmd, sim, addr);
		return;
	}

	if (cmd_match_uint32 (cmd, &addr)) {
		cmd_match_uint32 (cmd, &cnt);
	}

	if (!cmd_match_end (cmd)) {
		return;
	}

	for (i = 0; i < cnt; i++) {
		e68_dasm_mem (sim->cpu, &op, addr);
		st_dasm_str (str, &op, 1);

		pce_printf ("%08lX  %s\n", addr, str);

		addr += 2 * op.irn;
	}

	saddr = addr;
}

int st_cmd (atari_st_t *sim, cmd_t *cmd)
{
	if (sim->trm != NULL) {
		trm_check (sim->trm);
	}

	if (cmd_match (cmd, "b")) {
		cmd_do_b (cmd, &sim->bps);
	}
	else if (cmd_match (cmd, "c")) {
		st_cmd_c (cmd, sim);
	}
	else if (cmd_match (cmd, "g")) {
		st_cmd_g (cmd, sim);
	}
	else if (cmd_match (cmd, "halt")) {
		st_cmd_halt (cmd, sim);
	}
	else if (cmd_match (cmd, "hm")) {
		st_cmd_hm (cmd);
	}
	else if (cmd_match (cmd, "p")) {
		st_cmd_p (cmd, sim);
	}
	else if (cmd_match (cmd, "n")) {
		st_cmd_n (cmd, sim);
	}
	else if (cmd_match (cmd, "reset")) {
		st_cmd_reset (cmd, sim);
	}
	else if (cmd_match (cmd, "rte")) {
		st_cmd_rte (cmd, sim);
	}
	else if (cmd_match (cmd, "r")) {
		st_cmd_r (cmd, sim);
	}
	else if (cmd_match (cmd, "s")) {
		st_cmd_s (cmd, sim);
	}
	else if (cmd_match (cmd, "t")) {
		st_cmd_t (cmd, sim);
	}
	else if (cmd_match (cmd, "u")) {
		st_cmd_u (cmd, sim);
	}
	else {
		return (1);
	}

	if (sim->trm != NULL) {
		trm_set_msg_trm (sim->trm, "term.release", "1");
	}

	return (0);
}

void st_cmd_init (atari_st_t *sim, monitor_t *mon)
{
	mon_cmd_add (mon, par_cmd, sizeof (par_cmd) / sizeof (par_cmd[0]));
	mon_cmd_add_bp (mon);

	sim->cpu->log_ext = sim;
	sim->cpu->log_opcode = NULL;
	sim->cpu->log_undef = NULL;
	sim->cpu->log_exception = st_log_exception;
	sim->cpu->log_mem = NULL;
}
