/*
 * Author: Pawel Jablonski
 * E-mail: pj@xirx.net
 * WWW: xirx.net
 * GIT: git.xirx.net
 *
 * License: You can use this code however you like
 * but leave information about the original author.
 * Code is free for non-commercial and commercial use.
 */

#include "cpu.h"

/**
 * Constructor for the CPU class
 *
 * @param parent Parent object
 */
CPU::CPU(QObject *parent) : QObject(parent)
{
	this->urom0.data.fill(0);
	this->urom1.data.fill(0);
	this->rom.data.fill(0);

	this->reset();

	QObject::connect(&this->timer, SIGNAL(timeout()), this, SLOT(emulation()));
}

/**
 * Set first uROM memory data
 *
 * @param urom uROM data
 */
void CPU::setUrom0(const CPU::Memory &urom)
{
	this->urom0 = urom;
}

/**
 * Set second uROM memory data
 *
 * @param urom uROM data
 */
void CPU::setUrom1(const CPU::Memory &urom)
{
	this->urom1 = urom;
}

/**
 * Set ROM memory data
 *
 * @param rom ROM data
 */
void CPU::setRom(const CPU::Memory &rom)
{
	this->rom = rom;
}

//! Run executing emulation
void CPU::run()
{
	this->stepMode = false;

	this->timer.setSingleShot(false);
	this->timer.setInterval(INTERVAL);
	this->timer.start();
}

//! Run only one CPU step of emulation
void CPU::step()
{
	this->stepMode = true;

	this->timer.setSingleShot(true);
	this->timer.start(0);
}

//! Pause executing emulation
void CPU::pause()
{
	this->timer.stop();
}

//! Stop executing emulation
void CPU::stop()
{
	this->timer.stop();

	this->reset();
}

/**
 * Get register buffer
 *
 * @return Register buffer
 */
const CPU::Reg &CPU::getReg() const
{
	return(this->reg);
}

/**
 * Get ticks counter value
 *
 * @return Tick counter value
 */
unsigned long long CPU::getTicks() const
{
	return(this->ticks);
}

/**
 * Get ROM buffer
 *
 * @return ROM buffer
 */
const CPU::Memory &CPU::getRom() const
{
	return(this->rom);
}

/**
 * Get lower RAM buffer
 *
 * @return Lower RAM buffer
 */
const CPU::Memory &CPU::getRamLow() const
{
	return(this->ramLow);
}

/**
 * Get higher RAM buffer
 *
 * @return Higher RAM buffer
 */
const CPU::Memory &CPU::getRamHigh() const
{
	return(this->ramHigh);
}

//! Stop executing and reset data of emulation
void CPU::reset()
{
	this->stepMode = false;
	this->ticks = 0;

	this->timer.stop();

	this->ramLow.data.fill(0);
	this->ramHigh.data.fill(0);

	this->reg.i = 0;
	this->reg.c = false;
	this->reg.z = false;
	this->reg.pch = 0;
	this->reg.pcl = 0;
	this->reg.sph = STACK_PAGE;
	this->reg.spl = 0;
	this->reg.maxSpl = 0;
	this->reg.a = 0;
	this->reg.b = 0;
	this->reg.x = 0;
	this->reg.y = 0;
	this->reg.in = 0;
	this->reg.out = 0;
	this->reg.d = 0;
	this->reg.t = 0;
	this->reg.mah = 0;
	this->reg.mal = 0;
}

/**
 * Compute logic or arithmetic using ALU. It emulates functions of the 74181 chip.
 *
 * @param a First operant
 * @param b Second operant
 * @param s Operation type selector in range 0-15
 * @param m Operatiom mode selector. "0" for arithmetic and "1" for logic mode.
 * @param c Carry flag
 * @param z Zero flag
 *
 * @return Computed value
 */
