/*
    Copyright © 2019 Michal Schulz <michal.schulz@gmx.de>
    https://github.com/michalsc

    This Source Code Form is subject to the terms of the
    Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
    with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include "support.h"
#include "M68k.h"
#include "RegisterAllocator.h"
#include "cache.h"

uint32_t *EMIT_moveq(uint32_t *ptr, uint16_t **m68k_ptr, uint16_t *insn_consumed)
{
    uint8_t update_mask = M68K_GetSRMask(*m68k_ptr);
    uint16_t opcode = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[0]);
    int8_t value = opcode & 0xff;
    uint8_t reg = (opcode >> 9) & 7;
    uint8_t tmp_reg = RA_MapM68kRegisterForWrite(&ptr, reg);
    *insn_consumed = 1;

    if (opcode & 0x100) {
        ptr = EMIT_FlushPC(ptr);
        ptr = EMIT_InjectDebugString(ptr, "[JIT] opcode %04x at %08x not implemented\n", opcode, *m68k_ptr - 1);
        *ptr++ = svc(0x100);
        *ptr++ = svc(0x101);
        *ptr++ = svc(0x103);
        *ptr++ = (uint32_t)(uintptr_t)(*m68k_ptr - 8);
        *ptr++ = 48;
        ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
        *ptr++ = INSN_TO_LE(0xffffffff);
        return ptr;
    }

    (*m68k_ptr)++;

    *ptr++ = mov_immed_s8(tmp_reg, value);
    ptr = EMIT_AdvancePC(ptr, 2);

    if (update_mask)
    {
        uint8_t cc = RA_ModifyCC(&ptr);
        uint8_t alt_mask = update_mask;
        if ((alt_mask & 3) != 0 && (alt_mask & 3) < 3)
            alt_mask ^= 3;
        ptr = EMIT_ClearFlags(ptr, cc, alt_mask);
        if (value <= 0) {
            if (value < 0)
                ptr = EMIT_SetFlags(ptr, cc, SR_N);
            else
                ptr = EMIT_SetFlags(ptr, cc, SR_Z);
        }
    }

    return ptr;
}

uint32_t GetSR_Line7(uint16_t opcode)
{
    /* Line7 is moveq, if bit8 == 0, otherwise illegal */
    if (opcode & 0x100) {
        return SR_CCR << 16;    // Illegal, needs all CCR
    } else {
        return SR_NZVC;         // moveq needs none, sets NZVC
    }
}



