/****************************************************************************
*                                                                           *
* Project64 - A Nintendo 64 emulator.                                      *
* http://www.pj64-emu.com/                                                  *
* Copyright (C) 2012 Project64. All rights reserved.                        *
*                                                                           *
* License:                                                                  *
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html                        *
*                                                                           *
****************************************************************************/
#pragma once

#include <stdafx.h>

#include <Project64-core\N64System\Mips\OpCode.h>

class COpInfo
{
public:

    OPCODE m_OpCode;

    COpInfo()
    {
    }

    inline COpInfo(OPCODE opcode):
        m_OpCode(opcode)
    {
    }

    bool IsStaticJump()
    {
        // j, jal

        uint32_t op = m_OpCode.op;

        if (op == R4300i_J || op == R4300i_JAL)
        {
            return true;
        }

        return false;
    }

    bool IsJump()
    {
        // j, jal, jr, jalr, exception

        uint32_t op = m_OpCode.op;

        if (op == R4300i_J || op == R4300i_JAL)
        {
            return true;
        }

        if (op == R4300i_SPECIAL)
        {
            uint32_t fn = m_OpCode.funct;

            if (fn >= R4300i_SPECIAL_JR && fn <= R4300i_SPECIAL_BREAK)
            {
                return true;
            }
        }

        if (op == R4300i_REGIMM)
        {
            uint32_t rt = m_OpCode.rt;

            if (rt >= R4300i_REGIMM_TGEI && rt <= R4300i_REGIMM_TNEI)
            {
                return true;
            }
        }

        if (op == R4300i_CP0)
        {
            if ((m_OpCode.rs & 0x10) != 0)
            {
                uint32_t fn = m_OpCode.funct;
                if (fn == R4300i_COP0_CO_ERET)
                {
                    return true;
                }
            }
        }

        return false;
    }

    bool IsJAL()
    {
        return (m_OpCode.op == R4300i_JAL || (m_OpCode.op == R4300i_SPECIAL && m_OpCode.funct == R4300i_SPECIAL_JALR));
    }

    bool IsBranch()
    {
        uint32_t op = m_OpCode.op;

        if (op >= R4300i_BEQ && op <= R4300i_BGTZ)
        {
            return true;
        }

        if (op >= R4300i_BEQL && op <= R4300i_BGTZL)
        {
            return true;
        }

        if (op == R4300i_REGIMM)
        {
            uint32_t rt = m_OpCode.rt;

            if (rt >= R4300i_REGIMM_BLTZ && rt <= R4300i_REGIMM_BGEZL)
            {
                return true;
            }

            if (rt >= R4300i_REGIMM_BLTZAL && rt <= R4300i_REGIMM_BGEZALL)
            {
                return true;
            }
        }

        if (op == R4300i_CP1 && m_OpCode.fmt == R4300i_COP1_BC)
        {
            return true;
        }

        return false;
    }

    bool IsNOP()
    {
        if (m_OpCode.Hex == 0)
        {
            return true;
        }
        return false;
    }

    inline bool IsLoadStoreCommand()
    {
        return (m_OpCode.op >= R4300i_LDL && m_OpCode.op <= R4300i_SD && m_OpCode.op != R4300i_CACHE);
    }

    inline bool IsLoadCommand()
    {
        return (m_OpCode.op >= R4300i_LDL && m_OpCode.op <= R4300i_LWU ||
                m_OpCode.op >= R4300i_LL && m_OpCode.op <= R4300i_LD);
    }

    inline bool IsStoreCommand()
    {
        return (m_OpCode.op >= R4300i_SB && m_OpCode.op <= R4300i_SWR ||
                m_OpCode.op >= R4300i_SC && m_OpCode.op <= R4300i_SD);
    }

    // addiu sp, sp, x
    bool IsStackShift()
    {
        return (m_OpCode.op == R4300i_ADDIU || m_OpCode.op == R4300i_ADDI) && m_OpCode.rt == 29;
    }

    // addiu sp, sp, <negative value>
    bool IsStackAlloc() 
    {
        if (!IsStackShift())
        {
            return false;
        }

        return (short)m_OpCode.immediate < 0;
    }

    // addiu sp, sp, <positive value>
    bool IsStackFree() 
    {
        if (!IsStackShift())
        {
            return false;
        }

        return (short)m_OpCode.immediate > 0;
    }

