/*
 *   apple.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2005 CdB for HP
 *   Copyright (C) 2006 Christoph Gießelink
 *
 */
#include "pch.h"
#include "Emu48.h"
#include "Opcodes.h"
#include "apple.h"
#include "io.h"								// I/O register definitions
#include "i28f160.h"

#define w Chipset

#pragma intrinsic(memset,memcpy)

#include "Ops.h"

//
// ROM buffer access functions
//

static __inline void WrDirtyPage(DWORD d)
{
	if (pbyRomDirtyPage)					// using dirty ROM page table
	{
		DWORD dwPage = d / ROMPAGESIZE;		// this is the page

		_ASSERT(dwPage < dwRomDirtyPageSize);
		pbyRomDirtyPage[dwPage] = TRUE;		// page is dirty
	}
	return;
}

static __inline void EraseBlock(DWORD d,DWORD dwNibSize)
{
	LPBYTE pbyAddr = pbyRom + d;

	while (dwNibSize--)
	{
		WrDirtyPage(d++);					// make page dirty
		*pbyAddr++ = 0x0F;					// clear address
	}
	return;
}

static CONST LPBYTE ppReg[] = { w.A, w.B, w.C, w.D };

static QWORD DecodeReg64(LPBYTE R, BYTE byNF)
{
	QWORD qwVal = Npack64(R,16);			// generate 64bit number from register

	switch (byNF)							// field selector
	{
	case  0: return (qwVal >> (w.P*4)) & 0xf;			// P
	case  1: return qwVal & ~((QWORD)~0 << ((w.P+1)*4));// WP
	case  2: return (qwVal >> 8) & 0xf;					// XS
	case  3: return qwVal & 0xfff;						// X
	case  4: return (qwVal >> 60) & 0xf;				// S
	case  5: return (qwVal >> 12) & 0x0000ffffffffffff;	// M
	case  6: return qwVal & 0xff;						// B
	case  7: return qwVal;								// W
	case 15: return qwVal & 0xfffff;					// A
//	default: return qwVal & w.fld[byNF-8];				// F1-F7
	default: return qwVal;
	}
}

static void EncodeReg64(QWORD v, LPBYTE R, BYTE byNF)
{
	if (byNF > 7 && byNF < 15)				// user mask area F1-F7
	{
//		QWORD qwMask = w.fld[byNF-8];		// F1-F7
//		QWORD qwVal = Npack64(R,16);		// original content of register
//		v = (v & qwMask) | (qwVal & ~qwMask); // mask operation
		byNF = 7;							// write W area
	}

	Nunpack64(R+F_s[byNF], v, F_l[byNF]);
	return;
}

static QWORD o80BReg164(LPBYTE I)
{
	_ASSERT((I[5] & 3) < ARRAYSIZEOF(ppReg));
	return DecodeReg64(ppReg[I[5] & 3], I[6]);
}

static QWORD o80BReg264(LPBYTE I)
{
	_ASSERT((I[5] >> 2) < ARRAYSIZEOF(ppReg));
	return DecodeReg64(ppReg[I[5] >> 2], I[6]);
}

static void o80BRegWrite(QWORD v, LPBYTE I)
{
	_ASSERT((I[5] & 3) < ARRAYSIZEOF(ppReg));
	EncodeReg64(v, ppReg[I[5] & 3], I[6]);
	return;
}

// SETFLDx
VOID o80BF7x(LPBYTE I)
{
	QWORD qwVal;

_ASSERT(FALSE);								// not tested so far
	w.pc+=1;								// skip x nibble
	qwVal = Npack64(w.C,16);				// generate 64bit number from C register
	w.carry =  (qwVal == 0)					// set carry if field mask = 0
			|| (I[5] < 8) || (I[5] > 14);	// or x argument not in range 8..15

	if (!w.carry)							// field mask and argument are valid
	{
		_ASSERT(I[5] >= 8 && I[5] <= 14);
//		w.fld[I[5]-8] = qwVal;
	}
	return;
}

// RPL2 (normal LOOP with preserving Carry)
VOID o80B00(VOID)
{
	BYTE p[5];

	Nread(w.A, w.d0, 5);					// A=DAT0 A
	w.d0 = (w.d0 + 5) & 0xFFFFF;			// D0=D0+ 5
	Nread(p, Npack(w.A,5), 5);				// PC=(A)
	w.pc = Npack(p,5);
	return;
}

