#ifndef _PH_PHSUP_H
#define _PH_PHSUP_H

// This header file provides some useful definitions specific to phlib.

#include <intrin.h>
#include <wchar.h>
#include <assert.h>
#include <stdio.h>

// Memory

#define PTR_ADD_OFFSET(Pointer, Offset) ((PVOID)((ULONG_PTR)(Pointer) + (ULONG_PTR)(Offset)))
#define PTR_SUB_OFFSET(Pointer, Offset) ((PVOID)((ULONG_PTR)(Pointer) - (ULONG_PTR)(Offset)))
#define ALIGN_UP_BY(Address, Align) (((ULONG_PTR)(Address) + (Align) - 1) & ~((Align) - 1))
#define ALIGN_UP_POINTER_BY(Pointer, Align) ((PVOID)ALIGN_UP_BY(Pointer, Align))
#define ALIGN_UP(Address, Type) ALIGN_UP_BY(Address, sizeof(Type))
#define ALIGN_UP_POINTER(Pointer, Type) ((PVOID)ALIGN_UP(Pointer, Type))

#define PAGE_SIZE 0x1000

#define PH_LARGE_BUFFER_SIZE (256 * 1024 * 1024)

// Exceptions

#define PhRaiseStatus(Status) RtlRaiseStatus(Status)

#define SIMPLE_EXCEPTION_FILTER(Condition) \
    ((Condition) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)

// Compiler

#ifdef DEBUG
#define ASSUME_ASSERT(Expression) assert(Expression)
#define ASSUME_NO_DEFAULT assert(FALSE)
#else
#define ASSUME_ASSERT(Expression) __assume(Expression)
#define ASSUME_NO_DEFAULT __assume(FALSE)
#endif

// Time

#define PH_TICKS_PER_NS ((LONG64)1 * 10)
#define PH_TICKS_PER_MS (PH_TICKS_PER_NS * 1000)
#define PH_TICKS_PER_SEC (PH_TICKS_PER_MS * 1000)
#define PH_TICKS_PER_MIN (PH_TICKS_PER_SEC * 60)
#define PH_TICKS_PER_HOUR (PH_TICKS_PER_MIN * 60)
#define PH_TICKS_PER_DAY (PH_TICKS_PER_HOUR * 24)

#define PH_TICKS_PARTIAL_MS(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_MS) % 1000)
#define PH_TICKS_PARTIAL_SEC(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_SEC) % 60)
#define PH_TICKS_PARTIAL_MIN(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_MIN) % 60)
#define PH_TICKS_PARTIAL_HOURS(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_HOUR) % 24)
#define PH_TICKS_PARTIAL_DAYS(Ticks) ((ULONG64)(Ticks) / PH_TICKS_PER_DAY)

#define PH_TIMEOUT_MS PH_TICKS_PER_MS
#define PH_TIMEOUT_SEC PH_TICKS_PER_SEC

// Annotations

/**
 * Indicates that a function assumes the specified number of references are available for the
 * object.
 *
 * \remarks Usually functions reference objects if they store them for later usage; this annotation
 * specifies that the caller must supply these extra references itself. In effect these references
 * are "transferred" to the function and must not be used. E.g. if you create an object and
 * immediately call a function with _Assume_refs_(1), you may no longer use the object since that
 * one reference you held is no longer yours.
 */
#define _Assume_refs_(count)

#define _Callback_

/**
 * Indicates that a function may raise a software exception.
 *
 * \remarks Do not use this annotation for temporary usages of exceptions, e.g. unimplemented
 * functions.
 */
#define _May_raise_

/**
 * Indicates that a function requires the specified value to be aligned at the specified number of
 * bytes.
 */
#define _Needs_align_(align)

// Casts

// Zero extension and sign extension macros
#define C_1uTo2(x) ((unsigned short)(unsigned char)(x))
#define C_1sTo2(x) ((unsigned short)(signed char)(x))
#define C_1uTo4(x) ((unsigned int)(unsigned char)(x))
#define C_1sTo4(x) ((unsigned int)(signed char)(x))
#define C_2uTo4(x) ((unsigned int)(unsigned short)(x))
#define C_2sTo4(x) ((unsigned int)(signed short)(x))
#define C_4uTo8(x) ((unsigned __int64)(unsigned int)(x))
#define C_4sTo8(x) ((unsigned __int64)(signed int)(x))

// Sorting

typedef enum _PH_SORT_ORDER
{
    NoSortOrder = 0,
    AscendingSortOrder,
    DescendingSortOrder
} PH_SORT_ORDER, *PPH_SORT_ORDER;

FORCEINLINE LONG PhModifySort(
    _In_ LONG Result,
    _In_ PH_SORT_ORDER Order
    )
{
    if (Order == AscendingSortOrder)
        return Result;
    else if (Order == DescendingSortOrder)
        return -Result;
    else
        return Result;
}

#define PH_BUILTIN_COMPARE(value1, value2) \
    if (value1 > value2) \
        return 1; \
    else if (value1 < value2) \
        return -1; \
    \
    return 0

