// This unit has been copied from libz80 into Collapse OS and was slighly changed
/* =========================================================
 *  libz80 - Z80 emulation library
 * =========================================================
 *
 * (C) Gabriel Gambetta (gabriel.gambetta@gmail.com) 2000 - 2012
 *
 * Version 2.1.0
 *
 * ---------------------------------------------------------
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "z80.h"
#include "string.h"


#define BR (ctx->R1.br)
#define WR (ctx->R1.wr)

#define SETFLAG(F) setFlag(ctx, F)
#define RESFLAG(F) resFlag(ctx, F)
#define GETFLAG(F) getFlag(ctx, F)

#define VALFLAG(F,V) valFlag(ctx, F, V)


/* ---------------------------------------------------------
 *  Flag tricks
 * ---------------------------------------------------------
 *
 * To avoid repeating entries in the spec files, many operations that look similar are treated as special cases
 * of a more general operation.
 *
 * For example, ADD and ADC are similar in syntax and operation - the difference is that ADC takes the carry flag
 * into account.
 *
 * So we define a general operation doArithmetic(...) which accepts a boolean parameter specifying whether to do
 * a Carry-operation or not. Then, when we parse, we can say
 *
 * (ADD|ADC) ....
 *		doArithmetic(FLAG_FOR_%1)
 *
 * and everything works fine.
 *
 */

/* Flags for doIncDec() */
static const int ID_INC = 0;
static const int ID_DEC = 1;

/* Flags for enable / disable interrupts */
static const int IE_DI = 0;
static const int IE_EI = 1;

/* Flags for doSetRes() */
static const int SR_RES = 0;
static const int SR_SET = 1;

/* Flags for logical / arithmetic operations */
static const int IA_L = 0;
static const int IA_A = 1;

/* Flags for doArithmetic() - F1 = withCarry, F2 = isSub */
static const int F1_ADC = 1;
static const int F1_SBC = 1;
static const int F1_ADD = 0;
static const int F1_SUB = 0;

static const int F2_ADC = 0;
static const int F2_SBC = 1;
static const int F2_ADD = 0;
static const int F2_SUB = 1;

/* Increment or decrement R, preserving bit 7 */
#define INCR (ctx->R = (ctx->R & 0x80) | ((ctx->R + 1) & 0x7f))
#define DECR (ctx->R = (ctx->R & 0x80) | ((ctx->R - 1) & 0x7f))


/* ---------------------------------------------------------
 *  The opcode implementations
 * ---------------------------------------------------------
 */
#include "opcodes_decl.h"

typedef enum
{
	OP_NONE,
	OP_BYTE,
	OP_OFFSET,
	OP_WORD
} Z80OperandType;

typedef void (*Z80OpcodeFunc) (Z80Context* ctx);

struct Z80OpcodeEntry
{
	Z80OpcodeFunc func;

	int operand_type;
	char* format;

	struct Z80OpcodeTable* table;
};


struct Z80OpcodeTable
{
	int opcode_offset;
	struct Z80OpcodeEntry entries[256];
};


#include "opcodes_table.h"


/* ---------------------------------------------------------
 *  Data operations
 * ---------------------------------------------------------
 */
static void write8 (Z80Context* ctx, ushort addr, byte val)
{
	ctx->tstates += 3;
	ctx->memWrite(ctx->memParam, addr, val);
}


static void write16 (Z80Context* ctx, ushort addr, ushort val)
{
	write8(ctx, addr, val);
	write8(ctx, addr + 1, val >> 8);
}


static byte read8 (Z80Context* ctx, ushort addr)
{
	ctx->tstates += 3;
	return ctx->memRead(ctx->memParam, addr);
}


static ushort read16 (Z80Context* ctx, ushort addr)
{
	byte lsb = read8(ctx, addr);
	byte msb = read8(ctx, addr + 1);
	return msb << 8 | lsb;
}


static byte ioRead (Z80Context* ctx, ushort addr)
{
	ctx->tstates += 4;
	return ctx->ioRead(ctx->ioParam, addr);
}


static void ioWrite (Z80Context* ctx, ushort addr, byte val)
{
	ctx->tstates += 4;
	ctx->ioWrite(ctx->ioParam, addr, val);
}