unsigned char CPU::alu(unsigned char a, unsigned char b, unsigned char s, bool m, bool &c, bool &z)
{
	QVector<bool> inA(4);
	QVector<bool> inB(4);
	QVector<bool> inS(4);

	inA[0] = ((a & (1 << 0)) != 0);
	inA[1] = ((a & (1 << 1)) != 0);
	inA[2] = ((a & (1 << 2)) != 0);
	inA[3] = ((a & (1 << 3)) != 0);

	inB[0] = ((b & (1 << 0)) != 0);
	inB[1] = ((b & (1 << 1)) != 0);
	inB[2] = ((b & (1 << 2)) != 0);
	inB[3] = ((b & (1 << 3)) != 0);

	inS[0] = ((s & (1 << 0)) != 0);
	inS[1] = ((s & (1 << 1)) != 0);
	inS[2] = ((s & (1 << 2)) != 0);
	inS[3] = ((s & (1 << 3)) != 0);

	QVector<QVector<bool>> l1(4, QVector<bool>(5));

	for(int i = 0; i < 4; i++)
	{
		l1[i][0] = inA[i];
		l1[i][1] = (inB[i] & inS[0]);
		l1[i][2] = ((!inB[i]) & inS[1]);
		l1[i][3] = ((!inB[i]) & inA[i] & inS[2]);
		l1[i][4] = (inA[i] & inB[i] & inS[3]);
	}

	QVector<QVector<bool>> l2(4, QVector<bool>(2));

	for(int i = 0; i < 4; i++)
	{
		l2[i][0] = !(l1[i][0] | l1[i][1] | l1[i][2]);
		l2[i][1] = !(l1[i][3] | l1[i][4]);
	}

	QVector<bool> l3(20);

	l3[0] = !(c & (!m));

	l3[1] = !(l2[0][0]);
	l3[2] = ((!m) & l2[0][0]);
	l3[3] = ((!m) & l2[0][1] & c);

	l3[4] = !(l2[1][0]);
	l3[5] = ((!m) & l2[1][0]);
	l3[6] = ((!m) & l2[0][0] & l2[1][1]);
	l3[7] = ((!m) & l2[0][1] & l2[1][1] & c);

	l3[8] = !(l2[2][0]);
	l3[9] = ((!m) & l2[2][0]);
	l3[10] = ((!m) & l2[1][0] & l2[2][1]);
	l3[11] = ((!m) & l2[0][0] & l2[1][1] & l2[2][1]);
	l3[12] = ((!m) & l2[0][1] & l2[1][1] & l2[2][1] & c);

	l3[13] = !(l2[3][0]);
	l3[14] = !(l2[0][1] & l2[1][1] & l2[2][1] & l2[3][1]);
	l3[15] = !(l2[0][1] & l2[1][1] & l2[2][1] & l2[3][1] & c);

	l3[16] = (l2[0][0] & l2[1][1] & l2[2][1] & l2[3][1]);
	l3[17] = (l2[1][0] & l2[2][1] & l2[3][1]);
	l3[18] = (l2[2][0] & l2[3][1]);
	l3[19] = (l2[3][0]);

	QVector<bool> l4(8);

	l4[0] = (l3[1] & l2[0][1]);
	l4[1] = !(l3[2] | l3[3]);

	l4[2] = (l3[4] & l2[1][1]);
	l4[3] = !(l3[5] | l3[6] | l3[7]);

	l4[4] = (l3[8] & l2[2][1]);
	l4[5] = !(l3[9] | l3[10] | l3[11] | l3[12]);

	l4[6] = (l3[13] & l2[3][1]);
	l4[7] = !(l3[16] | l3[17] | l3[18] | l3[19]);

	QVector<bool> f(4);

	f[0] = (l3[0] ^ l4[0]);
	f[1] = (l4[1] ^ l4[2]);
	f[2] = (l4[3] ^ l4[4]);
	f[3] = (l4[5] ^ l4[6]);

	z = (f[0] & f[1] & f[2] & f[3]);
	c = ((!l3[15]) | (!l4[7]));

	unsigned char out = 0;

	out += (f[0] ? 1 : 0);
	out += (f[1] ? 2 : 0);
	out += (f[2] ? 4 : 0);
	out += (f[3] ? 8 : 0);

	return(out);
}

/**
 * Set Input register value
 *
 * @param in Input register value
 */
void CPU::inSlot(unsigned char in)
{
	if(this->ticks > 0)
	{
		this->reg.in = ((this->reg.in & (1 << 4)) | (in & ~(1 << 4)));
	}
}

