/*
 * Z80SIM  -  a Z80-CPU simulator
 *
 * This module allows operation of the system from an Altair 8800 front panel
 *
 * Copyright (C) 2008-2016 by Udo Munk
 *
 * History:
 * 20-OCT-08 first version finished
 * 26-OCT-08 corrected LED status while RESET is hold in upper position
 * 27-JAN-14 set IFF=0 when powered off, so that LED goes off
 * 02-MAR-14 source cleanup and improvements
 * 14-MAR-14 added Tarbell SD FDC and printer port
 * 15-APR-14 added fflush() for teletype
 * 19-APR-14 moved CPU error report into a function
 * 06-JUN-14 forgot to disable timer interrupts when machine switched off
 * 10-JUN-14 increased fp operation timer from 1ms to 100ms
 * 29-APR-15 added Cromemco DAZZLER to the machine
 * 01-MAR-16 added sleep for threads before switching tty to raw mode
 * 08-MAY-16 frontpanel configuration with path support
 * 06-DEC-16 implemented status display and stepping for all machine cycles
 * 23-DEC-16 implemented memory protect/unprotect
 */

#include <X11/Xlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include "sim.h"
#include "simglb.h"
#include "config.h"
#include "../../frontpanel/frontpanel.h"
#include "memory.h"
#include "../../iodevices/cromemco-dazzler.h"
#include "../../iodevices/unix_terminal.h"

extern int load_file(char *);
extern int load_core(void);
extern void cpu_z80(void), cpu_8080(void);

static BYTE fp_led_wait;
static int cpu_switch;
static int reset;
static BYTE power_switch = 1;
static int power;

static int load(void);
static void run_cpu(void), step_cpu(void);
static void run_clicked(int, int), step_clicked(int, int);
static void reset_clicked(int, int);
static void examine_clicked(int, int), deposit_clicked(int, int);
static void protect_clicked(int, int);
static void power_clicked(int, int);
static void int_clicked(int, int);
static void quit_callback(void);

/*
 *	This function initialises the front panel and terminal.
 *	Boot code gets loaded if provided and then the machine
 *	waits to be operated from the front panel, until power
 *	switched OFF again.
 */
void mon(void)
{
	static struct timespec timer;
	static struct sigaction newact;

	/*load memory from file */
	if (load())
		exit(1);

	/* initialize frontpanel */
	XInitThreads();

	if (!fp_init2(&confdir[0], "panel.conf")) {
		puts("frontpanel error");
		exit(1);
	}

	fp_addQuitCallback(quit_callback);
	fp_framerate(30.0);
	fp_bindSimclock(&fp_clock);
	fp_bindRunFlag(&cpu_state);

	/* bind frontpanel LED's to variables */
	fp_bindLight16("LED_ADDR_{00-15}", &fp_led_address, 1);
	fp_bindLight8("LED_DATA_{00-07}", &fp_led_data, 1);
	fp_bindLight8("LED_STATUS_{00-07}", &cpu_bus, 1);
	fp_bindLight8("LED_WAIT", &fp_led_wait, 1);
	fp_bindLight8("LED_INTEN", &IFF, 1);
	fp_bindLight8("LED_PROT", &mem_wp, 1);
	fp_bindLight8("LED_HOLD", &bus_request, 1);

	/* bind frontpanel switches to variables */
	fp_bindSwitch16("SW_{00-15}", &address_switch, &address_switch, 1);
	fp_bindSwitch8("SW_PWR", &power_switch, &power_switch, 1);
	fp_sampleSwitches();

	/* add callbacks for frontpanel switches */
	fp_addSwitchCallback("SW_RUN", run_clicked, 0);
	fp_addSwitchCallback("SW_STEP", step_clicked, 0);
	fp_addSwitchCallback("SW_RESET", reset_clicked, 0);
	fp_addSwitchCallback("SW_EXAMINE", examine_clicked, 0);
	fp_addSwitchCallback("SW_DEPOSIT", deposit_clicked, 0);
	fp_addSwitchCallback("SW_PROTECT", protect_clicked, 0);
	fp_addSwitchCallback("SW_PWR", power_clicked, 0);
        fp_addSwitchCallback("SW_INT", int_clicked, 0);

	/* give threads a bit time and then empty buffer */
	sleep(1);
	fflush(stdout);

	/* initialise terminal */
	set_unix_terminal();

	/* operate machine from front panel */
	while (cpu_error == NONE) {
		if (reset) {
			cpu_bus = 0;
			fp_led_address = 0xffff;
			fp_led_data = 0xff;
		} else {
			if (power) {
				fp_led_address = PC;
				if ((p_tab[PC >> 8] == MEM_RO) ||
				    (p_tab[PC >> 8] == MEM_WPROT))
					mem_wp = 1;
				else
					mem_wp = 0;
				if (!(cpu_bus & CPU_INTA))
					fp_led_data = *(mem_base() + PC);
				else
					fp_led_data = (int_data) ?
							int_data : 0xff;
			}
		}

		fp_clock++;
		fp_sampleData();

		switch (cpu_switch) {
		case 1:
			if (!reset) run_cpu();
			break;
		case 2:
			step_cpu();
			if (cpu_switch == 2)
				cpu_switch = 0;
			break;
		default:
			break;
		}

		fp_clock++;
		fp_sampleData();

		timer.tv_sec = 0;
		timer.tv_nsec = 10000000L;
		nanosleep(&timer, NULL);
	}

	/* timer interrupts off */
	newact.sa_handler = SIG_IGN;
	memset((void *) &newact.sa_mask, 0, sizeof(newact.sa_mask));
	newact.sa_flags = 0;
	sigaction(SIGALRM, &newact, NULL);

	/* reset terminal */
	reset_unix_terminal();
	putchar('\n');

	/* all LED's off and update front panel */
	cpu_bus = 0;
	bus_request = 0;
	IFF = 0;
	fp_led_wait = 0;
	fp_led_address = 0;
	fp_led_data = 0;
	fp_sampleData();

	/* wait a bit before termination */
	sleep(1);

	fp_quit();
}

