/*
 * Copyright (c) Des Herriott 1993, 1994
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holder not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The copyright holder makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Des Herriott
 */

/* Source file loop.c - this is the heart of the Z80 emulation. */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>

#include "z80.h"
#include "mem.h"
#include "rtcfg.h"
#include "util.h"
#ifdef LOAD_SAVE
#include "loadsave.h"
#endif
#ifdef LEVEL_LOADER
#include "snapshot.h"
#endif

#ifdef XZX_IF1
extern void rs232_ip_trap(void), rs232_op_trap(void);
#endif

#ifndef __GNUC__
#define inline
#endif

extern uns8 ixiy_tbl[];
extern uns8 parity_tbl[];
extern int hcarry_tbl[], sub_hcarry_tbl[];
extern int overflow_tbl[], sub_overflow_tbl[];

static uns16 work16;    /* 16-bit temporary workspace */
static uns8  work8;     /* 8-bit  temporary workspace */
static uns8  work8_2;   /* 8-bit  temporary secondary workspace */
static uns32 work32;    /* 32-bit temporary workspace */
static int cin7;		/* carry-in on bit 7 */
static int idx;			/* for half-carry & overflow detection */

static uns8 high_nib, low_nib, add, carry, subflg;	/* used by DAA */

static sgn8 disp;			/* IX/IY displacement byte */
static int ixiy_op;
static uns16 *safe_hl;

static int interrupted;		/* set if an interrupt is pending */
int basic_rom;				/* in which ROM does BASIC live? */
int instrs;					/* no. of instructions executed since powerup */
int n_interrupts;			/* no. of interrupts since powerup */
#ifdef DEBUG
int last_instrs;
#endif


/* Since the flags are stored in ints instead of in the f register itself,
 * we must store/retrieve them whenever the f register is read/written.
 * Fortunately, this doesn't happen much.
 */
void StoreFlags()
{
	if (carryFlag)    *f |= F_CARRY;    else *f &= ~F_CARRY;
	if (addsubFlag)   *f |= F_NADD;     else *f &= ~F_NADD;
	if (par_overFlag) *f |= F_OVERFLOW; else *f &= ~F_OVERFLOW;
	if (hcarryFlag)   *f |= F_HCARRY;   else *f &= ~F_HCARRY;
	if (zeroFlag)     *f |= F_ZERO;     else *f &= ~F_ZERO;
	if (signFlag)     *f |= F_NEG;      else *f &= ~F_NEG;
}

void RetrieveFlags()
{
	carryFlag    = *f & F_CARRY    ? 1 : 0;
	addsubFlag   = *f & F_NADD     ? 1 : 0;
	par_overFlag = *f & F_OVERFLOW ? 1 : 0;
	hcarryFlag   = *f & F_HCARRY   ? 1 : 0;
	zeroFlag     = *f & F_ZERO     ? 1 : 0;
	signFlag     = *f & F_NEG      ? 1 : 0;
}


/* Set the appropriate flags on an addition.  Return the new value
 * of the affected register.
 */
static inline void
add_and_test(uns8 reg2, uns8 carry)
{
	work16 = *a + reg2 + carry;
	idx = ((*a & 0x88) >> 1) | ((reg2 & 0x88) >> 2) | ((work16 & 0x88) >> 3);

	carryFlag = work16 & 0x0100;
	zeroFlag = !(work16 & 0xff);
	signFlag = work16 & 0x80;
	hcarryFlag = hcarry_tbl[idx & 0x07];
	par_overFlag = overflow_tbl[idx >> 4];
	Clr(addsubFlag);

	*a = (work16 & 0xff);
}

/* Set the appropriate flags on a subtraction.  Return the new value
 * of the affected register.
 */
static inline void
subtract_and_test(uns8 reg2, uns8 carry)
{
	work16 = *a - (reg2 + carry);
	idx = ((*a & 0x88) >> 1) | ((reg2 & 0x88) >> 2) | ((work16 & 0x88) >> 3);

	carryFlag = work16 & 0x0100;
	zeroFlag = !(work16 & 0xff);
	signFlag = work16 & 0x80;
	hcarryFlag = sub_hcarry_tbl[idx & 0x07];
	par_overFlag = sub_overflow_tbl[idx >> 4];
	Set(addsubFlag);

	*a = (work16 & 0xff);
}


/* Compare <val> with the accumulator.  Similar to subtraction,
 * but doesn't return anything, only sets some flags.
 */