    void ReadsGPR(int* nReg1, int* nReg2)
    {
        uint32_t op = m_OpCode.op;

        if (op == R4300i_SPECIAL)
        {
            uint32_t fn = m_OpCode.funct;

            if (fn >= R4300i_SPECIAL_SLLV && fn <= R4300i_SPECIAL_SRAV ||
                fn >= R4300i_SPECIAL_DSLLV && fn <= R4300i_SPECIAL_DSRAV ||
                fn >= R4300i_SPECIAL_MULT && fn <= R4300i_SPECIAL_TNE)
            {
                *nReg1 = m_OpCode.rs;
                *nReg2 = m_OpCode.rt;
                return;
            }

            if (fn == R4300i_SPECIAL_MTLO || fn == R4300i_SPECIAL_MTHI ||
                fn == R4300i_SPECIAL_JR || fn == R4300i_SPECIAL_JALR)
            {
                *nReg1 = m_OpCode.rs;
                *nReg2 = 0;
                return;
            }

            if (fn >= R4300i_SPECIAL_SLL && fn <= R4300i_SPECIAL_SRA ||
                fn >= R4300i_SPECIAL_DSLL && fn <= R4300i_SPECIAL_DSRA32)
            {
                *nReg1 = m_OpCode.rt;
                *nReg2 = 0;
                return;
            }

            *nReg1 = 0;
            *nReg2 = 0;
            return;
        }

        if (op >= R4300i_SB && op <= R4300i_SWR ||
            op == R4300i_SC || op == R4300i_SD ||
            op == R4300i_BEQ || op == R4300i_BEQL ||
            op == R4300i_BNE || op == R4300i_BNEL)
        {
            *nReg1 = m_OpCode.rs;
            *nReg2 = m_OpCode.rt;
            return;
        }

        if (op >= R4300i_BLEZL && op <= R4300i_LWU ||
            op >= R4300i_BLEZ && op <= R4300i_XORI ||
            op >= R4300i_CACHE && op <= R4300i_LD ||
            op >= R4300i_SWC1 && op <= R4300i_SDC2 ||
            op == R4300i_REGIMM)
        {
            *nReg1 = m_OpCode.rs;
            *nReg2 = 0;
            return;
        }

        if (op == R4300i_CP0 && m_OpCode.fmt == R4300i_COP0_MT)
        {
            *nReg1 = m_OpCode.rt;
            *nReg2 = 0;
            return;
        }

        if (op == R4300i_CP1)
        {
            if (m_OpCode.fmt == R4300i_COP1_MT ||
                m_OpCode.fmt == R4300i_COP1_DMT ||
                m_OpCode.fmt == R4300i_COP1_CT)
            {
                *nReg1 = m_OpCode.rt;
                *nReg2 = 0;
                return;
            }
        }

        *nReg1 = 0;
        *nReg2 = 0;
    }

    inline void WritesGPR(int* nReg)
    {
        uint32_t op = m_OpCode.op;

        if (op == R4300i_SPECIAL)
        {
            uint32_t fn = m_OpCode.funct;

            if (fn >= R4300i_SPECIAL_SLL && fn <= R4300i_SPECIAL_SRAV ||
                fn >= R4300i_SPECIAL_DSLLV && fn <= R4300i_SPECIAL_DSRAV ||
                fn >= R4300i_SPECIAL_DIVU && fn <= R4300i_SPECIAL_DSUBU ||
                fn >= R4300i_SPECIAL_DSLL && fn <= R4300i_SPECIAL_DSRA32 ||
                fn == R4300i_SPECIAL_JALR || fn == R4300i_SPECIAL_MFLO ||
                fn == R4300i_SPECIAL_MFHI)
            {
                *nReg = m_OpCode.rd;
                return;
            }

            *nReg = 0;
            return;
        }

        if (op >= R4300i_DADDI && op <= R4300i_LWU ||
            op >= R4300i_ADDI && op <= R4300i_LUI ||
            op == R4300i_LL || op == R4300i_LD ||
            (op == R4300i_CP0 && m_OpCode.fmt == R4300i_COP0_MF) ||
            (op == R4300i_CP1 && m_OpCode.fmt == R4300i_COP1_MF) ||
            (op == R4300i_CP1 && m_OpCode.fmt == R4300i_COP1_CF))
        {
            *nReg = m_OpCode.rt;
            return;
        }

        if (op == R4300i_JAL)
        {
            *nReg = 31; // RA
            return;
        }

        *nReg = 0;
    }

    inline bool ReadsHI()
    {
        return (m_OpCode.op == R4300i_SPECIAL && m_OpCode.funct == R4300i_SPECIAL_MFHI);
    }

    inline bool ReadsLO()
    {
        return (m_OpCode.op == R4300i_SPECIAL && m_OpCode.funct == R4300i_SPECIAL_MFLO);
    }

    inline bool WritesHI()
    {
        if (m_OpCode.op == R4300i_SPECIAL)
        {
            if (m_OpCode.funct == R4300i_SPECIAL_MTHI ||
                m_OpCode.funct >= R4300i_SPECIAL_MULT && m_OpCode.funct <= R4300i_SPECIAL_DDIVU)
            {
                return true;
            }
        }
        return false;
    }

    inline bool WritesLO()
    {
        if (m_OpCode.op == R4300i_SPECIAL)
        {
            if (m_OpCode.funct == R4300i_SPECIAL_MTLO ||
                m_OpCode.funct >= R4300i_SPECIAL_MULT && m_OpCode.funct <= R4300i_SPECIAL_DDIVU)
            {
                return true;
            }
        }
        return false;
    }

    inline uint32_t GetLoadStoreAddress()
    {
        return g_Reg->m_GPR[m_OpCode.base].UW[0] + (int16_t)m_OpCode.offset;
    }

    inline uint32_t GetStoreValueUnsigned()
    {
        return g_Reg->m_GPR[m_OpCode.rt].UW[0];
    }

    inline int NumBytesToLoad()
    {
        switch (m_OpCode.op)
        {
        case R4300i_LB:
        case R4300i_LBU:
            return 1;
        case R4300i_LH:
        case R4300i_LHU:
            return 2;
        case R4300i_LD:
        case R4300i_LDL:
        case R4300i_LDR:
        case R4300i_LDC1:
            return 8;
        default:
            return 4;
        }
    }

    inline int NumBytesToStore()
    {
        switch (m_OpCode.op)
        {
        case R4300i_SB:
            return 1;
        case R4300i_SH:
            return 2;
        case R4300i_SD:
        case R4300i_SDL:
        case R4300i_SDR:
        case R4300i_SDC1:
        case R4300i_SDC2:
            return 8;
        default:
            return 4;
        }
    }
};