#pragma once

#include "CPU.h"

// FPU проходит тесты xxdp: dffpa, dffpb, cffpc

enum FEC_CODES : uint16_t
{
	$FOP    = 2,        // floating opcode error
	$FDIVZ  = 4,        // floating divide by 0
	$FINT   = 6,        // floating to integer conversion error
	$FOVER  = 8,        // floating overflow
	$FUNDER = 10,       // floating underflow
	$FUNDF  = 12,       // floating undefined variable
	$FMMT   = 14        // Maintenance trap
};

enum FSR_BITS : uint16_t
{
	$FER   = 0100000,   // Floating ERror
	$FID   = 040000,    // Interrupt Disable
	$FPRES = 030020,    // reserved bits
	$FIUV  = 04000,     // Interrupt on Undefined Variable
	$FIU   = 02000,     // Interrupt on Underflow
	$FIV   = 01000,     // Interrupt on oVerflow
	$FIC   = 0400,      // Interrupt on integer Conversion error
	$FD    = 0200,      // floating Double precision mode
	$FL    = 0100,      // floating Long integer mode
	$FT    = 040,       // floating chop mode
	$FMM   = 020,       // Floating Maintenance Mode
	$FNZVC = 017,       // маска битов NZVC
	$FN    = 010,       // floating Negative
	$FZ    = 04,        // floating Zero
	$FVC   = 03,        // маска битов VC
	$FV    = 02,        // floating oVerflow
	$FC    = 01         // floating Carry
};

enum class OVR_FUNC
{
	STD,
	MODx,
	LDEXP
};

class CFPUException
{
	public:
		int             m_code;
		CString         m_info;

		CFPUException(const FEC_CODES code)
			: m_code(code)
		{
			switch (code)
			{
				case $FOP:
					m_info = _T("Floating opcode error");
					break;

				case $FDIVZ:
					m_info = _T("Floating divide by 0");
					break;

				case $FINT:
					m_info = _T("Floating to integer conversion error");
					break;

				case $FOVER:
					m_info = _T("Floating overflow");
					break;

				case $FUNDER:
					m_info = _T("Floating underflow");
					break;

				case $FUNDF:
					m_info = _T("Floating undefined variable");
					break;

				case $FMMT:
					m_info = _T("Maintenance trap");
					break;

				default:
					m_info = _T("Unknown error");
					break;
			}
		}

		virtual ~CFPUException() = default;
};


class CFPU
{
		CCPU           *m_pCPU;
		uint16_t        $FPSR;         // регистр состояния FPP
		uint16_t        $FEC;          // Floating Exception Code register
		uint16_t        $FEA;          // Floating Exception Address register
		uint64_t        $AC[8];        // storage for floating ACcumulators

		int             m_nMeth;    // метод адресации FSRC/FDST, src/dst
		CPUREG::REGS m_nReg;     // регистр  FSRC/FDST, src/dst
		int             m_nAc;      // аккумулятор - биты 7-6

		struct UnpackedFLT
		{
			uint64_t nFrac; // мантисса числа
			int      nExp;  // порядок числа
			bool     bSign; // знак, true - минус, false - плюс
			UnpackedFLT(): nFrac(0), nExp(0), bSign(false) {}
			UnpackedFLT(uint64_t f, int e, bool b): nFrac(f), nExp(e), bSign(b) {}
		};

		uint16_t        m_nOldPC;
		uint16_t        m_nDSTEA;
		int             m_nDSTLen;
		bool            m_bError;

		void            ExecuteILLEGAL();
		// инструкции класса 0
		void            ExecuteCFCC(); // 170000
		void            ExecuteSETF(); // 170001
		void            ExecuteSETI(); // 170002
		void            ExecuteSETD(); // 170011
		void            ExecuteSETL(); // 170012

		// инструкции класса 1
		void            ExecuteLDFPS(); // 1701XX
		void            ExecuteSTFPS(); // 1702XX
		void            ExecuteSTST();  // 1703XX
		void            ExecuteCLRX();  // 1704XX
		void            ExecuteTSTX();  // 1705XX
		void            ExecuteABSX();  // 1706XX
		void            ExecuteNEGX();  // 1707XX

		// инструкции класса 2
		void            ExecuteMULX();  // 1710XX
		void            ExecuteMODX();  // 1714XX
		void            ExecuteADDX();  // 1720XX
		void            ExecuteLDX();   // 1724XX
		void            ExecuteSUBX();  // 1730XX
		void            ExecuteCMPX();  // 1734XX
		void            ExecuteSTX();   // 1740XX
		void            ExecuteDIVX();  // 1744XX
		void            ExecuteSTEXP(); // 1750XX
		void            ExecuteSTCXJ(); // 1754XX
		void            ExecuteSTCXY(); // 1760XX
		void            ExecuteLDEXP(); // 1764XX
		void            ExecuteLDCJX(); // 1770XX
		void            ExecuteLDCYX(); // 1774XX

		uint16_t        GetArgAddr(int &len);
		uint16_t        CalcArgAddrX(int &len);

		uint32_t        GetInt(int len);
		uint32_t        GetI();
		uint32_t        GetL();
		uint32_t        GetSrc();

		uint64_t        GetXdef();
		uint64_t        GetX(uint16_t nTempFPSR);

		void            PutInt(uint32_t arg, int len);
		void            PutI(uint32_t arg);
		void            PutL(uint32_t arg);
		void            PutDst(uint32_t arg);

		void            PutX(uint16_t nTempFPSR, uint64_t arg);
		void            PutXDst(uint64_t arg);
		void            WriteDst(const uint16_t nTempFPSR, int len, const uint16_t addr, const uint64_t arg);

		bool            Over();
		bool            Under();

		bool            Over2();
		bool            Under2();

		void            SFNZ(const uint64_t arg);
		void            SFNZi(const uint32_t arg);
		void            SFNZ00(const uint64_t arg);
		void            CFCC();

		uint64_t        GetACdef() const;
		uint64_t        GetAC(const int nAC) const;
		void            PutACdef(const uint64_t arg);
		void            PutAC(const uint16_t nTempFPSR, const int nAC, const uint64_t arg);

		void            Unpack(uint64_t d, UnpackedFLT *nArg);
		uint64_t        Pack(UnpackedFLT *nArg, const bool bRoundTrunk, const OVR_FUNC func = OVR_FUNC::STD);

		void            I_add(const bool bAdding);
		UnpackedFLT     I_mul();
		void            Normalize(UnpackedFLT *uf);

		using ExecuteMethodRef = void (CFPU::*)();
		std::unique_ptr<ExecuteMethodRef[]> m_pExecuteMethodMap;
		void            RegisterMethodRef(uint16_t start, uint16_t end, ExecuteMethodRef methodref);
		void            PrepareCPU();   // Инициализация таблицы декодирования команд
		void            DoneCPU();      // удаление таблицы декодирования команд и освобождение памяти

		bool            MakeThrow(FEC_CODES code);

	public:
		CFPU(CCPU *pCPU);
		~CFPU();
		void            init();
		void            reset();
		int             TranslateInstruction(uint16_t nInstr);
		uint64_t        GetAccumulator(int n) const
		{
			if (n < 8)
			{
				return $AC[n];
			}

			ASSERT(FALSE);
			return 0;
		}
};