// FALSE +5
VOID o80B30(VOID)
{
	Ndec(w.D, 5, 0);			// D=D-1 A
	if (w.carry) w.pc = 0x03A9B; // memerr?
	w.d1 -= 5;					// D1=D1- 5 (don't care about carry here)
	Nwrite(w.A, w.d1, 5);		// DAT1=A A
	o80B00();					// LOOP
	return;
}

// DOFALSE
VOID o80B40(VOID)
{
	w.P = 0;								// P= 0
	PCHANGED;
	Nunpack(w.C,0x03AC0,5);					// LC(5) =FALSE
	memcpy(w.A, w.C, 5);					// A=C A
	o80B30();								// PC=(A) (call FALSE)
	return;
}

// BEEP2
VOID o80B50(CHIPSET* w)
{
	BYTE  fbeep;
	DWORD freq,dur;

	freq = Npack(w->D,5);					// frequency in Hz
	dur = Npack(w->C,5);					// duration in ms
	Nread(&fbeep,0x80F0F,1);				// fetch system flags -53 to -56

	w->carry = TRUE;						// setting of no beep
	if (!(fbeep & 0x8) && freq)				// bit -56 clear and frequency > 0 Hz
	{
		if (freq > 4400) freq = 4400;		// high limit of HP (SX)

		SoundBeep(freq,dur);				// beeping

		// estimate cpu cycles for beeping time (4MHz)
		w->cycles += dur * 4000;

		// original routine return with...
		w->P = 0;							// P=0
		w->intk = TRUE;						// INTON
		w->carry = FALSE;					// RTNCC
	}
	w->pc = rstkpop();
	return;
}

// MOVEDOWN
VOID o80B60(VOID)
{
	BYTE  byData[16];
	DWORD dwC,s;

	for (dwC = Npack(w.C,5); dwC > 0; dwC -= s)
	{
		s = ARRAYSIZEOF(byData);			// max size for data
		if (dwC < s) s = dwC;

		Npeek(byData,w.d0,s);				// read source without CRC update
		Nwrite(byData,w.d1,s);				// write destination

		w.d0 = (w.d0 + s) & 0xFFFFF;
		w.d1 = (w.d1 + s) & 0xFFFFF;
	}

	w.P = 0;
	PCHANGED;
	w.carry = FALSE;
	return;
}

// MOVEUP
VOID o80B70(VOID)
{
	BYTE  byData[16];
	DWORD dwC,s;

	for (dwC = Npack(w.C,5); dwC > 0; dwC -= s)
	{
		s = ARRAYSIZEOF(byData);			// max size for data
		if (dwC < s) s = dwC;

		w.d0 = (w.d0 - s) & 0xFFFFF;
		w.d1 = (w.d1 - s) & 0xFFFFF;

		Npeek(byData,w.d0,s);				// read source without CRC update
		Nwrite(byData,w.d1,s);				// write destination
	}

	w.P = 0;
	PCHANGED;
	w.carry = FALSE;
	return;
}

// CREATETEMP
VOID o80B80(VOID)
{
	DWORD dwC,dwAddress;

	dwC = Npack(w.C,5);						// desired size of hole
	dwAddress = RPL_CreateTemp(dwC,FALSE);	// allocate memory
	if (dwAddress)
	{
		w.d0 = dwAddress;					// object position
		w.d1 = (w.d0 + dwC) & 0xFFFFF;		// link field of hole
		w.carry = FALSE;					// no error
	}
	else
	{
		w.carry = TRUE;						// error
	}

	dwC += 6;								// desired size + link field
	Nunpack(w.B,dwC,5);						// B[A] = size + 6
	Nunpack(w.C,dwC,5);						// C[A] = size + 6
	return;
}

// RCKBp
VOID o80B90(CHIPSET* w)						// ROM Check Beep patch
{
	DWORD dw2F,dwCpuFreq;
	DWORD freq,dur;
	BYTE f,d;

	f = w->C[1];							// f = freq ctl
	d = w->C[0];							// d = duration ctl

	// CPU strobe frequency @ RATE 27 = 3.67MHz
	// CPU strobe frequency @ RATE 29 = 3.93MHz
	dwCpuFreq = ((27 + 1) * 524288) >> 2;

	dw2F = f * 180 + 367;					// F=f*90+183.5

	freq = dwCpuFreq / dw2F;
	dur = (dw2F * (256 - 16 * d)) * 1000 / 2 / dwCpuFreq;

	if (freq > 4400) freq = 4400;			// high limit of HP

	SoundBeep(freq,dur);					// beeping

	// estimate cpu cycles for beeping time (4MHz)
	w->cycles += dur * 4000;

	w->P = 0;								// P=0
	w->carry = FALSE;						// RTNCC
	w->pc = rstkpop();
	return;
}

