//
//  m3_core.h
//
//  Created by Steven Massey on 4/15/19.
//  Copyright © 2019 Steven Massey. All rights reserved.
//

#ifndef m3_core_h
#define m3_core_h

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

#include "wasm3.h"
#include "m3_config.h"

# if defined(__cplusplus)
#   define d_m3BeginExternC     extern "C" {
#   define d_m3EndExternC       }
# else
#   define d_m3BeginExternC
#   define d_m3EndExternC
# endif

d_m3BeginExternC

#define d_m3ImplementFloat (d_m3HasFloat || d_m3NoFloatDynamic)

#if !defined(d_m3ShortTypesDefined)

typedef uint64_t        u64;
typedef int64_t         i64;
typedef uint32_t        u32;
typedef int32_t         i32;
typedef uint16_t        u16;
typedef int16_t         i16;
typedef uint8_t         u8;
typedef int8_t          i8;

#if d_m3ImplementFloat
typedef double          f64;
typedef float           f32;
#endif

#endif // d_m3ShortTypesDefined

#define PRIf32          "f"
#define PRIf64          "lf"

typedef const void *            m3ret_t;
typedef const void *            voidptr_t;
typedef const char *            cstr_t;
typedef const char * const      ccstr_t;
typedef const u8 *              bytes_t;
typedef const u8 * const        cbytes_t;

typedef u16                     m3opcode_t;

typedef i64                     m3reg_t;

# if d_m3Use32BitSlots
typedef u32                     m3slot_t;
# else
typedef u64                     m3slot_t;
# endif

typedef m3slot_t *              m3stack_t;

typedef
const void * const  cvptr_t;

# if defined (DEBUG)

#   define d_m3Log(CATEGORY, FMT, ...)                  printf (" %8s  |  " FMT, #CATEGORY, ##__VA_ARGS__);

#   if d_m3LogParse
#       define m3log_parse(CATEGORY, FMT, ...)          d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_parse(...) {}
#   endif

#   if d_m3LogCompile
#       define m3log_compile(CATEGORY, FMT, ...)        d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_compile(...) {}
#   endif

#   if d_m3LogEmit
#       define m3log_emit(CATEGORY, FMT, ...)           d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_emit(...) {}
#   endif

#   if d_m3LogCodePages
#       define m3log_code(CATEGORY, FMT, ...)           d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_code(...) {}
#   endif

#   if d_m3LogModule
#       define m3log_module(CATEGORY, FMT, ...)         d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_module(...) {}
#   endif

#   if d_m3LogRuntime
#       define m3log_runtime(CATEGORY, FMT, ...)        d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
#   else
#       define m3log_runtime(...) {}
#   endif

#   define m3log(CATEGORY, FMT, ...)                    m3log_##CATEGORY (CATEGORY, FMT "\n", ##__VA_ARGS__)
# else
#   define d_m3Log(CATEGORY, FMT, ...)                  {}
#   define m3log(CATEGORY, FMT, ...)                    {}
# endif


# if defined(ASSERTS) || (defined(DEBUG) && !defined(NASSERTS))
#   define d_m3Assert(ASS)  if (!(ASS)) { printf("Assertion failed at %s:%d : %s\n", __FILE__, __LINE__, #ASS); abort(); }
# else
#   define d_m3Assert(ASS)
# endif

typedef void /*const*/ *                    code_t;
typedef code_t const * /*__restrict__*/     pc_t;


typedef struct M3MemoryHeader
{
    IM3Runtime      runtime;
    void *          maxStack;
    size_t          length;
}
M3MemoryHeader;

struct M3CodeMappingPage;

typedef struct M3CodePageHeader
{
    struct M3CodePage *           next;

    u32                           lineIndex;
    u32                           numLines;
    u32                           sequence;       // this is just used for debugging; could be removed
    u32                           usageCount;

# if d_m3RecordBacktraces
    struct M3CodeMappingPage *    mapping;
# endif // d_m3RecordBacktraces
}
M3CodePageHeader;


