/*
 * Windows and DOS version functions
 *
 * Copyright 1997 Marcus Meissner
 * Copyright 1998 Patrik Stridvall
 * Copyright 1998, 2003 Andreas Mohr
 * Copyright 1997, 2003 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winternl.h"
#include "winerror.h"
#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ver);


static inline UCHAR version_update_condition(UCHAR *last_condition, UCHAR condition)
{
    switch (*last_condition)
    {
        case 0:
            *last_condition = condition;
            break;
        case VER_EQUAL:
            if (condition >= VER_EQUAL && condition <= VER_LESS_EQUAL)
            {
                *last_condition = condition;
                return condition;
            }
            break;
        case VER_GREATER:
        case VER_GREATER_EQUAL:
            if (condition >= VER_EQUAL && condition <= VER_GREATER_EQUAL)
                return condition;
            break;
        case VER_LESS:
        case VER_LESS_EQUAL:
            if (condition == VER_EQUAL || (condition >= VER_LESS && condition <= VER_LESS_EQUAL))
                return condition;
            break;
    }
    if (!condition) *last_condition |= 0x10;
    return *last_condition & 0xf;
}

static inline BOOL version_compare_values(ULONG left, ULONG right, UCHAR condition)
{
    switch (condition)
    {
        case VER_EQUAL:
            if (left != right) return FALSE;
            break;
        case VER_GREATER:
            if (left <= right) return FALSE;
            break;
        case VER_GREATER_EQUAL:
            if (left < right) return FALSE;
            break;
        case VER_LESS:
            if (left >= right) return FALSE;
            break;
        case VER_LESS_EQUAL:
            if (left > right) return FALSE;
            break;
        default:
            return FALSE;
    }
    return TRUE;
}


/******************************************************************************
 *        VerifyVersionInfoA   (KERNEL32.@)
 */
BOOL WINAPI VerifyVersionInfoA( LPOSVERSIONINFOEXA lpVersionInfo, DWORD dwTypeMask,
                                DWORDLONG dwlConditionMask)
{
    OSVERSIONINFOEXW verW;

    verW.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
    verW.dwMajorVersion = lpVersionInfo->dwMajorVersion;
    verW.dwMinorVersion = lpVersionInfo->dwMinorVersion;
    verW.dwBuildNumber = lpVersionInfo->dwBuildNumber;
    verW.dwPlatformId = lpVersionInfo->dwPlatformId;
    verW.wServicePackMajor = lpVersionInfo->wServicePackMajor;
    verW.wServicePackMinor = lpVersionInfo->wServicePackMinor;
    verW.wSuiteMask = lpVersionInfo->wSuiteMask;
    verW.wProductType = lpVersionInfo->wProductType;
    verW.wReserved = lpVersionInfo->wReserved;

    return VerifyVersionInfoW(&verW, dwTypeMask, dwlConditionMask);
}


/******************************************************************************
 *        VerifyVersionInfoW   (KERNEL32.@)
 */