/* ---------------------------------------------------------
 *  Flag operations
 * ---------------------------------------------------------
 */

/** Sets a flag */
static void setFlag(Z80Context* ctx, Z80Flags flag)
{
	BR.F |= flag;
}

/** Resets a flag */
static void resFlag(Z80Context* ctx, Z80Flags flag)
{
	BR.F &= ~flag;
}

/** Puts a value in a flag */
static void valFlag(Z80Context* ctx, Z80Flags flag, int val)
{
	if (val)
		SETFLAG(flag);
	else
		RESFLAG(flag);
}

/** Returns a flag */
static int getFlag(Z80Context* ctx, Z80Flags flag)
{
	return (BR.F & flag) != 0;
}


/* ---------------------------------------------------------
 *  Flag adjustments
 * ---------------------------------------------------------
 */

static int parityBit[256] = {
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
	1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 };


static void adjustFlags (Z80Context* ctx, byte val)
{
	VALFLAG(F_5, (val & F_5) != 0);
	VALFLAG(F_3, (val & F_3) != 0);
}


static void adjustFlagSZP (Z80Context* ctx, byte val)
{
	VALFLAG(F_S, (val & 0x80) != 0);
	VALFLAG(F_Z, (val == 0));
	VALFLAG(F_PV, parityBit[val]);
}


/* Adjust flags after AND, OR, XOR */
static void adjustLogicFlag (Z80Context* ctx, int flagH)
{
    VALFLAG(F_S, (BR.A & 0x80) != 0);
    VALFLAG(F_Z, (BR.A == 0));
    VALFLAG(F_H, flagH);
    VALFLAG(F_N, 0);
    VALFLAG(F_C, 0);
    VALFLAG(F_PV, parityBit[BR.A]);

    adjustFlags(ctx, BR.A);
}


/* ---------------------------------------------------------
 *  Condition checks
 * ---------------------------------------------------------
 */

typedef enum
{
	C_,
	C_Z,
	C_NZ,
	C_C,
	C_NC,
	C_M,
	C_P,
	C_PE,
	C_PO
} Z80Condition;

static int condition(Z80Context* ctx, Z80Condition cond)
{
	if (cond == C_)
		return 1;

	if (cond == C_Z)
		return GETFLAG(F_Z);

	if (cond == C_NZ)
		return !GETFLAG(F_Z);

	if (cond == C_C)
		return GETFLAG(F_C);

	if (cond == C_NC)
		return !GETFLAG(F_C);

	if (cond == C_M)
		return GETFLAG(F_S);

	if (cond == C_P)
		return !GETFLAG(F_S);

	if (cond == C_PE)
		return GETFLAG(F_PV);

/*	if (cond == C_PO)*/
		return !GETFLAG(F_PV);
}


/* ---------------------------------------------------------
 *  Generic operations
 * ---------------------------------------------------------
 */


static int doComplement(byte v)
{
	if ((v & 0x80) == 0)
		return v;

	v = ~v;
	v &= 0x7F;
	v++;

	return -v;
}


/** Do an arithmetic operation (ADD, SUB, ADC, SBC y CP) */
static byte doArithmetic (Z80Context* ctx, byte value, int withCarry, int isSub)
{
	ushort res; /* To detect carry */

	if (isSub)
	{
		SETFLAG(F_N);
		VALFLAG(F_H, (((BR.A & 0x0F) - (value & 0x0F)) & 0x10) != 0);
		res = BR.A - value;
		if (withCarry && GETFLAG(F_C))
			res--;
	}
	else
	{
		RESFLAG(F_N);
		VALFLAG(F_H, (((BR.A & 0x0F) + (value & 0x0F)) & 0x10) != 0);
		res = BR.A + value;
		if (withCarry && GETFLAG(F_C))
			res++;
	}
	VALFLAG(F_S, ((res & 0x80) != 0));
	VALFLAG(F_C, ((res & 0x100) != 0));
	VALFLAG(F_Z, ((res & 0xff) == 0));
	int minuend_sign = BR.A & 0x80;
	int subtrahend_sign = value & 0x80;
	int result_sign = res & 0x80;
	int overflow;
	if(isSub)
		overflow = minuend_sign != subtrahend_sign && result_sign != minuend_sign;
	else
		overflow = minuend_sign == subtrahend_sign && result_sign != minuend_sign;
	VALFLAG(F_PV, overflow);
	adjustFlags(ctx, res);

	return (byte)(res & 0xFF);
}