/*
 *	Load code into memory from file, if provided
 */
int load(void)
{
	if (l_flag) {
		return(load_core());
	}

	if (x_flag) {
		return(load_file(xfn));
	}

	return(0);
}

/*
 *	Report CPU error
 */
void report_error(void)
{
	switch (cpu_error) {
	case NONE:
		break;
	case OPHALT:
		printf("\r\nINT disabled and HALT Op-Code reached at %04x\r\n",
		       PC - 1);
		break;
	case IOTRAPIN:
		printf("\r\nI/O input Trap at %04x, port %02x\r\n", PC, io_port);
		break;
	case IOTRAPOUT:
		printf("\r\nI/O output Trap at %04x, port %02x\r\n", PC, io_port);
		break;
	case IOHALT:
		printf("\r\nSystem halted, bye.\r\n");
		break;
	case IOERROR:
		printf("\r\nFatal I/O Error at %04x\r\n", PC);
		break;
	case OPTRAP1:
		printf("\r\nOp-code trap at %04x %02x\r\n", PC - 1,
		       *(mem_base() + PC - 1));
		break;
	case OPTRAP2:
		printf("\r\nOp-code trap at %04x %02x %02x\r\n",
		       PC - 2, *(mem_base() + PC - 2),
		       *(mem_base() + PC - 1));
		break;
	case OPTRAP4:
		printf("\r\nOp-code trap at %04x %02x %02x %02x %02x\r\n",
		       PC - 4, *(mem_base() + PC - 4), *(mem_base() + PC - 3),
		       *(mem_base() + PC - 2), *(mem_base() + PC - 1));
		break;
	case USERINT:
		printf("\r\nUser Interrupt at %04x\r\n", PC);
		break;
	case POWEROFF:
		printf("\r\nSystem powered off, bye.\r\n");
		break;
	default:
		printf("\r\nUnknown error %d\r\n", cpu_error);
		break;
	}
}

/*
 *	Run CPU
 */
void run_cpu(void)
{
	cpu_state = CONTIN_RUN;
	cpu_error = NONE;
	switch(cpu) {
	case Z80:
		cpu_z80();
		break;
	case I8080:
		cpu_8080();
		break;
	}
	report_error();
}

/*
 *	Step CPU
 */
void step_cpu(void)
{
	cpu_state = SINGLE_STEP;
	cpu_error = NONE;
	switch(cpu) {
	case Z80:
		cpu_z80();
		break;
	case I8080:
		cpu_8080();
		break;
	}
	cpu_state = STOPPED;
	report_error();
}

/*
 *	Callback for RUN/STOP switch
 */
void run_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	switch (state) {
	case FP_SW_DOWN:
		if (cpu_state != CONTIN_RUN) {
			cpu_state = CONTIN_RUN;
			fp_led_wait = 0;
			cpu_switch = 1;
		}
		break;
	case FP_SW_UP:
		if (cpu_state == CONTIN_RUN) {
			cpu_state = STOPPED;
			fp_led_wait = 1;
			cpu_switch = 0;
		}
		break;
	default:
		break;
	}
}

/*
 *	Callback for STEP switch
 */
void step_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	if (cpu_state == CONTIN_RUN)
		return;

	switch (state) {
	case FP_SW_UP:
		cpu_switch = 2;
		break;
	case FP_SW_DOWN:
		break;
	default:
		break;
	}
}

/*
 * Singe step through the machine cycles after first M1
 */
void wait_step(void)
{
	static struct timespec timer;

	if (cpu_state != SINGLE_STEP) {
		cpu_bus &= ~CPU_M1;
		m1_step = 0;
		return;
	}

	if ((cpu_bus & CPU_M1) && !m1_step) {
		cpu_bus &= ~CPU_M1;
		return;
	}

	cpu_switch = 3;

	while ((cpu_switch == 3) && !reset) {
		fp_clock++;
		fp_sampleData();
		timer.tv_sec = 0;
		timer.tv_nsec = 10000000L;
		nanosleep(&timer, NULL);
	}

	cpu_bus &= ~CPU_M1;
	m1_step = 0;
}