BOOL WINAPI VerifyVersionInfoW( LPOSVERSIONINFOEXW info, DWORD dwTypeMask,
                                DWORDLONG dwlConditionMask)
{
    OSVERSIONINFOEXW ver;

    TRACE("(%p 0x%x 0x%s)\n", info, dwTypeMask, wine_dbgstr_longlong(dwlConditionMask));

    ver.dwOSVersionInfoSize = sizeof(ver);
    if (!GetVersionExW((OSVERSIONINFOW*)&ver)) return FALSE;

    if (!dwTypeMask || !dwlConditionMask)
    {
        SetLastError(ERROR_BAD_ARGUMENTS);
        return FALSE;
    }

    if (dwTypeMask & VER_PRODUCT_TYPE)
    {
        if (!version_compare_values(ver.wProductType, info->wProductType, dwlConditionMask >> 7*3 & 0x07))
            goto mismatch;
    }
    if (dwTypeMask & VER_SUITENAME)
        switch (dwlConditionMask >> 6*3 & 0x07)
        {
            case VER_AND:
                if ((info->wSuiteMask & ver.wSuiteMask) != info->wSuiteMask)
                    goto mismatch;
                break;
            case VER_OR:
                if (!(info->wSuiteMask & ver.wSuiteMask) && info->wSuiteMask)
                    goto mismatch;
                break;
            default:
                SetLastError(ERROR_BAD_ARGUMENTS);
                return FALSE;
        }
    if (dwTypeMask & VER_PLATFORMID)
    {
        if (!version_compare_values(ver.dwPlatformId, info->dwPlatformId, dwlConditionMask >> 3*3 & 0x07))
            goto mismatch;
    }
    if (dwTypeMask & VER_BUILDNUMBER)
    {
        if (!version_compare_values(ver.dwBuildNumber, info->dwBuildNumber, dwlConditionMask >> 2*3 & 0x07))
            goto mismatch;
    }

    if (dwTypeMask & (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR))
    {
        unsigned char condition, last_condition = 0;
        BOOL succeeded = TRUE, do_next_check = TRUE;

        if (dwTypeMask & VER_MAJORVERSION)
        {
            condition = version_update_condition(&last_condition, dwlConditionMask >> 1*3 & 0x07);
            succeeded = version_compare_values(ver.dwMajorVersion, info->dwMajorVersion, condition);
            do_next_check = (ver.dwMajorVersion == info->dwMajorVersion) &&
                ((condition >= VER_EQUAL) && (condition <= VER_LESS_EQUAL));
        }
        if ((dwTypeMask & VER_MINORVERSION) && do_next_check)
        {
            condition = version_update_condition(&last_condition, dwlConditionMask >> 0*3 & 0x07);
            succeeded = version_compare_values(ver.dwMinorVersion, info->dwMinorVersion, condition);
            do_next_check = (ver.dwMinorVersion == info->dwMinorVersion) &&
                ((condition >= VER_EQUAL) && (condition <= VER_LESS_EQUAL));
        }
        if ((dwTypeMask & VER_SERVICEPACKMAJOR) && do_next_check)
        {
            condition = version_update_condition(&last_condition, dwlConditionMask >> 5*3 & 0x07);
            succeeded = version_compare_values(ver.wServicePackMajor, info->wServicePackMajor, condition);
            do_next_check = (ver.wServicePackMajor == info->wServicePackMajor) &&
                ((condition >= VER_EQUAL) && (condition <= VER_LESS_EQUAL));
        }
        if ((dwTypeMask & VER_SERVICEPACKMINOR) && do_next_check)
        {
            condition = version_update_condition(&last_condition, dwlConditionMask >> 4*3 & 0x07);
            succeeded = version_compare_values(ver.wServicePackMinor, info->wServicePackMinor, condition);
        }

        if (!succeeded) goto mismatch;
    }

    return TRUE;

mismatch:
    SetLastError(ERROR_OLD_WIN_VERSION);
    return FALSE;
}

/***********************************************************************
 *           TermsrvAppInstallMode       (KERNEL32.@)
 *
 * Find out whether the terminal server is in INSTALL or EXECUTE mode.
 */
BOOL WINAPI TermsrvAppInstallMode(void)
{
    FIXME("stub\n");
    return FALSE;
}

/***********************************************************************
 *           SetTermsrvAppInstallMode       (KERNEL32.@)
 *
 * This function is said to switch between the INSTALL (TRUE) or
 * EXECUTE (FALSE) terminal server modes.
 *
 * This function always returns zero on WinXP Home so it's probably
 * safe to return that value in most cases. However, if a terminal
 * server is running it will probably return something else.
 */
DWORD WINAPI SetTermsrvAppInstallMode(BOOL bInstallMode)
{
    FIXME("(%d): stub\n", bInstallMode);
    return 0;
}