/* Do a 16-bit addition, setting the appropriate flags. */
static ushort doAddWord(Z80Context* ctx, ushort a1, ushort a2, int withCarry, int isSub)
{
	if(withCarry && GETFLAG(F_C))
		a2++;
	int sum = a1;
	if(isSub)
	{
		sum -= a2;
		VALFLAG(F_H, ((a1 & 0x0fff) - (a2 & 0x0fff)) & 0x1000);
	}
	else
	{
		sum += a2;
		VALFLAG(F_H, ((a1 & 0x0fff) + (a2 & 0x0fff)) & 0x1000);
	}
	VALFLAG(F_C, sum & 0x10000);
	if(withCarry || isSub)
	{
		int minuend_sign = a1 & 0x8000;
		int subtrahend_sign = a2 & 0x8000;
		int result_sign = sum & 0x8000;
		int overflow;
		if(isSub)
			overflow = minuend_sign != subtrahend_sign && result_sign != minuend_sign;
		else
			overflow = minuend_sign == subtrahend_sign && result_sign != minuend_sign;
		VALFLAG(F_PV, overflow);
		VALFLAG(F_S, (sum & 0x8000) != 0);
		VALFLAG(F_Z, (sum & 0xFFFF) == 0);
	}
	VALFLAG(F_N, isSub);
	adjustFlags(ctx, sum >> 8);
	return sum;
}


static void doAND (Z80Context* ctx, byte value)
{
	BR.A &= value;
	adjustLogicFlag(ctx, 1);
}


static void doOR (Z80Context* ctx, byte value)
{
	BR.A |= value;
	adjustLogicFlag(ctx, 0);
}


static void doXOR (Z80Context* ctx, byte value)
{
	BR.A ^= value;
	adjustLogicFlag(ctx, 0);
}


static void doBIT (Z80Context* ctx, int b, byte val)
{
	if (val & (1 << b))
        RESFLAG(F_Z | F_PV);
    else
        SETFLAG(F_Z | F_PV);

    SETFLAG(F_H);
    RESFLAG(F_N);

    RESFLAG(F_S);
    if ((b == 7) && !GETFLAG(F_Z))
        SETFLAG(F_S);
}


static void doBIT_r(Z80Context* ctx, int b, byte val)
{
	doBIT(ctx, b, val);
	VALFLAG(F_5, val & F_5);
	VALFLAG(F_3, val & F_3);
}


static void doBIT_indexed(Z80Context* ctx, int b, ushort address)
{
	byte val = read8(ctx, address);
	doBIT(ctx, b, val);
	VALFLAG(F_5, (address >> 8) & F_5);
	VALFLAG(F_3, (address >> 8) & F_3);
}


byte doSetRes (Z80Context* ctx, int bit, int pos, byte val)
{
    if (bit)
		val |= (1 << pos);
    else
		val &= ~(1 << pos);
    return val;
}



static byte doIncDec (Z80Context* ctx, byte val, int isDec)
{
    if (isDec)
    {
        VALFLAG(F_PV, (val & 0x80) && !((val - 1) & 0x80));
        val--;
        VALFLAG(F_H, (val & 0x0F) == 0x0F);
    }
    else
    {
        VALFLAG(F_PV, !(val & 0x80) && ((val + 1) & 0x80));
        val++;
        VALFLAG(F_H, !(val & 0x0F));
    }

    VALFLAG(F_S, ((val & 0x80) != 0));
    VALFLAG(F_Z, (val == 0));
    VALFLAG(F_N, isDec);

    adjustFlags(ctx, val);

    return val;
}


static byte doRLC (Z80Context* ctx, int adjFlags, byte val)
{
    VALFLAG(F_C, (val & 0x80) != 0);
    val <<= 1;
    val |= (byte)GETFLAG(F_C);

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);

    if (adjFlags)
        adjustFlagSZP(ctx, val);

    return val;
}


