/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2010, Tom Charlesworth, Michael Pohoreski

AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

AppleWin 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.

You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Description: 6502/65C02 emulation
 *
 * Author: Various
 */

 /****************************************************************************
*
*  GENERAL PURPOSE MACROS
*
***/

#undef AF_TO_EF
#undef EF_TO_AF

#define STACK_PAGE 0x01

#define AF_TO_EF  flagc = (regs.ps & AF_CARRY);				    \
		  flagn = (regs.ps & AF_SIGN);				    \
		  flagv = (regs.ps & AF_OVERFLOW);			    \
		  flagz = (regs.ps & AF_ZERO);
#define EF_TO_AF  regs.ps = (regs.ps & ~(AF_CARRY | AF_SIGN |		    \
					 AF_OVERFLOW | AF_ZERO))	    \
			      | flagc 					    \
			      | flagn					    \
			      | (flagv ? AF_OVERFLOW : 0)		    \
			      | (flagz ? AF_ZERO     : 0)		    \
			      | AF_RESERVED | AF_BREAK;
// CYC(a): This can be optimised, as only certain opcodes will affect uExtraCycles
#define CYC(a)	 uExecutedCycles += (a)+uExtraCycles;
#define _POP (*(mem+((regs.sp >= 0x1FF) ? (regs.sp = 0x100) : ++regs.sp)))
#define _POP_ALT  (																\
			(memreadPageType[STACK_PAGE] == MEM_Normal)							\
				? _POP															\
				: (*(mem+TEXT_PAGE1_BEGIN+((regs.sp >= 0x1FF) ? (regs.sp = 0x100) : ++regs.sp))) /* memreadPageType[0x01] == MEM_Aux1K */ \
		)
#define _PUSH(a) *(mem+regs.sp--) = (a);									    \
		 if (regs.sp < 0x100)												    \
		   regs.sp = 0x1FF;
#define _PUSH_ALT(a) {																\
			LPBYTE page = memwrite[STACK_PAGE];										\
			if (page) {																\
				*(page+(regs.sp & 0xFF)) = (BYTE)(a);								\
			}																		\
			regs.sp--;																\
			if (regs.sp < 0x100)													\
				regs.sp = 0x1FF;													\
		}
#define _READ	(																\
			((addr & 0xF000) == 0xC000)											\
				? IORead[(addr>>4) & 0xFF](regs.pc,addr,0,0,uExecutedCycles)	\
				: *(mem+addr)													\
		)
#define _READ_ALT (																\
			(memreadPageType[addr >> 8] == MEM_Normal)							\
				? *(mem+addr)													\
				: (memreadPageType[addr >> 8] == MEM_Aux1K)						\
					? *(mem+TEXT_PAGE1_BEGIN+(addr&(TEXT_PAGE1_SIZE-1)))		\
					: (memreadPageType[addr >> 8] == MEM_IORead)				\
						? IORead[(addr >> 4) & 0xFF](regs.pc, addr, 0, 0, uExecutedCycles)	\
						: MemReadFloatingBus(uExecutedCycles)					\
		)
#define _READ_WITH_IO_F8xx (										/* GH#827 */\
			((addr & 0xF000) == 0xC000)											\
				? IORead[(addr>>4) & 0xFF](regs.pc,addr,0,0,uExecutedCycles)	\
				: (addr >= 0xF800)												\
					? IO_F8xx(regs.pc,addr,0,0,uExecutedCycles)					\
					: *(mem+addr)												\
		)
#define SETNZ(a) {							    \
		   flagn = ((a) & 0x80);				    \
		   flagz = !((a) & 0xFF);					    \
		 }
#define SETZ(a)	 flagz = !((a) & 0xFF);
#define _WRITE(a) {																		\
			{																			\
				memdirty[addr >> 8] = 0xFF;												\
				LPBYTE page = memwrite[addr >> 8];										\
				if (page)																\
					*(page+(addr & 0xFF)) = (BYTE)(a);									\
				else if ((addr & 0xF000) == 0xC000)										\
					IOWrite[(addr>>4) & 0xFF](regs.pc,addr,1,(BYTE)(a),uExecutedCycles);\
			}																			\
		}