uint32_t *EMIT_move(uint32_t *ptr, uint16_t **m68k_ptr, uint16_t *insn_consumed)
{
    uint8_t update_mask = M68K_GetSRMask(*m68k_ptr);
    uint16_t opcode = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[0]);
    int move_length = M68K_GetINSNLength(*m68k_ptr);
    uint8_t ext_count = 0;
    uint8_t tmp_reg = 0xff;
    uint8_t size = 1;
    uint8_t tmp = 0;
    uint8_t is_movea = (opcode & 0x01c0) == 0x0040;
    int is_load_immediate = 0;
    uint32_t immediate_value = 0;
    int loaded_in_dest = 0;
    *insn_consumed = 1;
    int done = 0;
    int fused_opcodes = 0;

    // Move from/to An in byte size is illegal
    if ((opcode & 0xf000) == 0x1000)
    {
        if (is_movea || (opcode & 0x38) == 0x08) {
            ptr = EMIT_FlushPC(ptr);
            ptr = EMIT_InjectDebugString(ptr, "[JIT] opcode %04x at %08x not implemented\n", opcode, *m68k_ptr - 1);
            *ptr++ = svc(0x100);
            *ptr++ = svc(0x101);
            *ptr++ = svc(0x103);
            *ptr++ = (uint32_t)(uintptr_t)(*m68k_ptr - 8);
            *ptr++ = 48;
            ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
            *ptr++ = INSN_TO_LE(0xffffffff);
            return ptr;
        }
    }

    if ((opcode & 0x3f) > 0x3c)
    {
        ptr = EMIT_FlushPC(ptr);
        ptr = EMIT_InjectDebugString(ptr, "[JIT] opcode %04x at %08x not implemented\n", opcode, *m68k_ptr - 1);
        *ptr++ = svc(0x100);
        *ptr++ = svc(0x101);
        *ptr++ = svc(0x103);
        *ptr++ = (uint32_t)(uintptr_t)(*m68k_ptr - 8);
        *ptr++ = 48;
        ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
        *ptr++ = INSN_TO_LE(0xffffffff);
        return ptr;
    }

    if ((opcode & 0x01c0) == 0x01c0) {
        if ((opcode & 0x0c00)) {
            ptr = EMIT_FlushPC(ptr);
            ptr = EMIT_InjectDebugString(ptr, "[JIT] opcode %04x at %08x not implemented\n", opcode, *m68k_ptr - 1);
            *ptr++ = svc(0x100);
            *ptr++ = svc(0x101);
            *ptr++ = svc(0x103);
            *ptr++ = (uint32_t)(uintptr_t)(*m68k_ptr - 8);
            *ptr++ = 48;
            ptr = EMIT_Exception(ptr, VECTOR_ILLEGAL_INSTRUCTION, 0);
            *ptr++ = INSN_TO_LE(0xffffffff);
            return ptr;
        }
    }

    /*
        Check if MOVE is qualified for merging:
        - move.l Reg, -(An)
        - move.l Reg, (An)+
        - move.l -(An), Reg
        - move.l (An)+, Reg
    */
    if ((opcode & 0xf000) == 0x2000)
    {
        // Fetch 2nd opcode just now
        uint16_t opcode2 = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[1]);

        // Is move.l Reg, -(An) ?: Dest mode 100, source mode 000 or 001
        if ((opcode & 0x01f0) == 0x0100)
        {
            // Candidate found. Is next opcode of same kind and same -(An)?
            if (
                (opcode2 & 0xf000) == 0x2000 &&          // move.l
                (opcode2 & 0x0ff0) == (opcode & 0x0ff0)  // same dest reg, same mode?
            )
            {
                uint8_t addr_reg = RA_MapM68kRegisterForWrite(&ptr, ((opcode >> 9) & 7) + 8);
                uint8_t src_reg_1 = RA_MapM68kRegister(&ptr, opcode & 0xf);
                uint8_t src_reg_2 = RA_MapM68kRegister(&ptr, opcode2 & 0xf);

                /* Merging not allowed if any of the registers stored is also address register */
                if (!(src_reg_1 == addr_reg || src_reg_2 == addr_reg))
                {
                    /* Two subsequent register moves to -(An) */
                    (*m68k_ptr)++;
                    update_mask = M68K_GetSRMask(*m68k_ptr);
                    (*m68k_ptr)++;

                    if (update_mask)
                    {
                        *ptr++ = cmn_reg(31, src_reg_2, LSL, 0);
                    }

                    *ptr++ = stp_preindex(addr_reg, src_reg_2, src_reg_1, -8);

                    tmp_reg = src_reg_2;
                
                    done = 1;
                    ptr = EMIT_AdvancePC(ptr, 4);
                    *insn_consumed = 2;
                    size = 4;
                }
            }
        }

        // Is move.l Reg, (An)+ ?: Dest mode 011, source mode 000 or 001
        else if ((opcode & 0x01f0) == 0x00c0)
        {
            // Candidate found. Is next opcode of same kind and same (An)+?
            if (
                (opcode2 & 0xf000) == 0x2000 &&          // move.l
                (opcode2 & 0x0ff0) == (opcode & 0x0ff0)  // same dest reg, same mode?
            )
            {
                uint8_t addr_reg = RA_MapM68kRegisterForWrite(&ptr, ((opcode >> 9) & 7) + 8);
                uint8_t src_reg_1 = RA_MapM68kRegister(&ptr, opcode & 0xf);
                uint8_t src_reg_2 = RA_MapM68kRegister(&ptr, opcode2 & 0xf);
                
                /* Merging not allowed if any of the registers stored is also address register */
                if (!(src_reg_1 == addr_reg || src_reg_2 == addr_reg))
                {
                    /* Two subsequent register moves to (An)+ */
                    (*m68k_ptr)++;
                    update_mask = M68K_GetSRMask(*m68k_ptr);
                    (*m68k_ptr)++;

                    if (update_mask)
                    {
                        *ptr++ = cmn_reg(31, src_reg_2, LSL, 0);
                    }

                    *ptr++ = stp_postindex(addr_reg, src_reg_1, src_reg_2, 8);

                    tmp_reg = src_reg_2;
                
                    done = 1;
                    ptr = EMIT_AdvancePC(ptr, 4);
                    *insn_consumed = 2;
                    size = 4;
                }
            }
        }

        // Is move.l (An)+, Reg ?: Dest mode 001 or 000, source mode 011
        else if ((opcode & 0x01b8) == 0x0018)
        {
            // Candidate found. Is next opcode of same kind and same (An)+?
            if (
                (opcode2 & 0xf000) == 0x2000 &&             // move.l
                (opcode2 & 0x01bf) == (opcode & 0x01bf) &&  // same src reg, same mode?
                (opcode2 & 0x0e40) != (opcode & 0x0e40)     // Two different dest registers!
            )
            {
                uint8_t addr_reg = RA_MapM68kRegisterForWrite(&ptr, (opcode & 7) + 8);
                uint8_t dst_reg_1 = RA_MapM68kRegisterForWrite(&ptr, ((opcode >> 9) & 0x7) + ((opcode >> 3) & 8));
                uint8_t dst_reg_2 = RA_MapM68kRegisterForWrite(&ptr, ((opcode2 >> 9) & 0x7) + ((opcode2 >> 3) & 8));
                uint8_t is_movea2 = (opcode2 & 0x01c0) == 0x0040;

                /* merging not allowed if any of the registers stored is also address register */
                if (!(dst_reg_1 == addr_reg || dst_reg_2 == addr_reg))
                {
                    /* Allow merging if two dest registers differ */
                    if (dst_reg_1 != dst_reg_2)
                    {
                        /* Two subsequent register moves from (An)+ */
                        (*m68k_ptr)+=2;
                        
                        *ptr++ = ldp_postindex(addr_reg, dst_reg_1, dst_reg_2, 8);

                        if (!is_movea2) {
                            update_mask = M68K_GetSRMask(*m68k_ptr - 1);
                                if (update_mask) {
                                *ptr++ = cmn_reg(31, dst_reg_2, LSL, 0);
                                tmp_reg = dst_reg_2;
                            }
                        }
                        else if (!is_movea) {
                            if (update_mask) {
                                *ptr++ = cmn_reg(31, dst_reg_1, LSL, 0);
                                tmp_reg = dst_reg_1;
                            }
                        }

                        is_movea = is_movea && is_movea2;
                                
                        done = 1;
                        ptr = EMIT_AdvancePC(ptr, 4);
                        *insn_consumed = 2;
                        size = 4;
                    }
                }
            }
        }

        // Is move.l -(An), Reg ?: Dest mode 001 or 000, source mode 011
        else if ((opcode & 0x01b8) == 0x0020)
        {
            // Candidate found. Is next opcode of same kind and same (An)+?
            if (
                (opcode2 & 0xf000) == 0x2000 &&             // move.l
                (opcode2 & 0x01bf) == (opcode & 0x01bf) &&  // same src reg, same mode?
                (opcode2 & 0x0e40) != (opcode & 0x0e40)     // Two different dest registers!
            )
            {
                uint8_t addr_reg = RA_MapM68kRegisterForWrite(&ptr, (opcode & 7) + 8);
                uint8_t dst_reg_1 = RA_MapM68kRegisterForWrite(&ptr, ((opcode >> 9) & 0x7) + ((opcode >> 3) & 8));
                uint8_t dst_reg_2 = RA_MapM68kRegisterForWrite(&ptr, ((opcode2 >> 9) & 0x7) + ((opcode2 >> 3) & 8));
                uint8_t is_movea2 = (opcode2 & 0x01c0) == 0x0040;

                /* merging not allowed if any of the registers stored is also address register */
                if (!(dst_reg_1 == addr_reg || dst_reg_2 == addr_reg))
                {
                    /* Allow merging if two dest registers differ */
                    if (dst_reg_1 != dst_reg_2)
                    {
                        /* Two subsequent register moves to (An)+ */
                        (*m68k_ptr)+=2;

                        *ptr++ = ldp_preindex(addr_reg, dst_reg_2, dst_reg_1, -8);

                        if (!is_movea2) {
                            update_mask = M68K_GetSRMask(*m68k_ptr - 1);
                                if (update_mask) {
                                *ptr++ = cmn_reg(31, dst_reg_2, LSL, 0);
                                tmp_reg = dst_reg_2;
                            }
                        }
                        else if (!is_movea) {
                            if (update_mask) {
                                *ptr++ = cmn_reg(31, dst_reg_1, LSL, 0);
                                tmp_reg = dst_reg_1;
                            }
                        }

                        is_movea = is_movea && is_movea2;
                    
                        done = 1;
                        ptr = EMIT_AdvancePC(ptr, 4);
                        *insn_consumed = 2;
                        size = 4;
                    }
                }
            }
        }
    }

    if (!done)
    {
        int sign_ext = 0;

        /* Reverse destination mode, since this one is reversed in MOVE instruction */
        tmp = (opcode >> 6) & 0x3f;
        tmp = ((tmp & 7) << 3) | (tmp >> 3);

        (*m68k_ptr)++;

        if ((opcode & 0x3000) == 0x1000)
            size = 1;
        else if ((opcode & 0x3000) == 0x2000)
            size = 4;
        else
            size = 2;

        /* Only if target is data register */
        if ((tmp & 0x38) == 0)
        {
            uint16_t opcode2 = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[move_length - 1]);
            
            /* Check if subsequent instruction is extb.l on the same target reg */
            if (size == 1 && (opcode2 & 0xfff8) == 0x49c0 && (opcode2 & 7) == (tmp & 7))
            {
                sign_ext = 1;
                fused_opcodes = 1;
                (*insn_consumed)++;
            }
            /* Check if subsequent instruction is ext.l (word->long) on same target and size is word */
            else if (size == 2 && (opcode2 & 0xfff8) == 0x48c0 && (opcode2 & 7) == (tmp & 7))
            {
                sign_ext = 1;
                fused_opcodes = 1;
                (*insn_consumed)++;
            }
            /* Check if subsequent instructions are ext.w + ext.l on the same target and size is byte */
            else if (size == 1 && (opcode2 & 0xfff8) == 0x4880 && (opcode2 & 7) == (tmp & 7))
            {
                uint16_t opcode3 = cache_read_16(ICACHE, (uintptr_t)&(*m68k_ptr)[move_length]);
                if ((opcode3 & 0xfff8) == 0x48c0 && (opcode3 & 7) == (tmp & 7))
                {
                    sign_ext = 1;
                    fused_opcodes = 2;
                    *insn_consumed+=2;
                }
            }

        }

        /* Copy 32bit from data reg to data reg */
        if (size == 4 || sign_ext)
        {
            /* If source was not a register (this is handled separately), but target is a register */
            if ((opcode & 0x38) != 0 && (opcode & 0x38) != 0x08) {
                if ((tmp & 0x38) == 0) {
                    loaded_in_dest = 1;
                    tmp_reg = RA_MapM68kRegisterForWrite(&ptr, tmp & 7);
                    if (sign_ext)
                        ptr = EMIT_LoadFromEffectiveAddress(ptr, 0x80 | size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
                    else
                        ptr = EMIT_LoadFromEffectiveAddress(ptr, size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
                }
                else if ((tmp & 0x38) == 0x08) {
                    loaded_in_dest = 1;
                    tmp_reg = RA_MapM68kRegisterForWrite(&ptr, 8 + (tmp & 7));
                    ptr = EMIT_LoadFromEffectiveAddress(ptr, size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
                }
            }
        }
        if (!loaded_in_dest)
        {
            if (is_movea && size == 2) {

                if (!((tmp & 7) == (opcode & 7) && ((opcode & 0x38) == 0x18 || (opcode & 0x38) == 0x20)))
                {
                    loaded_in_dest = 1;
                    tmp_reg = RA_MapM68kRegisterForWrite(&ptr, 8 + (tmp & 7));
                }
                // If dest register An and source is (An)+ mode, don't do postincrement at all. Change mode to (An)
                ptr = EMIT_LoadFromEffectiveAddress(ptr, 0x80 | size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 0, NULL);
            }
            else {
                /* No need to check if target is register if sign_ext is active */
                if (sign_ext)
                {
                    tmp_reg = RA_MapM68kRegisterForWrite(&ptr, tmp & 7);
                    loaded_in_dest = 1;
                    ptr = EMIT_LoadFromEffectiveAddress(ptr, 0x80 | size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 1, NULL);
                }
                else
                {
                    ptr = EMIT_LoadFromEffectiveAddress(ptr, size, &tmp_reg, opcode & 0x3f, *m68k_ptr, &ext_count, 1, NULL);
                }
            }
        }

        if ((opcode & 0x3f) == 0x3c) {
            is_load_immediate = 1;
            switch (size) {
                case 4:
                    immediate_value = cache_read_32(ICACHE, (uintptr_t)&(*(uint32_t*)(*m68k_ptr)));
                    break;
                case 2:
                    immediate_value = cache_read_16(ICACHE, (uintptr_t)&(**m68k_ptr));
                    break;
                case 1:
                    immediate_value = ((uint8_t*)*m68k_ptr)[1];
                    break;
            }
        }

        /* In case of movea the value is *always* sign-extended to 32 bits */
        if (is_movea && size == 2) {
            size = 4;
        }

        /* If opcodes were fused, make a size of 4 and reload SR mask **past** the fused stuff */
        if (fused_opcodes)
        {
            size = 4;
            update_mask = M68K_GetSRMask(*m68k_ptr + ext_count + fused_opcodes - 1);
        }

        if (update_mask && !is_load_immediate)
        {
            switch (size)
            {
                case 4:
                    if (!loaded_in_dest && (tmp & 0x38) == 0)
                    {
                        uint8_t dst_reg = RA_MapM68kRegisterForWrite(&ptr, tmp & 7);
                        *ptr++ = adds_reg(dst_reg, 31, tmp_reg, LSL, 0);
                        loaded_in_dest = 1;
                    }
                    else
                        *ptr++ = cmn_reg(31, tmp_reg, LSL, 0);
                    break;
                case 2:
                    *ptr++ = cmn_reg(31, tmp_reg, LSL, 16);
                    break;
                case 1:
                    *ptr++ = cmn_reg(31, tmp_reg, LSL, 24);
                    break;
            }
        }

        if (!loaded_in_dest)
            ptr = EMIT_StoreToEffectiveAddress(ptr, size, &tmp_reg, tmp, *m68k_ptr, &ext_count, 0);

        ptr = EMIT_AdvancePC(ptr, 2 * (ext_count + 1 + fused_opcodes));

        (*m68k_ptr) += ext_count + fused_opcodes;
    }

    if (!is_movea)
    {
        if (update_mask)
        {
            uint8_t cc = RA_ModifyCC(&ptr);

            if (is_load_immediate) {
                int32_t tmp_immediate = 0;
                uint8_t alt_mask = update_mask;
                if ((alt_mask & 3) != 0 && (alt_mask & 3) < 3)
                    alt_mask ^= 3;
                ptr = EMIT_ClearFlags(ptr, cc, alt_mask);
                switch (size)
                {
                    case 4:
                        tmp_immediate = (int32_t)immediate_value;
                        break;
                    case 2:
                        tmp_immediate = (int16_t)immediate_value;
                        break;
                    case 1:
                        tmp_immediate = (int8_t)immediate_value;
                        break;
                }

                if (tmp_immediate < 0 && update_mask & SR_N) {
                    ptr = EMIT_SetFlags(ptr, cc, SR_N);
                }
                if (tmp_immediate == 0 && update_mask & SR_Z) {
                    ptr = EMIT_SetFlags(ptr, cc, SR_Z);
                }
            } else {
                ptr = EMIT_GetNZ00(ptr, cc, &update_mask);

                if (update_mask & SR_Z)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_Z, ARM_CC_EQ);
                if (update_mask & SR_N)
                    ptr = EMIT_SetFlagsConditional(ptr, cc, SR_N, ARM_CC_MI);
            }
        }
    }

    RA_FreeARMRegister(&ptr, tmp_reg);
    return ptr;
}