static byte doRL (Z80Context* ctx, int adjFlags, byte val)
{
    int CY = GETFLAG(F_C);
    VALFLAG(F_C, (val & 0x80) != 0);
    val <<= 1;
    val |= (byte)CY;

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);

    if (adjFlags)
        adjustFlagSZP(ctx, val);

    return val;
}


static byte doRRC (Z80Context* ctx, int adjFlags, byte val)
{
    VALFLAG(F_C, (val & 0x01) != 0);
    val >>= 1;
    val |= ((byte)GETFLAG(F_C) << 7);

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);

    if (adjFlags)
        adjustFlagSZP(ctx, val);

    return val;
}


static byte doRR (Z80Context* ctx, int adjFlags, byte val)
{
    int CY = GETFLAG(F_C);
    VALFLAG(F_C, (val & 0x01));
    val >>= 1;
    val |= (CY << 7);

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);

    if (adjFlags)
        adjustFlagSZP(ctx, val);

    return val;
}


static byte doSL (Z80Context* ctx, byte val, int isArith)
{
    VALFLAG(F_C, (val & 0x80) != 0);
    val <<= 1;

    if (!isArith)
        val |= 1;

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);
    adjustFlagSZP(ctx, val);

    return val;
}


static byte doSR (Z80Context* ctx, byte val, int isArith)
{
    int b = val & 0x80;

    VALFLAG(F_C, (val & 0x01) != 0);
    val >>= 1;

    if (isArith)
        val |= b;

    adjustFlags(ctx, val);
    RESFLAG(F_H | F_N);
    adjustFlagSZP(ctx, val);

    return val;
}


static void doPush (Z80Context* ctx, ushort val)
{
	WR.SP--;
	WR.SP--;
	write16(ctx, WR.SP, val);
}


static ushort doPop (Z80Context* ctx)
{
	ushort val;
	val = read16(ctx, WR.SP);
	WR.SP++;
	WR.SP++;
    return val;
}


static byte doCP_HL(Z80Context * ctx)
{
	byte val = read8(ctx, WR.HL);
	byte result = doArithmetic(ctx, val, 0, 1);
	adjustFlags(ctx, val);
	return result;
}


/* The DAA opcode
 * According to the value in A and the flags set, add a value to A
 * This algorithm taken from:
 * http://www.worldofspectrum.org/faq/reference/z80reference.htm
 * and verified against the specification in the Zilog
 * Z80 Family CPU User Manual, rev. 04, Dec. 2004, pp. 166-167
 */

static void doDAA(Z80Context * ctx) {
  int correction_factor = 0x00;
  int carry = 0;
  if(BR.A > 0x99 || GETFLAG(F_C)) {
    correction_factor |= 0x60;
    carry = 1;
  }
  if((BR.A & 0x0f) > 9 || GETFLAG(F_H))
    correction_factor |= 0x06;
  int a_before = BR.A;
  if(GETFLAG(F_N))
    BR.A -= correction_factor;
  else
    BR.A += correction_factor;
  VALFLAG(F_H, (a_before ^ BR.A) & 0x10);
  VALFLAG(F_C, carry);
  VALFLAG(F_S, (BR.A & 0x80) != 0);
  VALFLAG(F_Z, (BR.A == 0));
  VALFLAG(F_PV, parityBit[BR.A]);
  adjustFlags(ctx, BR.A);
}

#include "opcodes_impl.c"


/* ---------------------------------------------------------
 *  The top-level functions
 * ---------------------------------------------------------
 */


static void do_execute(Z80Context* ctx)
{
	struct Z80OpcodeTable* current = &opcodes_main;
	struct Z80OpcodeEntry* entries = current->entries;
	Z80OpcodeFunc func;

	byte opcode;
	int offset = 0;
	do
	{
		if (ctx->exec_int_vector)
		{
			opcode = ctx->int_vector;
			ctx->tstates += 6;
		}
		else
		{
			opcode = read8(ctx, ctx->PC + offset);
			ctx->PC++;
			ctx->tstates += 1;
		}

		INCR;
		func = entries[opcode].func;
		if (func != NULL)
		{
			ctx->PC -= offset;
			func(ctx);
			ctx->PC += offset;
			break;
		}
		else if (entries[opcode].table != NULL)
		{
			current = entries[opcode].table;
			entries = current->entries;
			offset = current->opcode_offset;
			if (offset > 0)
				DECR;
		}

		else
		{
			/* NOP */
			break;
		}
	} while(1);
}