#define _WRITE_ALT(a) {																	\
			{																			\
				memdirty[memwriteDirtyPage[addr >> 8]] = 0xFF;							\
				LPBYTE page = memwrite[addr >> 8];										\
				if (page) {																\
					*(page+(addr & 0xFF)) = (BYTE)(a);									\
					if (memVidHD)											/* GH#997 */\
						*(memVidHD + addr) = (BYTE)(a);									\
				}																		\
				else if ((addr & 0xF000) == 0xC000)										\
					IOWrite[(addr>>4) & 0xFF](regs.pc,addr,1,(BYTE)(a),uExecutedCycles);\
			}																			\
		}
#define _WRITE_WITH_IO_F8xx(a) {											/* GH#827 */\
			if (addr >= 0xF800)															\
				IO_F8xx(regs.pc,addr,1,(BYTE)(a),uExecutedCycles);						\
			else {																		\
				memdirty[addr >> 8] = 0xFF;												\
				LPBYTE page = memwrite[addr >> 8];										\
				if (page) {																\
					*(page+(addr & 0xFF)) = (BYTE)(a);									\
					if (memVidHD)											/* GH#997 */\
						*(memVidHD + addr) = (BYTE)(a);									\
				}																		\
				else if ((addr & 0xF000) == 0xC000)										\
					IOWrite[(addr>>4) & 0xFF](regs.pc,addr,1,(BYTE)(a),uExecutedCycles);\
			}																			\
		}

#define ON_PAGECROSS_REPLACE_HI_ADDR if ((base ^ addr) >> 8) {addr = (val<<8) | (addr&0xff);} /* GH#282 */

//

// ExtraCycles:
// +1 if branch taken
// +1 if page boundary crossed
#define BRANCH_TAKEN {					\
			 base = regs.pc;		\
			 regs.pc += addr;		\
			 if ((base ^ regs.pc) & 0xFF00) \
			     uExtraCycles=2;		\
			 else				\
			     uExtraCycles=1;		\
		     }

//

// TODO Optimization Note: uExtraCycles = ((base ^ addr) >> 8) & 1;
#define CHECK_PAGE_CHANGE	if ((base ^ addr) & 0xFF00)			\
									uExtraCycles=1;

/****************************************************************************
*
*  ADDRESSING MODE MACROS
*
***/

#define ABS	 addr = *(LPWORD)(mem+regs.pc);	 regs.pc += 2;
#define IABSX    addr = *(LPWORD)(mem+(*(LPWORD)(mem+regs.pc))+(WORD)regs.x); regs.pc += 2;

// Optimised for page-cross
#define ABSX_OPT base = *(LPWORD)(mem+regs.pc); addr = base+(WORD)regs.x; regs.pc += 2; CHECK_PAGE_CHANGE;
// Not optimised for page-cross
#define ABSX_CONST base = *(LPWORD)(mem+regs.pc); addr = base+(WORD)regs.x; regs.pc += 2;

// Optimised for page-cross
#define ABSY_OPT base = *(LPWORD)(mem+regs.pc); addr = base+(WORD)regs.y; regs.pc += 2; CHECK_PAGE_CHANGE;
// Not optimised for page-cross
#define ABSY_CONST base = *(LPWORD)(mem+regs.pc); addr = base+(WORD)regs.y; regs.pc += 2;

// TODO Optimization Note (just for IABSCMOS): uExtraCycles = ((base & 0xFF) + 1) >> 8;
#define _IABS_CMOS base = *(LPWORD)(mem+regs.pc);	                          \
		 addr = *(LPWORD)(mem+base);		                  \
		 if ((base & 0xFF) == 0xFF) uExtraCycles=1;		  \
		 regs.pc += 2;
