/*
* Find Windows version using bisection method and VerifyVersionInfo.
*
* Author:   M1xA, www.m1xa.com
* Date:     2013.07.07
* Licence:  MIT
* Version:  1.0
*
* API:
*
* BOOL GetVersionExEx(OSVERSIONINFOEX * osVer);
* Returns: 0 if fails.
*
* Supported OS: Windows 2000 .. Windows 8.1.
*/
#ifndef __FIND_VERSION__
#define __FIND_VERSION__

#include <windows.h>

#define FV_EQUAL 0
#define FV_LESS (-1)
#define FV_GREAT 1
#define FV_MIN_VALUE 0
#define FV_MINOR_VERSION_MAX_VALUE 16

int testValue(OSVERSIONINFOEX * value, DWORD verPart, DWORDLONG eq, DWORDLONG gt)
{
    if (VerifyVersionInfo(value, verPart, eq) == FALSE)
    {
        if (VerifyVersionInfo(value, verPart, gt) == TRUE)
            return FV_GREAT;
        return FV_LESS;
    }
    else
        return FV_EQUAL;
}

DWORDLONG gtFor(DWORD target)
{
    return VerSetConditionMask(0, target, VER_GREATER);
}

DWORDLONG eqFor(DWORD target)
{
    return VerSetConditionMask(0, target, VER_EQUAL);
}

// NOLINTBEGIN(bugprone-macro-parentheses)
#define findPartTemplate(T)\
BOOL findPart##T(T * part, DWORD partType, OSVERSIONINFOEX * ret, T a, T b) \
{ \
    int funx = FV_EQUAL; \
\
    DWORDLONG const eq = eqFor(partType); \
    DWORDLONG const gt = gtFor(partType); \
\
    T * p = part; \
\
    *p = (a + b) / 2; \
\
    while ((funx = testValue(ret, partType, eq, gt)) != FV_EQUAL) \
    { \
        switch (funx) \
        { \
            case FV_GREAT: a = *p; break; \
            case FV_LESS:  b = *p; break; \
        } \
\
        *p = (a + b) / 2; \
\
        if (*p == a) \
        { \
            if (testValue(ret, partType, eq, gt) == FV_EQUAL) \
                return TRUE; \
\
            *p = b; \
\
            if (testValue(ret, partType, eq, gt) == FV_EQUAL) \
                return TRUE; \
\
            a = 0; \
            b = 0; \
            *p = 0; \
        } \
\
        if (a == b) \
        { \
            *p = 0; \
            return FALSE; \
        } \
    } \
\
    return TRUE; \
}
// NOLINTEND(bugprone-macro-parentheses)

findPartTemplate(DWORD)
findPartTemplate(WORD)
findPartTemplate(BYTE)

BOOL GetVersionExEx(OSVERSIONINFOEX * osVer)
{
    BOOL ret = TRUE;

    ZeroMemory(osVer, sizeof(OSVERSIONINFOEX));

    osVer->dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    ret &= findPartDWORD(&osVer->dwPlatformId, VER_PLATFORMID, osVer, FV_MIN_VALUE, MAXDWORD);
    ret &= findPartDWORD(&osVer->dwMajorVersion, VER_MAJORVERSION, osVer, FV_MIN_VALUE, MAXDWORD);
    ret &= findPartDWORD(&osVer->dwMinorVersion, VER_MINORVERSION, osVer, FV_MIN_VALUE, FV_MINOR_VERSION_MAX_VALUE);
    ret &= findPartDWORD(&osVer->dwBuildNumber, VER_BUILDNUMBER, osVer, FV_MIN_VALUE, MAXDWORD);
    ret &= findPartWORD(&osVer->wServicePackMajor, VER_SERVICEPACKMAJOR, osVer, FV_MIN_VALUE, MAXWORD);
    ret &= findPartWORD(&osVer->wServicePackMinor, VER_SERVICEPACKMINOR, osVer, FV_MIN_VALUE, MAXWORD);
    ret &= findPartWORD(&osVer->wSuiteMask, VER_SUITENAME, osVer, FV_MIN_VALUE, MAXWORD);
    ret &= findPartBYTE(&osVer->wProductType, VER_PRODUCT_TYPE, osVer, FV_MIN_VALUE, MAXBYTE);

    return ret;
}

#endif
