// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)

#include <common.h>

namespace skyline::nce {
    /**
     * @note We use unscoped enumerations to leak all register enumerations into the registers namespace
     */
    namespace registers {
        enum X : u8 { X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30 };
        enum W : u8 { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, W16, W17, W18, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30 };
    }

    namespace instructions {
        /**
         * @url https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction
         */
        struct Brk {
            /**
             * @brief Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
             */
            constexpr Brk(u16 value) : sig0(0x0), value(value), sig1(0x6A1) {}

            constexpr bool Verify() {
                return (sig0 == 0x0 && sig1 == 0x6A1);
            }

            union {
                struct {
                    u8 sig0 : 5;  //!< 5-bit signature (0x0)
                    u32 value : 16; //!< 16-bit immediate value
                    u16 sig1 : 11; //!< 11-bit signature (0x6A1)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Brk) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call
         */
        struct Svc {
            constexpr bool Verify() {
                return (sig0 == 0x1 && sig1 == 0x6A0);
            }

            union {
                struct {
                    u8 sig0 : 5;  //!< 5-bit signature (0x0)
                    u32 value : 16; //!< 16-bit immediate value
                    u16 sig1 : 11; //!< 11-bit signature (0x6A1)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Svc) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register
         */
        struct Mrs {
            constexpr Mrs(u32 srcReg, registers::X destReg) : srcReg(srcReg), destReg(destReg), sig(0xD53) {}

            constexpr bool Verify() {
                return (sig == 0xD53);
            }

            union {
                struct {
                    u8 destReg : 5; //!< 5-bit destination register
                    u32 srcReg : 15; //!< 15-bit source register
                    u16 sig : 12; //!< 16-bit signature (0xD53)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Mrs) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/g/base-instructions-alphabetic-order/msr-register-move-general-purpose-register-to-system-register
         */
        struct Msr {
            constexpr bool Verify() {
                return (sig == 0xD51);
            }

            union {
                struct {
                    u8 srcReg : 5; //!< 5-bit destination register
                    u32 destReg : 15; //!< 15-bit source register
                    u16 sig : 12; //!< 16-bit signature (0xD51)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Msr) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch
         */
        struct B {
          public:
            /**
             * @param negate The direction of the supplied offset
             */
            constexpr B(i32 offset, bool negate = false) : offset(negate ? -offset : offset), sig(0x5) {}

            /**
             * @return The offset encoded within the instruction in bytes
             */
            constexpr i32 Offset() {
                return offset * static_cast<i32>(sizeof(u32));
            }

            constexpr bool Verify() {
                return (sig == 0x5);
            }

            union {
                struct {
                    i32 offset : 26; //!< 26-bit branch offset
                    u8 sig : 6;  //!< 6-bit signature (0x5)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(B) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/h/base-instructions-alphabetic-order/bl-branch-with-link
         */
        struct BL {
          public:
            constexpr BL(i32 offset) : offset(offset), sig(0x25) {}

            /**
             * @return The offset encoded within the instruction in bytes
             */
            constexpr i32 Offset() {
                return offset * static_cast<i32>(sizeof(u32));
            }

            constexpr bool Verify() {
                return (sig == 0x25);
            }

            union {
                struct {
                    i32 offset : 26; //!< 26-bit branch offset
                    u8 sig : 6;  //!< 6-bit signature (0x25)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(BL) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/movz-move-wide-with-zero
         */
        struct Movz {
          public:
            /**
             * @param destReg The destination Xn register to store the value in
             * @param imm16 The 16-bit value to store
             * @param shift The offset (in units of 16-bits) in the register to store the value at
             */
            constexpr Movz(registers::X destReg, u16 imm16, u8 shift = 0) : destReg(static_cast<u8>(destReg)), imm16(imm16), hw(shift), sig(0xA5), sf(1) {}

            /**
             * @param destReg The destination Wn register to store the value in
             * @param imm16 The 16-bit value to store
             * @param shift The offset (in units of 16-bits) in the register to store the value at
             */
            constexpr Movz(registers::W destReg, u16 imm16, u8 shift = 0) : destReg(static_cast<u8>(destReg)), imm16(imm16), hw(shift), sig(0xA5), sf(0) {}

            /**
             * @return The shift encoded within the instruction in bytes
             */
            constexpr u8 Shift() {
                return static_cast<u8>(hw * sizeof(u16));
            }

            constexpr bool Verify() {
                return (sig == 0xA5);
            }

            union {
                struct __attribute__((packed)) {
                    u8 destReg : 5;  //!< 5-bit destination register
                    u16 imm16 : 16; //!< 16-bit immediate value
                    u8 hw : 2;  //!< 2-bit offset
                    u8 sig : 8;  //!< 8-bit signature (0xA5)
                    u8 sf : 1;  //!< 1-bit register type
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Movz) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/movk-move-wide-with-keep
         */
        struct Movk {
          public:
            /**
             * @param destReg The destination Xn register to store the value in
             * @param imm16 The 16-bit value to store
             * @param shift The offset (in units of 16-bits) in the register to store the value at
             */
            constexpr Movk(registers::X destReg, u16 imm16, u8 shift = 0) : destReg(static_cast<u8>(destReg)), imm16(imm16), hw(shift), sig(0xE5), sf(1) {}