//! Process one step of the emulation
void CPU::emulation()
{
	unsigned long long tick = 0;

	unsigned char uromCycle = 0;
	int uromAddress;
	unsigned char urom0;
	unsigned char urom1;

	BusAR busAR;
	BusAW busAW;
	BusB busB;
	BusC busC;

	int address;

	unsigned char aluS;
	bool aluM;
	bool aluC;

	unsigned char busARregABXY;
	unsigned char busAWregABXY;
	unsigned char busBregAB;

	unsigned char valueAR = 0;
	unsigned char valueB = 0;

	while(tick < TICKS_PER_INTERVAL)
	{
		do
		{
			uromAddress = (static_cast<int>(this->reg.i) << UROM_ADDRESS_INSTRUCTION_POSITION);
			uromAddress += (static_cast<int>(uromCycle & UROM_ADDRESS_CYCLE_MASK) << UROM_ADDRESS_CYCLE_POSITION);
			uromAddress += (static_cast<int>(this->reg.c ? 1 : 0) << UROM_ADDRESS_FLAG_C_POSITION);
			uromAddress += (static_cast<int>(this->reg.z ? 1 : 0) << UROM_ADDRESS_FLAG_Z_POSITION);

			urom0 = this->urom0.data[uromAddress];
			urom1 = this->urom1.data[uromAddress];

			uromCycle++;

			busAR = static_cast<BusAR>((urom0 & UROM_0_BUS_AR_MASK) >> UROM_0_BUS_AR_POSITION);
			busAW = static_cast<BusAW>((urom0 & UROM_0_BUS_AW_MASK) >> UROM_0_BUS_AW_POSITION);
			busB = static_cast<BusB>((urom0 & UROM_0_BUS_B_MASK) >> UROM_0_BUS_B_POSITION);
			busC = static_cast<BusC>((urom1 & UROM_1_BUS_C_MASK) >> UROM_1_BUS_C_POSITION);

			aluS = ((urom1 & UROM_1_ALU_S_MASK) >> 2);
			aluM = ((urom1 & UROM_1_ALU_M_MASK) != 0);
			aluC = ((urom1 & UROM_1_ALU_C_MASK) != 0);

			if((busAR == BusAR::ABXY) && (busAW == BusAW::ABXY))
			{
				busARregABXY = ((this->reg.i & INSTRUCTION_REG_ABXY_SECOND_MASK) >> INSTRUCTION_REG_ABXY_SECOND_OFFSET);
			}
			else
			{
				busARregABXY = ((this->reg.i & INSTRUCTION_REG_ABXY_FIRST_MASK) >> INSTRUCTION_REG_ABXY_FIRST_OFFSET);
			}

			busAWregABXY = (this->reg.i & INSTRUCTION_REG_ABXY_FIRST_MASK);
			busBregAB = ((this->reg.i & INSTRUCTION_REG_AB_MASK) >> INSTRUCTION_REG_AB_OFFSET);

			switch(busC)
			{
				case BusC::PC :
					address = ((static_cast<int>(this->reg.pch) << 8) + static_cast<int>(this->reg.pcl));
					break;

				case BusC::MA :
					address = ((static_cast<int>(this->reg.mah) << 8) + static_cast<int>(this->reg.mal));
					break;

				case BusC::SP :
					address = ((static_cast<int>(this->reg.sph) << 8) + static_cast<int>(this->reg.spl));
					break;

				case BusC::XY :
					address = ((static_cast<int>(this->reg.y) << 8) + static_cast<int>(this->reg.x));
					break;
			}

			switch(busAR)
			{
				case BusAR::ABXY :
					switch(busARregABXY)
					{
						case 0 :
							valueAR = this->reg.a;
							break;

						case 1 :
							valueAR = this->reg.b;
							break;

						case 2 :
							valueAR = this->reg.x;
							break;

						case 3 :
							valueAR = this->reg.y;
							break;
					}
					break;

				case BusAR::D :
					valueAR = this->reg.d;
					break;

				case BusAR::IN :
					valueAR = this->reg.in;
					break;

				case BusAR::T :
					valueAR = this->reg.t;
					break;

				case BusAR::Ram :
					if(address < MEMORY_SIZE)
					{
						valueAR = this->ramLow.data[address];
					}
					else
					{
						valueAR = this->ramHigh.data[address - MEMORY_SIZE];
					}
					break;

				case BusAR::Flash :
					if(address < MEMORY_SIZE)
					{
						valueAR = this->rom.data[address];
					}
					else
					{
						valueAR = this->ramHigh.data[address - MEMORY_SIZE];
					}
					break;

				case BusAR::PCL :
					valueAR = this->reg.pcl;
					break;

				case BusAR::PCH :
					valueAR = this->reg.pch;
					break;
			}

			switch(busB)
			{
				case BusB::AB :
					switch(busBregAB)
					{
						case 0 :
							valueB = this->reg.a;
							break;

						case 1 :
							valueB = this->reg.b;
							break;
					}
					break;

				case BusB::D :
					valueB = this->reg.d;
					break;
			}

			switch(busAW)
			{
				case BusAW::None :
					break;

				case BusAW::ABXY :
					switch(busAWregABXY)
					{
						case 0 :
							this->reg.a = valueAR;
							break;

						case 1 :
							this->reg.b = valueAR;
							break;

						case 2 :
							this->reg.x = valueAR;
							break;

						case 3 :
							this->reg.y = valueAR;
							break;
					}
					break;

				case BusAW::D :
					this->reg.d = valueAR;
					break;

				case BusAW::OUT :
					{
						this->reg.out = valueAR;

						emit outSignal(this->reg.out);
					}
					break;

				case BusAW::ALU_T :
					{
						QVector<bool> z(2);
						bool c = aluC;
						unsigned char value = 0;

						value = this->alu((valueAR & ALU_4BIT_MASK), (valueB & ALU_4BIT_MASK), aluS, aluM, c, z[0]);
						value += (this->alu((valueAR >> ALU_4BIT_OFFSET), (valueB >> ALU_4BIT_OFFSET), aluS, aluM, c, z[1]) << ALU_4BIT_OFFSET);

						this->reg.t = value;
						this->reg.c = c;
						this->reg.z = (z[0] & z[1]);
					}
					break;

				case BusAW::RPC :
					uromCycle = 0;
					break;

				case BusAW::I :
					this->reg.i = valueAR;
					break;

				case BusAW::Ram :
					if(address < MEMORY_SIZE)
					{
						this->ramLow.data[address] = valueAR;
					}
					else
					{
						this->ramHigh.data[address - MEMORY_SIZE] = valueAR;
					}
					break;

				case BusAW::PCL :
					this->reg.pcl = valueAR;
					break;

				case BusAW::PCH :
					this->reg.pch = valueAR;
					break;

				case BusAW::MAL :
					this->reg.mal = valueAR;
					break;

				case BusAW::MAH :
					this->reg.mah = valueAR;
					break;

				case BusAW::PC_PLUS :
					{
						unsigned int pcAddress = ((static_cast<unsigned int>(this->reg.pch) << 8) + static_cast<unsigned int>(this->reg.pcl) + 1);

						this->reg.pch = static_cast<unsigned char>(pcAddress >> 8);
						this->reg.pcl = static_cast<unsigned char>(pcAddress & 0xff);
					}
					break;

				case BusAW::SP_PLUS :
					this->reg.spl++;
					this->reg.maxSpl = qMax(this->reg.maxSpl, this->reg.spl);
					break;

				case BusAW::SP_MINUS :
					this->reg.spl--;
					break;

				case BusAW::RPC_PLUS :
					{
						unsigned int pcAddress = ((static_cast<unsigned int>(this->reg.pch) << 8) + static_cast<unsigned int>(this->reg.pcl) + 1);

						this->reg.pch = static_cast<unsigned char>(pcAddress >> 8);
						this->reg.pcl = static_cast<unsigned char>(pcAddress & 0xff);

						uromCycle = 0;
					}
					break;
			}

			tick++;
			this->ticks++;

			if((this->ticks % CLOCK_TICKS_PER_INTERVAL) == 0)
			{
				this->reg.in ^= IO::IN_CLOCK_BIT;
			}
		}
		while(uromCycle > 0);

		if(this->stepMode)
		{
			break;
		}
	}

	emit updateSignal();
}