uint32_t GetSR_Line1(uint16_t opcode)
{
    /* MOVEA case - illegal with byte size */
    if ((opcode & 0x01c0) == 0x0040) {
        kprintf("Undefined Line1\n");
        return SR_CCR << 16;
    }
    
    /* Normal move case: destination allows highest mode + reg of 071 */
    if ((opcode & 0x01c0) == 0x01c0 && (opcode & 0x0e00) > 0x0200) {
        kprintf("Undefined Line1\n");
        return SR_CCR << 16;
    }

    /* Normal move case: source allows highest mode + reg of 074 */
    if ((opcode & 0x0038) == 0x0038 && (opcode & 7) > 4) {
        kprintf("Undefined Line1\n");
        return SR_CCR << 16;
    }

    return SR_NZVC;
}

uint32_t GetSR_Line2(uint16_t opcode)
{
    /* MOVEA case - needs none, sets none */
    if ((opcode & 0x01c0) == 0x0040) {
        return 0;
    }

    /* Normal move case: destination allows highest mode + reg of 071 */
    if ((opcode & 0x01c0) == 0x01c0 && (opcode & 0x0e00) > 0x0200) {
        kprintf("Undefined Line2\n");
        return SR_CCR << 16;
    }

    /* Normal move case: source allows highest mode + reg of 074 */
    if ((opcode & 0x0038) == 0x0038 && (opcode & 7) > 4) {
        kprintf("Undefined Line2\n");
        return SR_CCR << 16;
    }

    return SR_NZVC;
}

uint32_t GetSR_Line3(uint16_t opcode)
{
    return GetSR_Line2(opcode);
}
