#ifndef ___SYSTEM__H___
#define ___SYSTEM__H___

// This should probably be moved to platform.h at some point

#if defined(_M_X64) || defined(_M_AMD64) || defined(__amd64__)
#  define SYSTEM_AMD64
#elif defined(_M_IX86) || defined(__i386__) || defined(_X86_)
#  define SYSTEM_X86
#elif defined(_M_ARM64)
#  define SYSTEM_ARM64
#else
#  error "Unknown architecture!"
#endif

#ifdef _WIN64
#  define SYSFMT_HEXPTR _T("0x%016IX")
#else
#  define SYSFMT_HEXPTR _T("0x%08X")
#endif


// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the SYSTEM_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// SYSTEM_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.

#ifdef SYSTEM_EXPORTS
#  define SYSTEM_API __declspec(dllexport)
#else
#  define SYSTEM_API __declspec(dllimport) // BUGBUG: This is a plugin, who is going to import the functions directly?
#endif

#define SYS_ALIGNUP(num, al) ( ((num)+((al)-1)) & ~((al)-1) )
#define SYS_UNSAFEALIGNON(num, al) ( (num) % (al) == 0 ? (num) : SYS_ALIGNUP((num), (al)) ) // al CANNOT be 0!
#define SYS_ALIGNON(num, al) ( (al) ? SYS_UNSAFEALIGNON((num), (al)) : (num) )

#define NEW_STACK_SIZE     256*256

// Proc types:
#define PT_NOTHING      0
#define PT_PROC         1
#define PT_STRUCT       2
#define PT_VTABLEPROC   3

// Proc results:
#define PR_OK           0
#define PR_ERROR        -1
#define PR_CALLBACK     1

// Real world argument types
#define PAT_VOID        0
#define PAT_INT         1
#define PAT_LONG        2
#define PAT_STRING      3
#define PAT_WSTRING     4
#define PAT_GUID        5
#define PAT_CALLBACK    6
#define PAT_REGMEM      7
#ifdef _UNICODE
#define PAT_TSTRING     PAT_WSTRING
#else
#define PAT_TSTRING     PAT_STRING
#endif
#define PAT_PTR ( (4==sizeof(void*)) ? PAT_INT : PAT_LONG )
#define PAT_ALIGNFLAG 0x8000 // Type is aligned to its natural alignment

// Input/Output Source/Destination
#define IOT_NONE    0
#define IOT_STACK   -1
#define IOT_REG     1
#define IOT_INLINE  (__INST_LAST+1) // should replace pointer to inline input
// #define INLINE_INPUT -> any other value, will contain pointer to input string

// Options
#define POPT_CDECL      0x1    // (Option & 0x1) == 0x1 -> cdecl, otherwise stdcall
#define POPT_PERMANENT  0x2    // Permanent proc, will not be destroyed after calling
#define POPT_ALWRETURN  0x4    // Always return
#define POPT_NEVERREDEF 0x8    // Never redefine
#define POPT_GENSTACK   0x10   // Use general stack (non temporary for callback)
#define POPT_ERROR      0x20   // Call GetLastError after proc and push it to stack
#define POPT_UNLOAD     0x40   // unload dll after call
#define POPT_CLONE      0x80   // Callback clone
#define POPT_SYNTAX2    0x100  // "?2" syntax mode (direct callback ids and aligned uppercased types)

// Proc argument (ProcParameter) options
#define PAO_PTRFLAG -1 // Could be changed to 0x80000000 if we need to support "*&iN"
#define PAO_ARRBASE 1
#define ParamOptionIsPointer(opt) ( (opt) < 0 )

typedef struct
{
    int Type; // Can be ORed with PAT_ALIGNFLAG to request alignment in structs
    int Option; // PAO_PTRFLAG -> Pointer, PAO_ARRBASE-... -> Special+PAO_ARRBASE
    INT_PTR Value; // it can hold any pointer sized value
#ifndef _WIN64
    int _value; // Upper 32 bits of Value when type is 64 bit (2 pushes)
#endif
    int Size; // Value real size (should be either 1 or 2 (the number of pushes))
    int Output;
    INT_PTR Input;
    HGLOBAL allocatedBlock; // block allocated for passing string, wstring or guid param
} ProcParameter;