#define d_m3CodePageFreeLinesThreshold      4+2       // max is: select _sss & CallIndirect + 2 for bridge

#define d_m3MemPageSize                     65536

#define d_m3Reg0SlotAlias                   60000
#define d_m3Fp0SlotAlias                    (d_m3Reg0SlotAlias + 2)

#define d_m3MaxSaneTypesCount               100000
#define d_m3MaxSaneFunctionsCount           100000
#define d_m3MaxSaneImportsCount             10000
#define d_m3MaxSaneExportsCount             10000
#define d_m3MaxSaneGlobalsCount             100000
#define d_m3MaxSaneElementSegments          100000
#define d_m3MaxSaneDataSegments             100000
#define d_m3MaxSaneTableSize                100000
#define d_m3MaxSaneUtf8Length               10000
#define d_m3MaxSaneFunctionArgRetCount      1000    // still insane, but whatever

#define d_externalKind_function             0
#define d_externalKind_table                1
#define d_externalKind_memory               2
#define d_externalKind_global               3

static const char * const c_waTypes []          = { "nil", "i32", "i64", "f32", "f64", "unknown" };
static const char * const c_waCompactTypes []   = { "_", "i", "I", "f", "F", "?" };


# if d_m3VerboseErrorMessages

M3Result m3Error (M3Result i_result, IM3Runtime i_runtime, IM3Module i_module, IM3Function i_function,
                  const char * const i_file, u32 i_lineNum, const char * const i_errorMessage, ...);