FORCEINLINE int charcmp(
    _In_ signed char value1,
    _In_ signed char value2
    )
{
    return C_1sTo4(value1 - value2);
}

FORCEINLINE int ucharcmp(
    _In_ unsigned char value1,
    _In_ unsigned char value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int shortcmp(
    _In_ signed short value1,
    _In_ signed short value2
    )
{
    return C_2sTo4(value1 - value2);
}

FORCEINLINE int ushortcmp(
    _In_ unsigned short value1,
    _In_ unsigned short value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int intcmp(
    _In_ int value1,
    _In_ int value2
    )
{
    return value1 - value2;
}

FORCEINLINE int uintcmp(
    _In_ unsigned int value1,
    _In_ unsigned int value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int int64cmp(
    _In_ __int64 value1,
    _In_ __int64 value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int uint64cmp(
    _In_ unsigned __int64 value1,
    _In_ unsigned __int64 value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int intptrcmp(
    _In_ LONG_PTR value1,
    _In_ LONG_PTR value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int uintptrcmp(
    _In_ ULONG_PTR value1,
    _In_ ULONG_PTR value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int singlecmp(
    _In_ float value1,
    _In_ float value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int doublecmp(
    _In_ double value1,
    _In_ double value2
    )
{
    PH_BUILTIN_COMPARE(value1, value2);
}

FORCEINLINE int wcsicmp2(
    _In_opt_ PWSTR Value1,
    _In_opt_ PWSTR Value2
    )
{
    if (Value1 && Value2)
        return _wcsicmp(Value1, Value2);
    else if (!Value1)
        return !Value2 ? 0 : -1;
    else
        return 1;
}

typedef int (__cdecl *PC_COMPARE_FUNCTION)(void *, const void *, const void *);

// Synchronization

#ifndef _WIN64

#ifndef _InterlockedCompareExchangePointer
void *_InterlockedCompareExchangePointer(
    void *volatile *Destination,
    void *Exchange,
    void *Comparand
    );
#endif

#if (_MSC_VER < 1900)
#ifndef _InterlockedExchangePointer
FORCEINLINE void *_InterlockedExchangePointer(
    void *volatile *Destination,
    void *Exchange
    )
{
    return (PVOID)_InterlockedExchange(
        (PLONG_PTR)Destination,
        (LONG_PTR)Exchange
        );
}
#endif
#endif

#endif

FORCEINLINE LONG_PTR _InterlockedExchangeAddPointer(
    _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend,
    _In_ LONG_PTR Value
    )
{
#ifdef _WIN64
    return (LONG_PTR)_InterlockedExchangeAdd64((PLONG64)Addend, (LONG64)Value);
#else
    return (LONG_PTR)_InterlockedExchangeAdd((PLONG)Addend, (LONG)Value);
#endif
}

FORCEINLINE LONG_PTR _InterlockedIncrementPointer(
    _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend
    )
{
#ifdef _WIN64
    return (LONG_PTR)_InterlockedIncrement64((PLONG64)Addend);
#else
    return (LONG_PTR)_InterlockedIncrement((PLONG)Addend);
#endif
}

FORCEINLINE LONG_PTR _InterlockedDecrementPointer(
    _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend
    )
{
#ifdef _WIN64
    return (LONG_PTR)_InterlockedDecrement64((PLONG64)Addend);
#else
    return (LONG_PTR)_InterlockedDecrement((PLONG)Addend);
#endif
}

FORCEINLINE BOOLEAN _InterlockedBitTestAndResetPointer(
    _Inout_ _Interlocked_operand_ LONG_PTR volatile *Base,
    _In_ LONG_PTR Bit
    )
{
#ifdef _WIN64
    return _interlockedbittestandreset64((PLONG64)Base, (LONG64)Bit);
#else
    return _interlockedbittestandreset((PLONG)Base, (LONG)Bit);
#endif
}

FORCEINLINE BOOLEAN _InterlockedBitTestAndSetPointer(
    _Inout_ _Interlocked_operand_ LONG_PTR volatile *Base,
    _In_ LONG_PTR Bit
    )
{
#ifdef _WIN64
    return _interlockedbittestandset64((PLONG64)Base, (LONG64)Bit);
#else
    return _interlockedbittestandset((PLONG)Base, (LONG)Bit);
#endif
}

FORCEINLINE BOOLEAN _InterlockedIncrementNoZero(
    _Inout_ _Interlocked_operand_ LONG volatile *Addend
    )
{
    LONG value;
    LONG newValue;

    value = *Addend;

    while (TRUE)
    {
        if (value == 0)
            return FALSE;

        if ((newValue = _InterlockedCompareExchange(
            Addend,
            value + 1,
            value
            )) == value)
        {
            return TRUE;
        }

        value = newValue;
    }
}

FORCEINLINE BOOLEAN _InterlockedIncrementPositive(
    _Inout_ _Interlocked_operand_ LONG volatile *Addend
    )
{
    LONG value;
    LONG newValue;

    value = *Addend;

    while (TRUE)
    {
        if (value <= 0)
            return FALSE;

        if ((newValue = _InterlockedCompareExchange(
            Addend,
            value + 1,
            value
            )) == value)
        {
            return TRUE;
        }

        value = newValue;
    }
}

// Strings

#define PH_INT32_STR_LEN 12
#define PH_INT32_STR_LEN_1 (PH_INT32_STR_LEN + 1)

#define PH_INT64_STR_LEN 50
#define PH_INT64_STR_LEN_1 (PH_INT64_STR_LEN + 1)

#define PH_PTR_STR_LEN 24
#define PH_PTR_STR_LEN_1 (PH_PTR_STR_LEN + 1)

FORCEINLINE VOID PhPrintInt32(
    _Out_writes_(PH_INT32_STR_LEN_1) PWSTR Destination,
    _In_ LONG Int32
    )
{
    _ltow(Int32, Destination, 10);
}

FORCEINLINE VOID PhPrintUInt32(
    _Out_writes_(PH_INT32_STR_LEN_1) PWSTR Destination,
    _In_ ULONG UInt32
    )
{
    _ultow(UInt32, Destination, 10);
}

FORCEINLINE VOID PhPrintInt64(
    _Out_writes_(PH_INT64_STR_LEN_1) PWSTR Destination,
    _In_ LONG64 Int64
    )
{
    _i64tow(Int64, Destination, 10);
}

FORCEINLINE VOID PhPrintUInt64(
    _Out_writes_(PH_INT64_STR_LEN_1) PWSTR Destination,
    _In_ ULONG64 UInt64
    )
{
    _ui64tow(UInt64, Destination, 10);
}

FORCEINLINE VOID PhPrintPointer(
    _Out_writes_(PH_PTR_STR_LEN_1) PWSTR Destination,
    _In_ PVOID Pointer
    )
{
    Destination[0] = '0';
    Destination[1] = 'x';
#ifdef _WIN64
    _ui64tow((ULONG64)Pointer, &Destination[2], 16);
#else
    _ultow((ULONG)Pointer, &Destination[2], 16);
#endif
}

// Misc.

FORCEINLINE ULONG PhCountBits(
    _In_ ULONG Value
    )
{
    ULONG count = 0;

    while (Value)
    {
        count++;
        Value &= Value - 1;
    }

    return count;
}

FORCEINLINE ULONG PhRoundNumber(
    _In_ ULONG Value,
    _In_ ULONG Granularity
    )
{
    return (Value + Granularity / 2) / Granularity * Granularity;
}

FORCEINLINE ULONG PhMultiplyDivide(
    _In_ ULONG Number,
    _In_ ULONG Numerator,
    _In_ ULONG Denominator
    )
{
    return (ULONG)(((ULONG64)Number * (ULONG64)Numerator + Denominator / 2) / (ULONG64)Denominator);
}

FORCEINLINE LONG PhMultiplyDivideSigned(
    _In_ LONG Number,
    _In_ ULONG Numerator,
    _In_ ULONG Denominator
    )
{
    if (Number >= 0)
        return PhMultiplyDivide(Number, Numerator, Denominator);
    else
        return -(LONG)PhMultiplyDivide(-Number, Numerator, Denominator);
}

FORCEINLINE VOID PhProbeAddress(
    _In_ PVOID UserAddress,
    _In_ SIZE_T UserLength,
    _In_ PVOID BufferAddress,
    _In_ SIZE_T BufferLength,
    _In_ ULONG Alignment
    )
{
    if (UserLength != 0)
    {
        if (((ULONG_PTR)UserAddress & (Alignment - 1)) != 0)
            PhRaiseStatus(STATUS_DATATYPE_MISALIGNMENT);

        if (
            ((ULONG_PTR)UserAddress + UserLength < (ULONG_PTR)UserAddress) ||
            ((ULONG_PTR)UserAddress < (ULONG_PTR)BufferAddress) ||
            ((ULONG_PTR)UserAddress + UserLength > (ULONG_PTR)BufferAddress + BufferLength)
            )
            PhRaiseStatus(STATUS_ACCESS_VIOLATION);
    }
}

FORCEINLINE PLARGE_INTEGER PhTimeoutFromMilliseconds(
    _Out_ PLARGE_INTEGER Timeout,
    _In_ ULONG Milliseconds
    )
{
    if (Milliseconds == INFINITE)
        return NULL;

    Timeout->QuadPart = -(LONGLONG)UInt32x32To64(Milliseconds, PH_TIMEOUT_MS);

    return Timeout;
}

FORCEINLINE NTSTATUS PhGetLastWin32ErrorAsNtStatus()
{
    ULONG win32Result;

    // This is needed because NTSTATUS_FROM_WIN32 uses the argument multiple times.
    win32Result = GetLastError();

    return NTSTATUS_FROM_WIN32(win32Result);
}

FORCEINLINE PVOID PhGetModuleProcAddress(
    _In_ PWSTR ModuleName,
    _In_ PSTR ProcName
    )
{
    HMODULE module;

    module = GetModuleHandle(ModuleName);

    if (module)
        return GetProcAddress(module, ProcName);
    else
        return NULL;
}

#endif