#define _IABS_CMOS_ALT base = *(LPWORD)(mem+regs.pc);				\
		if (memreadPageType[base >> 8] == MEM_Aux1K)				\
			base = TEXT_PAGE1_BEGIN + (base & (TEXT_PAGE1_SIZE-1));	\
		addr = *(LPWORD)(mem+base);									\
		if ((base & 0xFF) == 0xFF) uExtraCycles=1;					\
		regs.pc += 2;
#define IABS_NMOS base = *(LPWORD)(mem+regs.pc);	                          \
		 if ((base & 0xFF) == 0xFF)				  \
		       addr = *(mem+base)+((WORD)*(mem+(base&0xFF00))<<8);\
		 else                                                   \
		       addr = *(LPWORD)(mem+base);                        \
		 regs.pc += 2;
#define _IABS_NMOS_ALT base = *(LPWORD)(mem+regs.pc);				\
		if (memreadPageType[base >> 8] == MEM_Aux1K)				\
			base = TEXT_PAGE1_BEGIN + (base & (TEXT_PAGE1_SIZE-1));	\
		if ((base & 0xFF) == 0xFF)									\
			addr = *(mem+base)+((WORD)*(mem+(base&0xFF00))<<8);		\
		else														\
			addr = *(LPWORD)(mem+base);								\
		regs.pc += 2;

#define IMM	 addr = regs.pc++;

#define INDX	 base = ((*(mem+regs.pc++))+regs.x) & 0xFF;          \
		 if (base == 0xFF)                                   \
		     addr = *(mem+0xFF)+(((WORD)*mem)<<8);           \
		 else                                                \
		     addr = *(LPWORD)(mem+base);

// Optimised for page-cross
#define INDY_OPT	 if (*(mem+regs.pc) == 0xFF)             /*incurs an extra cycle for page-crossing*/ \
		     base = *(mem+0xFF)+(((WORD)*mem)<<8);           \
		 else                                                \
		     base = *(LPWORD)(mem+*(mem+regs.pc));           \
		 regs.pc++;                                          \
		 addr = base+(WORD)regs.y;                           \
		 CHECK_PAGE_CHANGE;
// Not optimised for page-cross
#define INDY_CONST	 if (*(mem+regs.pc) == 0xFF)             /*no extra cycle for page-crossing*/ \
		     base = *(mem+0xFF)+(((WORD)*mem)<<8);           \
		 else                                                \
		     base = *(LPWORD)(mem+*(mem+regs.pc));           \
		 regs.pc++;                                          \
		 addr = base+(WORD)regs.y;

#define IZPG	 base = *(mem+regs.pc++);                            \
		 if (base == 0xFF)                                   \
		     addr = *(mem+0xFF)+(((WORD)*mem)<<8);           \
		 else                                                \
		     addr = *(LPWORD)(mem+base);

#define REL	 addr = (signed char)*(mem+regs.pc++);

// TODO Optimization Note:
// . Opcodes that generate zero-page addresses can't be accessing $C000..$CFFF
//   so they could be paired with special READZP/WRITEZP macros (instead of READ/WRITE)
#define ZPG 	 addr =   *(mem+regs.pc++);
#define ZPGX	 addr = ((*(mem+regs.pc++))+regs.x) & 0xFF;
#define ZPGY	 addr = ((*(mem+regs.pc++))+regs.y) & 0xFF;

// Tidy 3 char addressing modes to keep the opcode table visually aligned, clean, and readable.
#undef asl
#undef idx
#undef imm
#undef izp
#undef lsr
#undef rel
#undef rol
#undef ror
#undef zpx
#undef zpy

#define asl ASLA // Arithmetic Shift Left
#define idx INDX
#define imm IMM
#define izp IZPG
#define lsr LSRA // Logical Shift Right
#define rel REL
#define rol ROLA // Rotate Left
#define ror RORA // Rotate Right
#define zpx ZPGX
#define zpy ZPGY