/*
 * Singe step through interrupt machine cycles
 */
void wait_int_step(void)
{
	static struct timespec timer;

	if (cpu_state != SINGLE_STEP)
		return;

	cpu_switch = 3;

	while ((cpu_switch == 3) && !reset) {
		fp_clock++;
		fp_sampleData();
		timer.tv_sec = 0;
		timer.tv_nsec = 10000000L;
		nanosleep(&timer, NULL);
	}
}

/*
 *	Callback for RESET switch
 */
void reset_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	switch (state) {
	case FP_SW_UP:
		reset = 1;
		cpu_state |= RESET;
		IFF = 0;
		int_int = 0;
		int_nmi = 0;
		int_data = 0;
		int_protection = 0;
		m1_step = 0;
		break;
	case FP_SW_CENTER:
		if (reset) {
			cpu_state &= ~RESET;
			reset = 0;
			PC = 0;
			if (cpu == Z80)
				SP = 0xffff;
			int_mode = 0;
			fp_led_address = 0;
			fp_led_data = *(mem_base());
			if ((p_tab[0] == MEM_RO) || (p_tab[0] == MEM_WPROT))
				mem_wp = 1;
			else
				mem_wp = 0;
			cpu_bus = CPU_WO | CPU_M1 | CPU_MEMR;
			cromemco_dazzler_off();
		}
		break;
	case FP_SW_DOWN:
		cromemco_dazzler_off();
		break;
	default:
		break;
	}
}

/*
 *	Callback for EXAMINE/EXAMINE NEXT switch
 */
void examine_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	if (cpu_state == CONTIN_RUN)
		return;

	switch (state) {
	case FP_SW_UP:
		fp_led_address = address_switch;
		fp_led_data = *(mem_base() + address_switch);
		PC = address_switch;
		if ((p_tab[PC >> 8] == MEM_RO) || (p_tab[PC >> 8] == MEM_WPROT))
			mem_wp = 1;
		else
			mem_wp = 0;
		break;
	case FP_SW_DOWN:
		fp_led_address++;
		fp_led_data = *(mem_base() + fp_led_address);
		PC = fp_led_address;
		if ((p_tab[PC >> 8] == MEM_RO) || (p_tab[PC >> 8] == MEM_WPROT))
			mem_wp = 1;
		else
			mem_wp = 0;
		break;
	default:
		break;
	}
}

/*
 *	Callback for DEPOSIT/DEPOSIT NEXT switch
 */
void deposit_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	if (cpu_state == CONTIN_RUN)
		return;

	if ((p_tab[PC >> 8] == MEM_RO) || (p_tab[PC >> 8] == MEM_WPROT)) {
		mem_wp = 1;
		return;
	} else {
		mem_wp = 0;
	}

	switch (state) {
	case FP_SW_UP:
		fp_led_data = *(mem_base() + PC) = address_switch & 0xff;
		break;
	case FP_SW_DOWN:
		PC++;
		fp_led_address++;
		fp_led_data = *(mem_base() + PC) = address_switch & 0xff;
		break;
	default:
		break;
	}
}

/*
 *	Callback for PROTECT/UNPROTECT switch
 */
void protect_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	if (cpu_state == CONTIN_RUN)
		return;

	switch (state) {
	case FP_SW_UP:
		if (p_tab[PC >> 8] == MEM_RW) {
			p_tab[PC >> 8] = MEM_WPROT;
			mem_wp = 1;
		}
		break;
	case FP_SW_DOWN:
		if (p_tab[PC >> 8] == MEM_WPROT) {
			p_tab[PC >> 8] = MEM_RW;
			mem_wp = 0;
		}
		break;
	default:
		break;
	}
}

/*
 *	Callback for INT/BOOT switch
 */
void int_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	if (!power)
		return;

	switch (state) {
	case FP_SW_UP:
		int_int = 1;
		break;
	case FP_SW_DOWN:
		fp_led_address = boot_switch;
		fp_led_data = *(mem_base() + boot_switch);
		PC = boot_switch;
		break;
	default:
		break;
	}
}

/*
 *	Callback for POWER switch
 */
void power_clicked(int state, int val)
{
	val = val;	/* to avoid compiler warning */

	switch (state) {
	case FP_SW_DOWN:
		if (power)
			break;
		power++;
		cpu_bus = CPU_WO | CPU_M1 | CPU_MEMR;
		fp_led_address = PC;
		fp_led_data = *(mem_base() + PC);
		fp_led_wait = 1;
		system("tput clear");
		break;
	case FP_SW_UP:
		if (!power)
			break;
		power--;
		cpu_switch = 0;
		cpu_state = STOPPED;
		cpu_error = POWEROFF;
		cromemco_dazzler_off();
		break;
	default:
		break;
	}
}

/*
 * Callback for quit (graphics window closed)
 */
void quit_callback(void)
{
	power--;
	cpu_switch = 0;
	cpu_state = STOPPED;
	cpu_error = POWEROFF;
	cromemco_dazzler_off();
}