#  define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) \
            m3Error (RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ##__VA_ARGS__)

# else
#  define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) (RESULT)
# endif

#define ErrorRuntime(RESULT, RUNTIME, FORMAT, ...)      _m3Error (RESULT, RUNTIME, NULL, NULL,  __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorModule(RESULT, MOD, FORMAT, ...)           _m3Error (RESULT, MOD->runtime, MOD, NULL,  __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorCompile(RESULT, COMP, FORMAT, ...)         _m3Error (RESULT, COMP->runtime, COMP->module, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)

#if d_m3LogNativeStack
void        m3StackCheckInit        ();
void        m3StackCheck            ();
int         m3StackGetMax           ();
#else
#define     m3StackCheckInit()
#define     m3StackCheck()
#define     m3StackGetMax()         0
#endif

#if d_m3LogTimestamps
#define     PRIts                   "%llu"
uint64_t    m3_GetTimestamp         ();
#else
#define     PRIts                   "%s"
#define     m3_GetTimestamp()       ""
#endif

void        m3_Abort                (const char* message);
void *      m3_Malloc_Impl          (size_t i_size);
void *      m3_Realloc_Impl         (void * i_ptr, size_t i_newSize, size_t i_oldSize);
void        m3_Free_Impl            (void * i_ptr);
void *      m3_CopyMem              (const void * i_from, size_t i_size);

#if d_m3LogHeapOps

// Tracing format: timestamp;heap:OpCode;name;size(bytes);new items;new ptr;old items;old ptr

static inline void * m3_AllocStruct_Impl(ccstr_t name, size_t i_size) {
    void * result = m3_Malloc_Impl(i_size);
    fprintf(stderr, PRIts ";heap:AllocStruct;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
    return result;
}

static inline void * m3_AllocArray_Impl(ccstr_t name, size_t i_num, size_t i_size) {
    void * result = m3_Malloc_Impl(i_size * i_num);
    fprintf(stderr, PRIts ";heap:AllocArr;%s;%zu;%zu;%p;;\n", m3_GetTimestamp(), name, i_size, i_num, result);
    return result;
}

static inline void * m3_ReallocArray_Impl(ccstr_t name, void * i_ptr_old, size_t i_num_new, size_t i_num_old, size_t i_size) {
    void * result = m3_Realloc_Impl (i_ptr_old, i_size * i_num_new, i_size * i_num_old);
    fprintf(stderr, PRIts ";heap:ReallocArr;%s;%zu;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_size, i_num_new, result, i_num_old, i_ptr_old);
    return result;
}

static inline void * m3_Malloc (ccstr_t name, size_t i_size) {
    void * result = m3_Malloc_Impl (i_size);
    fprintf(stderr, PRIts ";heap:AllocMem;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
    return result;
}
static inline void * m3_Realloc (ccstr_t name, void * i_ptr, size_t i_newSize, size_t i_oldSize) {
    void * result = m3_Realloc_Impl (i_ptr, i_newSize, i_oldSize);
    fprintf(stderr, PRIts ";heap:ReallocMem;%s;;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_newSize, result, i_oldSize, i_ptr);
    return result;
}

#define     m3_AllocStruct(STRUCT)                  (STRUCT *)m3_AllocStruct_Impl  (#STRUCT, sizeof (STRUCT))
#define     m3_AllocArray(STRUCT, NUM)              (STRUCT *)m3_AllocArray_Impl   (#STRUCT, NUM, sizeof (STRUCT))
#define     m3_ReallocArray(STRUCT, PTR, NEW, OLD)  (STRUCT *)m3_ReallocArray_Impl (#STRUCT, (void *)(PTR), (NEW), (OLD), sizeof (STRUCT))
#define     m3_Free(P)                              do { void* p = (void*)(P);                                  \
                                                        if (p) { fprintf(stderr, PRIts ";heap:FreeMem;;;;%p;\n", m3_GetTimestamp(), p); }     \
                                                        m3_Free_Impl (p); (P) = NULL; } while(0)
#else
#define     m3_Malloc(NAME, SIZE)                   m3_Malloc_Impl(SIZE)
#define     m3_Realloc(NAME, PTR, NEW, OLD)         m3_Realloc_Impl(PTR, NEW, OLD)
#define     m3_AllocStruct(STRUCT)                  (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT))
#define     m3_AllocArray(STRUCT, NUM)              (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT) * (NUM))
#define     m3_ReallocArray(STRUCT, PTR, NEW, OLD)  (STRUCT *)m3_Realloc_Impl ((void *)(PTR), sizeof (STRUCT) * (NEW), sizeof (STRUCT) * (OLD))
#define     m3_Free(P)                              do { m3_Free_Impl ((void*)(P)); (P) = NULL; } while(0)
#endif

M3Result    NormalizeType           (u8 * o_type, i8 i_convolutedWasmType);

bool        IsIntType               (u8 i_wasmType);
bool        IsFpType                (u8 i_wasmType);
bool        Is64BitType             (u8 i_m3Type);
u32         SizeOfType              (u8 i_m3Type);

M3Result    Read_u64                (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    Read_u32                (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#if d_m3ImplementFloat
M3Result    Read_f64                (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    Read_f32                (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#endif
M3Result    Read_u8                 (u8  * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    Read_opcode             (m3opcode_t * o_value, bytes_t  * io_bytes, cbytes_t i_end);

M3Result    ReadLebUnsigned         (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLebSigned           (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLEB_u32             (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLEB_u7              (u8  * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLEB_i7              (i8  * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLEB_i32             (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    ReadLEB_i64             (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result    Read_utf8               (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end);

cstr_t      SPrintValue             (void * i_value, u8 i_type);
size_t      SPrintArg               (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type);

void        ReportError             (IM3Runtime io_runtime, IM3Module i_module, IM3Function i_function, ccstr_t i_errorMessage, ccstr_t i_file, u32 i_lineNum);

# if d_m3RecordBacktraces
void        PushBacktraceFrame         (IM3Runtime io_runtime, pc_t i_pc);
void        FillBacktraceFunctionInfo  (IM3Runtime io_runtime, IM3Function i_function);
void        ClearBacktrace             (IM3Runtime io_runtime);
# endif

d_m3EndExternC

#endif // m3_core_h