/*
 * Copyright (c) 2024 Ross Cunniff
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

// OADL opcodes

enum {
    // Miscellaneous group
    OP_NOP = 0x00,
    OP_POP,                     // val pop
    OP_DUP,                     // val dup => val val
    OP_EXCH,                    // v1 v2 exch => v2 v1
    OP_UNUSED_0x04,             // unused opcode #0x04
    OP_INC,                     // v0 => v0 + 1 (without promotion)
    OP_DEC,                     // v0 => v0 - 1 (without promotion)
    // Unused: 0x07

    // Arithmetic
    OP_ADD = 0x08,              // op1 op2 add => (op1 + op2)
    OP_SUB,                     // op1 op2 sub => (op1 - op2)
    OP_MUL,                     // op1 op2 mul => (op1 * op2)
    OP_DIV,                     // op1 op2 div => (op1 / op2)
    OP_MOD,                     // op1 op2 mod => (op1 % op2)
    OP_NEG,                     // op1 neg => (-op1)
    OP_POW,                     // op1 op2 pow => (op1 ** op2)
    // Unused: 0x0F

    // Boolean/bit
    OP_AND = 0x10,              // op1 op2 and => (op1 & op2)
    OP_OR,                      // op1 op2 or => (op1 | op2)
    OP_XOR,                     // op1 op2 xor => (op1 ^ op2)
    OP_NOT,                     // op1 not => (~op1)
    OP_LOGNOT,                  // op1 lognot => (~op1)
    OP_LSHIFT,                  // op1 op2 lshift => (op1 << op2)
    OP_RSHIFT,                  // op1 op2 rshift => (op1 >> op2)
    // Unused: 0x17

    // Comparison
    OP_LT = 0x18,               // op1 op2 lt => (op1 < op2)
    OP_GT,                      // op1 op2 gt => (op1 > op2)
    OP_LE,                      // op1 op2 le => (op1 <= op2)
    OP_GE,                      // op1 op2 ge => (op1 >= op2)
    // Unused: 0x1C - 0x1F

    // Equivalence
    OP_EQ = 0x20,               // op1 op2 eq => (op1 == op2)
    OP_NE,                      // op1 op2 ne => (op1 != op2)
    OP_ARR_EQ,                  // op1 op2 arreq => (op1 #= op2)
    // Unused: 0x21 - 0x27

    // String matching
    OP_MATCH = 0x28,            // str patt comp MATCH => new match env
    OP_ENDMATCH,                // ENDMATCH => pop match env
    OP_NMATCH,                  // NMATCH => # of matches
    OP_MATCHN,                  // N MATCHN => (match #N)
    // Unused: 0x2C - 0x2F

    // Execution flow
    OP_CALL = 0x30,             // arg1 arg2 ... argN N addr call
    OP_RET,                     // retval ret
    OP_MCALL,                   // arg1 arg2 ... argN N obj public mcall
    OP_JMP,                     // addr jmp
    OP_JMPZ,                    // val addr jmpz
    OP_LOCALS,                  // Allocate locals on the stack
    OP_TRY,                     // addr try
    OP_ENDTRY,                  // endtry
    OP_THROW,                   // excepnum throw
    OP_PROC,                    // PROC => current executing proc
    OP_ARR_CALL,                // arr addr arrcall
    OP_ARR_MCALL,               // arr obj public arrmcall
    // Unused: 0x3C - 0x3F

    // Get/set global, local, arg, index
    OP_GETG = 0x40,             // global getg => (*global)
    OP_SETG,                    // global val setg
    OP_SETG_TC,                 // type global val setg_tc
    OP_GETL,                    // local getl => (*local)
    OP_SETL,                    // local val setl
    OP_SETL_TC,                 // type local val setl_tc
    OP_GETA,                    // arg geta => (*arg)
    OP_SETA,                    // arg val seta
    OP_SETA_TC,                 // type arg val seta_tc
    OP_GETP,                    // prop getp => (self.prop)
    OP_SETP,                    // prop val setp
    OP_SETP_TC,                 // type prop val setp_tc
    OP_GETPK,                   // prop getpk => prop (self.prop)
    OP_GETI,                    // arr index geti => (arr[index])
    OP_GETI_M,                  // arr i0 i1 ... n geti_m => arr[i0,i1,...]
    OP_SETI,                    // arr index val seti
    OP_SETI_M,                  // arr i0 i1 ... n val seti_m
    OP_GETIK,                   // arr index getik => arr index arr[index]
    OP_GETIK_M,                 // arr i0 i1 ... n getik_m => arr i0 ... a[i]
    OP_GETPUB,                  // obj pub getpub => obj.pub
    OP_SETPUB,                  // obj pub val setpub
    OP_GETPUBK,                 // obj pub getpubk => obj pub obj.pub
    OP_SELF,                    // self => self
    OP_LOOPINIT,                // vbase expr num ishash loopinit => stop?
    OP_LOOPINCR,                // vbase num ishash loopincr => stop?
    OP_GETI_X,                  // arr index geti_x => arr#[index]
    OP_GETI_XK,                 // arr index geti_xk => arr index arr#[index]
    OP_SETI_X,                  // arr index val seti_x
    OP_SETSUBR,                 // arr s0 e0 s1 e1 ... n val setsubr
    OP_GETSUB_K,                // arr s0 e0 ... n getsub_k => arr s0 .. arr[..]
    OP_ARGCHECK,                // arg type argcheck
    // Unused: 0x5F - 0x5F

    // debugging ops
    OP_BREAKPOINT = 0x60,       // Break and exec
    OP_DEBUG_POP,               // Pop debug name stack
    OP_LINE,                    // linenum line
    OP_DEBUG_PRINT,             // var DEBUG_PRINT
    OP_BREAK_HALT,              // Break and halt
    // Unused: 0x65 - 0x6F

    // Push instructions - 0 extra bytes
    OP_NIL = 0x70,              // Push nil - 0xFFFFFFFF
    OP_FLT_0,                   // Push 0.0
    OP_INT_0,                   // Push 0
    OP_DBL_0,                   // Push 0.d (only OADL64)
    OP_I64_0,                   // Push 0L (only OADL64)
    OP_WCH_0,                   // Push '\x0'L
    OP_F16_0,                   // Push 0.0h
    OP_U16_0,                   // Push 0us
    OP_I16_0,                   // Push 0s
    OP_U8_0,                    // Push 0ub
    OP_I8_0,                    // Push 0b
    OP_CHR_0,                   // Push '\x0'
    OP_FALSE,                   // Push false
    OP_TRUE,                    // Push true
    OP_ARG_0,                   // Push address of arg 0
    // Unused: 0x7F

    // Unused: 0x80 - 0x8F

    // Push instructions - 1 extra byte
    OP_INT_8 = 0x90,            // Push -128 to 127
    OP_I64_8,                   // Push -128l to 127l (OADL64)
    OP_WCH_8,                   // Push '\x0'L to '\xFF'L
    OP_U16_8,                   // Push 0us to 255us
    OP_I16_8,                   // Push -128s to 127s
    OP_U8_8,                    // Push 0ub to 255ub
    OP_I8_8,                    // Push -128b to 127b
    OP_CHR_8,                   // Push Char
    OP_TYP_8,                   // Push 8-bit Type
    OP_EXCP_8,                  // Push 8-bit Exception
    OP_LOCAL_8,                 // next byte is local var offs
    OP_ARG_8,                   // next byte is arg offs
    OP_BUILTIN,                 // next byte is builtin number from builtin.h
    OP_FILE_8,                  // Push 8-bit File
    // Unused: 0x9E - 0x9F

    // Multibyte instructions - 2 extra bytes
    OP_INT_16 = 0xA0,           // Push -32768 to 32767
    OP_FLT_16,                  // next 2 bytes are upper bits of float
    OP_I64_16,                  // Push -32768l to 32767l (OADL64)
    OP_DBL_16,                  // next 2 bytes are upper bits of dbl (OADL64)
    OP_WCH_16,                  // Push '\x0800'L to '\xFFFF'L
    OP_F16_16,                  // Push Half
    OP_U16_16,                  // Push 0us to 65535us
    OP_I16_16,                  // Push -32768s to 32767s
    OP_LOCAL_16,                // next 2 bytes are local var offs
    OP_ARG_16,                  // next 2 bytes are arg offs
    OP_DEBUG_PUSH,              // next 2 bytes are unique ID
    // Unused: 0xAB - 0xAF

    // Multibyte instructions - various extra bytes
    OP_FLT_24 = 0xB0,           // next 3 bytes are upper 3 of float
    OP_DBL_24,                  // next 3 bytes are upper 3 of dbl (OADL64)
    OP_WCH_24,                  // Push '\x080000'L to '\xFFFFFF'L
    OP_ADDRESS,                 // next 3 bytes are code address offset
    // Unused: 0xB4 - 0xB7      // next 3 bytes...

    OP_PARENT = 0xB8,           // next 4 bytes are parent object
    OP_DBL_32,                  // next 4 bytes are upper 4 of Double (OADL64)
    OP_I64_32,                  // next 4 bytes are lower 4 of Long (OADL64)
    OP_DEBUG_VAR,               // next 4 bytes are name for debugging
    OP_FILE,                    // next 4 bytes are array ID of filename
    OP_DEBUG_USE_NS,            // next 4 bytes are namespace name ID
    // Unused: 0xBE             // next 4 bytes...

    OP_VAR_64 = 0xBF,           // next 8 bytes are 64-bit Var (OADL64 only)

    // Multibyte instructions - 4 extra bytes
    OP_PUSH_32 = 0xC0,          // Lower 6 bits of opcode are type.
                                // next 4 bytes are value.
};

typedef uint8_t Opcode;

namespace oadl {
    extern const uint8_t opcodeSizes[256];
    extern const uint8_t op0types[16];
    extern const uint8_t op8types[16];
    extern const uint8_t op16types[16];

#if defined(OADL_DEFINE_MACH_TABLE) // [

    const uint8_t opcodeSizes[256] = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x00
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x10
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x20
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x30
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x40
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x50
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x60
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x70
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0x80
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // 0x90
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,  // 0xA0
        4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 9,  // 0xB0
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  // 0xC0
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  // 0xD0
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  // 0xE0
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,  // 0xF0
    };
    const uint8_t op0types[16] = {
        OADL_VT_NULL,   OADL_VT_FLOAT,  OADL_VT_INT,    OADL_VT_DOUBLE,
        OADL_VT_LONG,   OADL_VT_WCHAR,  OADL_VT_HALF,   OADL_VT_USHORT,
        OADL_VT_SHORT,  OADL_VT_UBYTE,  OADL_VT_BYTE,   OADL_VT_CHAR,
        OADL_VT_BOOL,   OADL_VT_BOOL,   OADL_VT_ARG,    OADL_VT_NULL,
    };
    const uint8_t op8types[16] = {
        OADL_VT_INT,    OADL_VT_LONG,   OADL_VT_WCHAR,  OADL_VT_USHORT,
        OADL_VT_SHORT,  OADL_VT_UBYTE,  OADL_VT_BYTE,   OADL_VT_CHAR,
        OADL_VT_TYPE,   OADL_VT_EXCEPT, OADL_VT_LOCAL,  OADL_VT_ARG,
        OADL_VT_INTR,   OADL_VT_FILE,   OADL_VT_NULL,   OADL_VT_NULL,
    };
    const uint8_t op16types[16] = {
        OADL_VT_INT,    OADL_VT_FLOAT,  OADL_VT_LONG,   OADL_VT_DOUBLE,
        OADL_VT_WCHAR,  OADL_VT_HALF,   OADL_VT_USHORT, OADL_VT_SHORT,
        OADL_VT_LOCAL,  OADL_VT_ARG,    OADL_VT_NULL,   OADL_VT_NULL,
        OADL_VT_NULL,   OADL_VT_NULL,   OADL_VT_NULL,   OADL_VT_NULL,
    };

#endif // ]

   // Instruction decode methods
    static inline int instrCount(Opcode op)
    {
        return oadl::opcodeSizes[op];
    }

    static inline int encode(Opcode *ip, Opcode op, uint32_t a0, uint32_t a1)
    {
        int size = instrCount(op);

        ip[0] = op;

        // Note tricky fallthroughs
        switch (size) {
    #if defined(OADL64)
        case 9 :
            ip[8] = (Opcode) ((a1 >> 24) & 0xFF);
            ip[7] = (Opcode) ((a1 >> 16) & 0xFF);
            ip[6] = (Opcode) ((a1 >>  8) & 0xFF);
            ip[5] = (Opcode) ((a1      ) & 0xFF);
            // FALLTHROUGH
    #endif
        case 5 :
            ip[4] = (Opcode) ((a0 >> 24) & 0xFF);
            // FALLTHROUGH
        case 4 :
            ip[3] = (Opcode) ((a0 >> 16) & 0xFF);
            // FALLTHROUGH
        case 3 :
            ip[2] = (Opcode) ((a0 >>  8) & 0xFF);
            // FALLTHROUGH
        case 2 :
            ip[1] = (Opcode) ((a0      ) & 0xFF);
            break;
        }

        return size;
    }

    static inline int decode(Opcode *ip, Opcode *op,
                                        uint32_t *a0, uint32_t *a1)
    {
        Opcode b0 = ip[0];
        *op = b0;
        uint32_t va0 = 0, va1 = 0;
        int size = instrCount(b0);

        // Note tricky fallthroughs again
        switch (size) {
    #if defined(OADL64)
        case 9 :
            va1 |= ip[8] << 24;
            va1 |= ip[7] << 16;
            va1 |= ip[6] << 8;
            va1 |= ip[5];
            // FALLTHROUGH
    #endif
        case 5 :
            // FALLTHROUGH
            va0 |= ip[4] << 24;
        case 4 :
            // FALLTHROUGH
            va0 |= ip[3] << 16;
        case 3 :
            // FALLTHROUGH
            va0 |= ip[2] << 8;
        case 2 :
            // FALLTHROUGH
            va0 |= ip[1];
            break;
        }

        *a0 = va0; *a1 = va1;

        return size;
    }
}