// setup basic memory configuration
VOID o80B04(VOID)
{
	DWORD a;

	a = Npack(w.C,5);						// save C[A]
	Reset();								// unconfig all devices
	Nunpack(w.C,0x100,5);					// IO:  0x00100
	Config();								// addr
	Nunpack(w.C,0x80000,5);					// RAM: 0x80000 size 256KB
	Config();								// size
	Config();								// addr
	Nunpack(w.C,a,5);						// restore C[A]
	w.P = 0;
	PCHANGED;
	return;
}

// erase flash bank
VOID o80B14(VOID)
{
	DWORD dwStart,dwStop;

	BYTE byBank = w.C[15];					// C[S] = bank to erase

_ASSERT(FALSE);								// not tested so far
	// ROM is logically organized in 16 banks with 128KB
	dwStart = byBank * _KB(128);			// start address
	dwStop = dwStart + _KB(128);			// last address
	if (byBank == 0) dwStart += _KB(64);	// skip boot loader

	// clear bank
	EraseBlock(dwStart,dwStop-dwStart);

	w.carry = FALSE;						// no error
	return;
}

// write bytes to flash
VOID o80B24(VOID)
{
	LPBYTE pbyBuffer;
	DWORD  dwNib,dwAddr,dwSize;

	dwNib = Npack(w.C,5) * 2;				// no. of nibbles to copy
	dwAddr = FlashROMAddr(w.d1);			// linear addr in flash chip

	dwSize = dwRomSize - dwAddr;			// remaining memory size in flash
	if (dwNib > dwSize) dwNib = dwSize;		// prevent buffer overflow

	pbyBuffer = (LPBYTE) malloc(dwNib);		// allocate data buffer
	if (pbyBuffer != NULL)
	{
		DWORD i;

		Npeek(pbyBuffer,w.d0,dwNib);		// get data

		for (i = 0; i < dwNib; ++i)
		{
			WrDirtyPage(dwAddr);			// make page dirty
			pbyRom[dwAddr++] = pbyBuffer[i]; // write data
		}
		free(pbyBuffer);
	}

	w.d0 += dwNib;							// update source register
	w.d1 += dwNib;							// update destination register
	w.carry = FALSE;						// no error
	return;
}

// OUTBYT
VOID o80B65(VOID)
{
	// set Transmitting annunciator
	BYTE byAnn = w.IORam[ANNCTRL+1] | 0x2;
	WriteIO(&byAnn,ANNCTRL+1,1);

	SendByteUdp((BYTE) Npack(w.A,2));		// send data byte

	w.P = 0;
	PCHANGED;
	w.carry = FALSE;						// no error
	return;
}

