/*
 * 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 auxfuncs.c - contains auxiliary functions used mainly
 * to set flags after certain operations. Also handles memory writes.
 *
 * This file gets included by any files which need its functions - not
 * very pretty, but it lets me take advantage of gcc's inlining...
 */

#ifndef __GNUC__
#define inline
#endif

extern int hcarry_tbl[], sub_hcarry_tbl[];
extern int parity_tbl[];
extern int overflow_tbl[], sub_overflow_tbl[];
 
/* Set the appropriate flags on an addition.  Return the new value
 * of the affected register.
 */
static inline uns8
add_and_test(uns8 reg1, uns8 reg2, uns8 carry)
{
	uns16 temp16;
	int idx;

	temp16 = reg1 + reg2 + carry;
	idx = ((reg1 & 0x88) >> 1) | ((reg2 & 0x88) >> 2) | ((temp16 & 0x88) >> 3);

	carryFlag = temp16 & 0x0100;
	zeroFlag = !(temp16 & 0xff);
	signFlag = temp16 & 0x80;
	hcarryFlag = hcarry_tbl[idx & 0x07];
	par_overFlag = overflow_tbl[idx >> 4];
	Clr(addsubFlag);

	return (temp16 & 0xff);
}

/* Set the appropriate flags on a subtraction.  Return the new value
 * of the affected register.
 */
static inline uns8
subtract_and_test(uns8 reg1, uns8 reg2, uns8 carry)
{
	uns16 temp16;
	int idx;

	temp16 = reg1 - (reg2 + carry);
	idx = ((reg1 & 0x88) >> 1) | ((reg2 & 0x88) >> 2) | ((temp16 & 0x88) >> 3);

	carryFlag = temp16 & 0x0100;
	zeroFlag = !(temp16 & 0xff);
	signFlag = temp16 & 0x80;
	hcarryFlag = sub_hcarry_tbl[idx & 0x07];
	par_overFlag = sub_overflow_tbl[idx >> 4];
	Set(addsubFlag);

	return (temp16 & 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)
{
	uns16 temp16;
	int cin7;
	int idx;

	temp16 = *a - val;
	idx = ((*a & 0x88) >> 1) | ((val & 0x88) >> 2) | ((temp16 & 0x88) >> 3);
	cin7 = ((*a & 0x7f) - (val & 0x7f)) & 0x80;

	carryFlag = temp16 & 0x0100;
	zeroFlag = !(temp16 & 0xff);
	signFlag = temp16 & 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 uns16
add_and_test16(uns16 reg1, uns16 reg2, int carry)
{
	uns32 temp32;
	int idx;

	temp32 = reg1 + reg2 + carry;
	idx = ((reg1 & 0x8800) >> 9) | ((reg2 & 0x8800) >> 10) |
		  ((temp32 & 0x8800) >> 11);

	carryFlag = temp32 & 0x10000;
	zeroFlag = !(temp32 & 0xffff);
	hcarryFlag = hcarry_tbl[idx & 0x07];
	par_overFlag = overflow_tbl[idx >> 4];
	signFlag = temp32 & 0x8000;
	Clr(addsubFlag);

	return (temp32 & 0xffff);
}

/* Perform a 16-bit subtraction, setting the appropriate flags.
 * <carry> must be either 1 or 0 on entry.
 */
static inline uns16
subtract_and_test16(uns16 reg1, uns16 reg2, int carry)
{
	uns32 temp32;
	int idx;

	temp32 = reg1 - (reg2 + (uns16) carry);
	idx = ((reg1 & 0x8800) >> 9) | ((reg2 & 0x8800) >> 10) |
		  ((temp32 & 0x8800) >> 11);

	carryFlag = temp32 & 0x10000;
	zeroFlag = !(temp32 & 0xffff);
	hcarryFlag = sub_hcarry_tbl[idx & 0x07];
	par_overFlag = sub_overflow_tbl[idx >> 4];
	signFlag = temp32 & 0x8000;
	Set(addsubFlag);

	return (temp32 & 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 inline void
mem_write(uns16 addr, uns8 val)
{
#ifdef ZXSPEC
	/* Can't be allowed to overwrite the ROM */
#ifdef SPEC16K
	if (addr < 16384 || addr > 32767)
#else
	if (addr < 16384)
#endif
		return;
#endif

	theMemory[addr] = val;

#ifdef ZXSPEC
	/* Handle a write to the display file or attribute area */
	if (addr < 22528)
		screen_write(addr, val);
	else if (addr < 23296)
		attr_write(addr, val);
#endif
}