static void unhalt(Z80Context* ctx)
{
    if (ctx->halted)
    {
        ctx->halted = 0;
        ctx->PC++;
    }
}


static void do_nmi(Z80Context* ctx)
{
	unhalt(ctx);
	ctx->IFF2 = ctx->IFF1;
	ctx->IFF1 = 0;
    doPush(ctx, ctx->PC);
	ctx->PC = 0x0066;
	ctx->nmi_req = 0;
	ctx->tstates += 5;
}


static void do_int(Z80Context* ctx)
{
    unhalt(ctx);
	ctx->IFF1 = 0;
	ctx->IFF2 = 0;
	ctx->int_req = 0;
    if (ctx->IM == 0)
    {
		ctx->exec_int_vector = 1;
		do_execute(ctx);
		ctx->exec_int_vector = 0;
    }
    else if (ctx->IM == 1)
    {
        doPush(ctx, ctx->PC);
        ctx->PC = 0x0038;
		ctx->tstates += 7;
    }
    else if (ctx->IM == 2)
    {
        doPush(ctx, ctx->PC);
		ushort vector_address = (ctx->I << 8) | ctx->int_vector;
		ctx->PC = read16(ctx, vector_address);
		ctx->tstates += 7;
    }
}


void Z80Execute (Z80Context* ctx)
{
	if (ctx->nmi_req)
		do_nmi(ctx);
	else if (ctx->int_req && !ctx->defer_int && ctx->IFF1)
		do_int(ctx);
	else
	{
		ctx->defer_int = 0;
		do_execute(ctx);
	}
}


unsigned Z80ExecuteTStates(Z80Context* ctx, unsigned tstates)
{
	ctx->tstates = 0;
	while (ctx->tstates < tstates)
		Z80Execute(ctx);
	return ctx->tstates;
}


void Z80Debug (Z80Context* ctx, char* dump, char* decode)
{
	char tmp[20];
	struct Z80OpcodeTable* current = &opcodes_main;
	struct Z80OpcodeEntry* entries = current->entries;
	char* fmt;
	byte opcode;
	ushort parm;
	int offset = 0;
	int PC = ctx->PC;
	int size = 0;

	if (dump)
		dump[0] = 0;

	if (decode)
		decode[0] = 0;

	do
	{
		opcode = read8(ctx, PC + offset);
		size++;

		PC++;
		fmt = entries[opcode].format;
		if (fmt != NULL)
		{
			PC -= offset;
			parm = read16(ctx, PC);

			if (entries[opcode].operand_type == OP_NONE)
				size++;
			else
				size += 2;
			if (entries[opcode].operand_type != OP_WORD)
			{
				parm &= 0xFF;
				size--;
			}

			if (decode)
				sprintf(decode, fmt, parm);

			PC += offset;
			break;
		}
		else if (entries[opcode].table != NULL)
		{
			current = entries[opcode].table;
			entries = current->entries;
			offset = current->opcode_offset;
		}

		else
		{
			if (decode != NULL)
				strcpy(decode, "NOP (ignored)");
			break;
		}
	} while(1);

	if (dump)
	{
		for (offset = 0; offset < size; offset++)
		{
			sprintf(tmp, "%02X", read8(ctx, ctx->PC + offset));
			strcat(dump, tmp);
		}
	}
}


void Z80RESET (Z80Context* ctx)
{
	ctx->PC = 0x0000;
	BR.F = 0;
	ctx->IM = 0;
	ctx->IFF1 = ctx->IFF2 = 0;
	ctx->R = 0;
	ctx->I = 0;
	ctx->halted = 0;
	ctx->tstates = 0;
	ctx->nmi_req = 0;
	ctx->int_req = 0;
	ctx->defer_int = 0;
	ctx->exec_int_vector = 0;
}


void Z80INT (Z80Context* ctx, byte value)
{
	ctx->int_req = 1;
	ctx->int_vector = value;
}


void Z80NMI (Z80Context* ctx)
{
	ctx->nmi_req = 1;
}