#define ParamIsSimple(par) ( (par).Option == 0 )
#define ParamIsPointer(par) ParamOptionIsPointer((par).Option)
#define ParamIsArray(par) ( (par).Option > 0 ) // AKA special
#define GetParamArrayTypeSize GetSpecialParamTypeSize
#ifdef POPT_SYNTAX2
#define GetParamType(par) ( (BYTE) (par).Type ) 
#else
#define GetParamType(par) ( (par).Type )
#endif

// Our single proc (Since the user will free proc with GlobalFree, 
// I've declared all variables as statics)
typedef struct tag_SystemProc SystemProc;
struct tag_SystemProc
{
    int ProcType;
    int ProcResult;
    TCHAR DllName[1024];
    TCHAR ProcName[1024];
    HMODULE Dll;
    HANDLE  Proc;
    int Options;
    int ParamCount;
    ProcParameter Params[100];  // I hope nobody will use more than 100 params

    // Callback specific
    int CallbackIndex;
    int ArgsSize;
    // Clone of current element (used for multi-level callbacks)
    SystemProc *Clone;
};

typedef struct tag_CallbackThunk CallbackThunk;
struct tag_CallbackThunk
{
    #ifdef SYSTEM_X86
        /*
        #pragma pack(push,1)
        char mov_eax_imm;
        int sysprocptr;
        char reljmp_imm;
        int realprocaddr;
        #pragma pack(pop)
        */
        char asm_code[10];
    #elif defined(SYSTEM_AMD64) || defined(SYSTEM_ARM64)
        char asm_code[BUGBUG64(1)]; // TODO: BUGBUG64
    #else
        #error "Asm thunk not implemeted for this architecture!"
    #endif

    CallbackThunk* pNext;
};

// Free() only knows about pNext in CallbackThunk, it does not know anything about the assembly, that is where this helper comes in...
#ifdef SYSTEM_X86
#   define GetAssociatedSysProcFromCallbackThunkPtr(pCbT) ( (SystemProc*)  *(unsigned int*) (((char*)(pCbT))+1) )
#elif defined(SYSTEM_AMD64) || defined(SYSTEM_ARM64)
#   define GetAssociatedSysProcFromCallbackThunkPtr(pCbT) BUGBUG64(NULL)
#else
#   error "GetAssociatedSysProcFromCallbackThunkPtr not defined for the current architecture!"
#endif


extern const int ParamSizeByType[];   // Size of every parameter type (*4 bytes)

extern HANDLE CreateCallback(SystemProc *cbproc);
extern SystemProc* PrepareProc(BOOL NeedForCall);
extern void ParamAllocate(SystemProc *proc);
extern void ParamsDeAllocate(SystemProc *proc);
extern void ParamsIn(SystemProc *proc);
extern void ParamsOut(SystemProc *proc);
#ifdef SYSTEM_AMD64
extern SystemProc* CallProc2(SystemProc *proc, UINT_PTR ParamCount);
#define CallProc(p) CallProc2((p), (p)->ParamCount) // ParamCount is passed as a parameter so CallProc2 can determine the required stack size without a function call
#else //! SYSTEM_AMD64
extern SystemProc* CallProc(SystemProc *proc);
#endif //~ SYSTEM_AMD64
#ifndef SYSTEM_NOCALLBACKS
extern SystemProc* CallBack(SystemProc *proc);
extern SystemProc* RealCallBack();
#endif
extern void CallStruct(SystemProc *proc);

#ifdef _UNICODE
#   define STRSET2CH(str, c1, c2) ( *(DWORD*)(str) = ((c1)|(c2)<<16) )
#else
#   define STRSET2CH(str, c1, c2) ( *(WORD*)(str) = ((c1)|(c2)<<8) )
#endif

#endif