static inline void
compare_and_test(uns8 val)
{
	work16 = *a - val;
	idx = ((*a & 0x88) >> 1) | ((val & 0x88) >> 2) | ((work16 & 0x88) >> 3);
	cin7 = ((*a & 0x7f) - (val & 0x7f)) & 0x80;

	carryFlag = work16 & 0x0100;
	zeroFlag = !(work16 & 0xff);
	signFlag = work16 & 0x80;
	hcarryFlag = sub_hcarry_tbl[idx & 0x07];
	par_overFlag = sub_overflow_tbl[idx >> 4];
	Set(addsubFlag);
}


/* The next two functions set the appropriate flags after an increment
 * or decrement of a given register <reg>.
 */
static inline void
inc_test(uns8 reg)
{
	par_overFlag = (reg == 0x80); 
	zeroFlag = !reg;
	signFlag = reg & 0x80;
	hcarryFlag = ((reg & 0x0F) == 0);
	Clr(addsubFlag);
}


static inline void
dec_test(uns8 reg)
{
	par_overFlag = (reg == 0x7f);
	zeroFlag = !reg;
	signFlag = reg & 0x80;
	hcarryFlag = ((reg & 0x0F) == 0x0F);
	Set(addsubFlag);
}


/* Perform a 16-bit addition, setting the appropriate flags.
 * <carry> must be either 1 or 0 on entry.
 */
static inline void
add_and_test16(uns16 reg2, int carry)
{
	work32 = *hl + reg2 + carry;
	idx = ((*hl & 0x8800) >> 9) | ((reg2 & 0x8800) >> 10) |
		  ((work32 & 0x8800) >> 11);

	carryFlag = work32 & 0x10000;
	zeroFlag = !(work32 & 0xffff);
	hcarryFlag = hcarry_tbl[idx & 0x07];
	par_overFlag = overflow_tbl[idx >> 4];
	signFlag = work32 & 0x8000;
	Clr(addsubFlag);

	*hl = (work32 & 0xffff);
}

/* Perform a 16-bit subtraction, setting the appropriate flags.
 * <carry> must be either 1 or 0 on entry.
 */
static inline void
subtract_and_test16(uns16 reg2, int carry)
{
	work32 = *hl - (reg2 + (uns16) carry);
	idx = ((*hl & 0x8800) >> 9) | ((reg2 & 0x8800) >> 10) |
		  ((work32 & 0x8800) >> 11);

	carryFlag = work32 & 0x10000;
	zeroFlag = !(work32 & 0xffff);
	hcarryFlag = sub_hcarry_tbl[idx & 0x07];
	par_overFlag = sub_overflow_tbl[idx >> 4];
	signFlag = work32 & 0x8000;
	Set(addsubFlag);

	*hl = (work32 & 0xffff);
}


/* Set the flags after the 3 bitwise operations.  These three
 * functions are very similar (or & xor are identical), but I
 * kept them separate for the sake of clarity.
 */
static inline void
do_and_flags(uns8 reg)
{
	zeroFlag = !reg; signFlag = reg & 0x80;
	par_overFlag = parity_tbl[reg];
	Set(hcarryFlag); Clr(carryFlag); Clr(addsubFlag);
}

static inline void
do_or_flags(uns8 reg)
{
	zeroFlag = !reg; signFlag = reg & 0x80;
	par_overFlag = parity_tbl[reg];
	Clr(hcarryFlag); Clr(carryFlag); Clr(addsubFlag);
}

static inline void
do_xor_flags(uns8 reg)
{
	zeroFlag = !reg; signFlag = reg & 0x80;
	par_overFlag = parity_tbl[reg];
	Clr(hcarryFlag); Clr(carryFlag); Clr(addsubFlag);
}


/* Set flags after a shift or rotate operation */
static inline void
do_shift_test(uns8 reg)
{
	Clr(hcarryFlag); Clr(addsubFlag);
	par_overFlag = parity_tbl[reg];
	zeroFlag = !reg; signFlag = reg & 0x80;
}


static void
int_occurred(int signo)
{
	interrupted = 1;
}


#ifdef SLOWDOWN
/* Do nothing! */
static void
nop(void)
{
}
#endif