// CdB for HP: add apples BUSCC commands
VOID o80BExt(LPBYTE I) // Saturnator extentions
{
	DWORD a;

	w.pc+=2;
	switch (I[3]+(I[4]<<4))
	{
	case 0x00: o80B00(); break; // RPL2 (preserve Carry)
	case 0x03: o80B30(); break; // FALSE
	case 0x04: o80B40(); break; // DOFALSE
	case 0x05: o80B50(&w); PCHANGED; break; // BEEP2 implemented using Emu48's beep
	case 0x06: o80B60(); break; // MOVEDOWN
	case 0x07: o80B70(); break; // MOVEUP
	case 0x08: o80B80(); break; // CREATETEMP
	case 0x09: o80B90(&w); PCHANGED; break; // RCKBp (ROM Check Beep patch)
	case 0x0A: break; // KEYDN not implemented
	case 0x0B: break; // no doslow implemented
	case 0x10: // simulate off function
		{
			BOOL bShutdn = TRUE;			// shut down

			// only shut down when no timer wake up
			if (w.IORam[TIMER1_CTRL]&WKE)	// WKE bit of timer1 is set
			{
				if (ReadT1()&0x08)			// and MSB of timer1 is set
				{
					w.IORam[TIMER1_CTRL] &= ~WKE;	// clear WKE
					bShutdn = FALSE;		// don't shut down
					}
				}
			if (w.IORam[TIMER2_CTRL]&WKE)	// WKE bit of timer2 is set
			{
				if (ReadT2()&0x80000000)	// and MSB of timer2 is set
				{
					w.IORam[TIMER2_CTRL] &= ~WKE;	// clear WKE
					bShutdn = FALSE;		// don't shut down
				}
			}
			if (w.in==0 && bShutdn)			// shut down only when enabled
			{
				w.Shutdn = TRUE;			// set mode before exit emulation loop
				bInterrupt = TRUE;
			}
		}
		break;
	case 0x11: w.pc+=2; break; // do not do gettime, just skip the RTN after it to fall in the normal gettime function (only valid in untouched ROM)
	case 0x12: break; // do not do settime, fall in the normal settime function (only valid in untouched ROM)
	case 0x13: break; // RESETOS not implemented
	case 0x14: break; // AUTOTEST not implemented
	case 0x15: break; // NATIVE? not implemented
	case 0x17: break; // SERIAL not implemented
	case 0x28: w.HST |= I[5]; w.pc+=1; break; // HST=1.x
	case 0x29: w.A[4]= w.A[3]= w.A[2]= w.A[0]= 0; if (cCurrentRomType=='Q') w.A[1]=5; else w.A[1]=4; break; // screen height = 0x50 = 80
	case 0x2A: w.A[4]= w.A[3]= w.A[2]= 0; w.A[1]=8; w.A[0]=3; break; // screen width = 0x83 = 131
	case 0x2B: w.carry = (cCurrentRomType == '2'); break; // it is medium apple
	case 0x2C: w.carry = (cCurrentRomType == 'Q'); break; // it is big apple
	case 0x2E: w.carry = (nCurrentClass == 50);    break; // it is big apple V2
	case 0x30: w.d0address= Npack(w.C,5)>>12; Map(0,0xff); break; //config_disp0 Ca:address 4K data
	case 0x31: w.d0address=0; Map(0,0xff); RefreshDisp0(); break; //unconfig_disp0 does the refresh
	case 0x32: RefreshDisp0(); break; //refresh_disp0 force refresh
	case 0x33: a= Npack(w.C,2); if (a>(DWORD)SCREENHEIGHT) a= SCREENHEIGHT; /* w.lcounter = (SCREENHEIGHT-a) */; w.d0size= a; RefreshDisp0(); break; //set_lines_disp0 nb in Cb
	case 0x34: w.d0offset= Npack(w.C,5); w.d0offset &= 0x7FF; break; //set_offset_disp0 offset to disp in disp0
	case 0x35: Nunpack(w.C,w.d0offset,5); break;			// native_get_line_disp0
	case 0x38: w.HST |= I[5]; w.pc+=3; break; // ?HST=1.x not implemented
	case 0x40: o80B04(); break; // setup basic memory configuration
//	case 0x41: o80B14(); break; // erase flash bank
	case 0x42: o80B24(); break; // write bytes into flash
//	case 0x43: ???				// format flash bank
	case 0x50: break; // REMON not implemented
	case 0x51: break; // REMOFF not implemented
	case 0x56: o80B65(); break; // OUTBYT
	case 0x57: w.D[0]= w.D[1]= 0; break;
	case 0x60: break; // ACCESSSD not implemented
	case 0x61: break; // PORTTAG? not implemented
	case 0x64: w.carry = FALSE; break; // no SD card inserted
	case 0x66: w.carry = FALSE; break; // simulate format fail card inserted
	case 0x7F: w.carry = TRUE; w.pc+=1; break; // SETFLDn not implemented, set carry for failed
	case 0x80: { QWORD b = o80BReg264(I); o80BRegWrite(b, I);                             w.pc+=2; break; } // r=s
	case 0x81: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a+b, I);  w.pc+=2; break; } // r=r+s
	case 0x82: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a-b, I);  w.pc+=2; break; } // r=r-s
	case 0x83: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a*b, I);  w.pc+=2; break; } // r=r*s
	case 0x84: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a/b, I);  w.pc+=2; break; } // r=r/s
	case 0x85: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a%b, I);  w.pc+=2; break; } // r=r%s
	case 0x86: { QWORD b = o80BReg264(I); o80BRegWrite(~b, I);                            w.pc+=2; break; } // r=-r-1
	case 0x87: { QWORD b = o80BReg264(I); o80BRegWrite((QWORD)(-(__int64)b), I);          w.pc+=2; break; } // r=-r
	case 0x88: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a<<b, I); w.pc+=2; break; } // r=r<s
	case 0x89: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a>>b, I); w.pc+=2; break; } // r=r>s
	case 0x8A: { QWORD a = o80BReg164(I); QWORD b = o80BReg264(I); o80BRegWrite(a^b,  I); w.pc+=2; break; } // r=r^s
	case 0x90: break; // data streamer not implemented
	case 0xEE: break; // ARMFLUSH not implemented
	case 0xEF: break; // ARMSYS not implemented
	case 0xFF: break; // ARMSAT not implemented
	default: w.pc-= 2;
	}
	return;
}
