#pragma once

#ifdef TSFUNCS_INCLUDE_MINHOOK
#include "MinHook.h"
#endif

typedef unsigned char BYTE;
typedef unsigned int ADDR;

#ifndef TSFUNCS_DEBUG
    #define TSFUNCS_DEBUG true
#endif

#define BlFunctionDef(returnType, convention, name, ...) \
    typedef returnType (convention *tsh_##name##FnT)(__VA_ARGS__); \
    tsh_##name##FnT name;

#define BlFunctionDefExtern(returnType, convention, name, ...) \
    typedef returnType (convention *tsh_##name##FnT)(__VA_ARGS__); \
    extern tsh_##name##FnT name;

#define BlFunctionDefIntern(name) \
    tsh_##name##FnT name;

#define BlScanFunctionCode(target, pattern, mask) \
    target = (tsh_##target##FnT)tsh_ScanFunctionCode((char *)pattern), (char *)mask); \
    if (!target) { \
        tsh_BlPrintf("TSHooks | Cannot find function "#target"!"); \
        return false; \
    } else { \
        if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Found function "#target" at %08x", (int)target); \
    }

#define BlScanFunctionHex(target, text) \
    target = (tsh_##target##FnT)tsh_ScanFunctionHex((char *)text); \
    if (!target) { \
        tsh_BlPrintf("TSHooks | Cannot find function "#target"!"); \
        return false; \
    } else { \
        if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Found function "#target" at %08x", (int)target); \
    }

#define BlScanCode(target, pattern, mask) \
    target = tsh_ScanFunctionCode((char *)pattern, (char *)mask); \
    if (!target) { \
        tsh_BlPrintf("TSHooks | Cannot find pattern "#target"!"); \
        return false; \
    } else { \
        if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Found "#target" at %08x", (int)target); \
    }

#define BlScanHex(target, text) \
    target = tsh_ScanFunctionHex((char *)text); \
    if (!target) { \
        tsh_BlPrintf("TSHooks | Cannot find "#target"!"); \
        return false; \
    } else { \
        if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Found "#target" at %08x", (int)target); \
    }

#define BlFunctionHookDef(func) \
    tsh_##func##FnT func##Original;

#define BlCreateHook(func) \
    { \
        MH_STATUS status = MH_CreateHook(reinterpret_cast<void *>(func), reinterpret_cast<void *>(&func##Hook), reinterpret_cast<void **>(&func##Original)); \
        if (status != MH_OK) { \
            tsh_BlPrintf("TSHooks | Failed to create hook for "#func": %s", MH_StatusToString(status)); \
            return false; \
        } else { \
            if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Created hook for "#func""); \
        } \
    }

#define BlTestEnableHook(func) \
    { \
        MH_STATUS status = MH_EnableHook(reinterpret_cast<void *>(func)); \
        if (status != MH_OK) { \
            tsh_BlPrintf("TSHooks | Failed to enable hook for "#func": %s", MH_StatusToString(status)); \
            return false; \
        } else { \
            if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Enabled hook for "#func""); \
        } \
    }

#define BlTestDisableHook(func) \
    { \
        MH_STATUS status = MH_DisableHook(reinterpret_cast<void *>(func)); \
        if (status != MH_OK) { \
            tsh_BlPrintf("TSHooks | Failed to disable hook for "#func": %s", MH_StatusToString(status)); \
            return false; \
        } else { \
            if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Disabled hook for "#func""); \
        } \
    }

#define BlEnableHook(func) \
    { \
        MH_STATUS status = MH_EnableHook(reinterpret_cast<void *>(func)); \
        if (status != MH_OK) { \
            tsh_BlPrintf("TSHooks | Failed to enable hook for "#func": %s", MH_StatusToString(status)); \
        } else { \
            if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Enabled hook for "#func""); \
        } \
    }

#define BlDisableHook(func) \
    { \
        MH_STATUS status = MH_DisableHook(reinterpret_cast<void *>(func)); \
        if (status != MH_OK) { \
            tsh_BlPrintf("TSHooks | Failed to disable hook for "#func": %s", MH_StatusToString(status)); \
        } else { \
            if (TSFUNCS_DEBUG) tsh_BlPrintf("TSHooks | Disabled hook for "#func""); \
        } \
    }

#define BlPatchAllMatches(len, patt, mask, repl) \
    tsh_PatchAllMatches(len, (char *)patt, (char *)mask, (char *)repl, TSFUNCS_DEBUG);

#define BlPatchByte(addr, byte) \
    tsh_PatchByte((ADDR)addr, (BYTE)byte);

#define BlPatchBytes(len, addr, repl) \
    tsh_PatchBytes(len, (ADDR)addr, (BYTE *)repl);

#define BlInit if (!tsh_InitInternal()) return false;

#define BlPrintf tsh_BlPrintf

bool tsh_InitInternal();
ADDR tsh_ScanFunctionCode(char *, char *);
ADDR tsh_ScanFunctionHex(char *);
int  tsh_PatchAllMatches(unsigned int, char *, char *, char *, bool);
void tsh_PatchByte(ADDR, BYTE);
void tsh_PatchBytes(unsigned int, ADDR, BYTE *);
void tsh_PatchInt(ADDR, int);

BlFunctionDefExtern(void, , tsh_BlPrintf, const char *, ...);