static void
dump_cpu_state(void)
{
	fprintf(stderr, "Z80 CPU State\n-------------\n");

	StoreFlags();

	fprintf(stderr, "  AF  = %04X\t\t  AF' = %04X\t\t  A = %02X  F = %02X\n",
		theProcessor.af_pair, theProcessor.af_alt, *a, *f);
	fprintf(stderr, "  BC  = %04X\t\t  BC' = %04X\t\t  B = %02X  C = %02X\n",
		theProcessor.bc_pair, theProcessor.bc_alt, *b, *c);
	fprintf(stderr, "  DE  = %04X\t\t  DE' = %04X\t\t  D = %02X  E = %02X\n",
		theProcessor.de_pair, theProcessor.de_alt, *d, *e);
	fprintf(stderr, "  HL  = %04X\t\t  HL' = %04X\t\t  H = %02X  L = %02X\n",
		theProcessor.hl_pair, theProcessor.hl_alt, *h, *l);
	fprintf(stderr, "  IX  = %04X\t\t  IY  = %04X\n\n",
		theProcessor.ix_reg, theProcessor.iy_reg);

	fprintf(stderr, "  PC  = %04X\t\t  SP  = %04X\n",
		theProcessor.pc_reg, theProcessor.sp_reg);

	fprintf(stderr, "   R  = %02X\t\t   I  = %02X\n",
		theProcessor.refresh, theProcessor.int_vec);

	fprintf(stderr, "  interrupts %s, IM%d\n",
		theProcessor.iff1 ? "enabled" : "disabled", theProcessor.im);

	fprintf(stderr, "  FLAGS: C=%d N=%d P/V=%d H=%d Z=%d S=%d\n\n",
		!!carryFlag, !!addsubFlag, !!par_overFlag, 
		!!hcarryFlag, !!zeroFlag, !!signFlag);
}


/*
 * Do The Biz.
 */
void
xzx_main_loop(void)
{
	register uns8 op;
	int refresh_interval;
	struct sigaction sigact;
#ifdef SLOWDOWN
	int i;
#endif
	int *em;

#ifdef __GO32__
	int gcount=0;
#endif

	extern void (*screen_refresh)(void);
	extern void check_events(void);
	extern void dd_check(void);

	em = &GetCfg(emode);
	basic_rom = GET_BASIC_ROM(*em);

#ifdef MITSHM
	if (GetCfg(use_mitshm))
		refresh_interval = GetCfg(rr_shm);
	else
#endif
		refresh_interval = GetCfg(rr_noshm);

	/* start interrupts */
	if (!GetCfg(icnt)) {
		sigact.sa_handler = int_occurred;
#ifdef SA_RESTART
		sigact.sa_flags = SA_RESTART;
#else
		sigact.sa_flags = 0;
#endif
		sigaction(SIGALRM, &sigact, NULL);
		int_frequency(20000);
	}

	while(1) {
		instrs++;

#ifdef SLOWDOWN
		for (i = 0; i < GetCfg(slowdown); i++) nop();
#endif

		/* ROM2 on the +3 contains all the DOS routines */
		if ((*em == 3) && (RPAGE(0) == ROM2)) dd_check();

#ifdef XZX_IF1
		if ((*pc == 0x0008 || *pc == 0x1708) &&
				(RPAGE(0) == basic_rom) && GetCfg(if1_active))
			page_in(0, ROM2);
#endif

		op = GetNextOp;

#ifdef XZX_IF1
		if ((*pc == 0x0701) && RPAGE(0) == ROM2 && GetCfg(if1_active))
			page_in(0, basic_rom);
#endif

restart_switch:
		switch (op) {
#			include "z80ops.c"
		}
		if (ixiy_op) {
			ixiy_op = 0; *hl -= disp; hl = safe_hl;
#ifdef LITTLE_ENDIAN
			h = (uns8 *)hl + 1; l = (uns8 *)hl;
#else
			h = (uns8 *)hl; l = (uns8 *)hl + 1;
#endif
		}

		/* On average there are about 5833 Z80 instructions in 0.02 secs, on
	  	 * a 3.5MHz Z80 (assuming average instruction length of 12 T-states)
		 */
#ifdef __GO32__
		if (gcount++>3000)
			{
			interrupted = 1;
			gcount = 0;
			}
#endif
		if (interrupted || (GetCfg(icnt) && !(instrs % GetCfg(ifreq))) ) {
			interrupted = 0;
			n_interrupts++;

			theProcessor.refresh = instrs & 0x7f;

			check_events();

			/* every 25 interrupts -> twice/sec. */
			if ((n_interrupts % 25) == 0)
				do_flashing();

			if ((n_interrupts % refresh_interval) == 0)
				screen_refresh();

#ifdef DEBUG
			if (!GetCfg(icnt) && (n_interrupts % 50) == 0) {
				printf("%d instructions executed in 1 second (~=%d%%)\n",
					instrs - last_instrs,
					(instrs - last_instrs) * 100 / 291666);
				last_instrs = instrs;
			}
#endif

			/* that's all we need do if interrupts are disabled */
			if (!theProcessor.iff1) continue;

			/* kludge: make sure we don't return to a halt instruction */
			if (op == 0x76) (*pc)++;

			PushValue(*pc);

			if (theProcessor.im == 2) {
				*pc = read_word((theProcessor.int_vec << 8) | 0xff);
			} else {
				*pc = 0x38;
			}
		}
	} 
}