            /**
             * @param destReg The destination Wn register to store the value in
             * @param imm16 The 16-bit value to store
             * @param shift The offset (in units of 16-bits) in the register to store the value at
             */
            constexpr Movk(registers::W destReg, u16 imm16, u8 shift = 0) : destReg(static_cast<u8>(destReg)), imm16(imm16), hw(shift), sig(0xE5), sf(0) {}

            /**
             * @return The shift encoded within the instruction in bytes
             */
            constexpr u8 Shift() {
                return static_cast<u8>(hw * sizeof(u16));
            }

            constexpr bool Verify() {
                return (sig == 0xE5);
            }

            union {
                struct __attribute__((packed)) {
                    u8 destReg : 5;  //!< 5-bit destination register
                    u16 imm16 : 16; //!< 16-bit immediate value
                    u8 hw : 2;  //!< 2-bit offset
                    u8 sig : 8;  //!< 8-bit signature (0xA5)
                    u8 sf : 1;  //!< 1-bit register type
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Movk) == sizeof(u32));

        /**
         * @param destination The destination register of the operation
         * @param value The value to insert into the register
         * @return A array with the instructions to insert the value
         * @note 0 is returned for any instruction that isn't required
         */
        template<typename Type>
        constexpr std::array<u32, sizeof(Type) / sizeof(u16)> MoveRegister(registers::X destination, Type value) {
            std::array<u32, sizeof(Type) / sizeof(u16)> instructions;

            auto valuePointer{reinterpret_cast<u16 *>(&value)};
            bool zeroed{};
            u8 offset{};

            for (auto &instruction : instructions) {
                auto offsetValue{*(valuePointer + offset)};
                if (!zeroed) {
                    instruction = instructions::Movz(destination, offsetValue, offset).raw;
                    zeroed = true;
                } else if (offsetValue && zeroed) {
                    instruction = instructions::Movk(destination, offsetValue, offset).raw;
                } else {
                    instruction = 0;
                }
                offset++;
            }

            return instructions;
        }

        /**
         * @url https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/mov-register-move-register-an-alias-of-orr-shifted-register
         */
        struct Mov {
          public:
            /**
             * @brief Creates a MOV (Register) instruction
             * @param destReg The destination Xn register to store the value in
             * @param srcReg The source Xn register to retrieve the value from
             */
            constexpr Mov(registers::X destReg, registers::X srcReg) : destReg(static_cast<u8>(destReg)), sig0(0x1F), imm(0), srcReg(static_cast<u8>(srcReg)), sig1(0x150), sf(1) {}

            /**
             * @brief Creates a MOV instruction
             * @param destReg The destination Wn register to store the value in
             * @param srcReg The source Wn register to retrieve the value from
             */
            constexpr Mov(registers::W destReg, registers::W srcReg) : destReg(static_cast<u8>(destReg)), sig0(0x1F), imm(0), srcReg(static_cast<u8>(srcReg)), sig1(0x150), sf(0) {}

            constexpr bool Verify() {
                return (sig0 == 0x1F) && (sig1 == 0x150);
            }

            union {
                struct __attribute__((packed)) {
                    u8 destReg : 5;  //!< 5-bit destination register
                    u8 sig0 : 5;  //!< 5-bit signature (0x1F)
                    u8 imm : 6;  //!< 6-bit immediate value
                    u8 srcReg : 5;  //!< 5-bit source register
                    u16 sig1 : 10; //!< 10-bit signature (0x150)
                    u8 sf : 1;  //!< 1-bit register type
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Mov) == sizeof(u32));

        /**
         * @url https://developer.arm.com/docs/ddi0596/e/base-instructions-alphabetic-order/ldr-immediate-load-register-immediate
         */
        struct Ldr {
          public:
            /**
             * @param raw The raw value of the whole instruction
             */
            constexpr Ldr(u32 raw) : raw(raw) {}

            constexpr bool Verify() {
                return (sig0 == 0x0 && sig1 == 0x1CA && sig2 == 0x1);
            }

            union {
                struct __attribute__((packed)) {
                    u8 destReg : 5; //!< 5-bit destination register
                    u8 srcReg : 5; //!< 5-bit source register
                    u8 sig0 : 2; //!< 2-bit signature (0x0)
                    u16 imm : 9; //!< 6-bit immediate value
                    u16 sig1 : 9; //!< 9-bit signature (0x1CA)
                    u8 sf : 1; //!< 1-bit register type
                    u8 sig2 : 1; //!< 1-bit signature (0x1)
                };
                u32 raw{};
            };
        };
        static_assert(sizeof(Ldr) == sizeof(u32));
    }
}
