// bdls_filesystemutil_windowsimputil.h                               -*-C++-*-
#ifndef INCLUDED_BDLS_FILESYSTEMUTIL_WINDOWSIMPUTIL
#define INCLUDED_BDLS_FILESYSTEMUTIL_WINDOWSIMPUTIL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide testable 'bdls::FilesystemUtil' operations on Windows.
//
//@CLASSES:
//  bdls::FilesystemUtil_WindowsImpUtil: testable file-system utilities
//
//@SEE_ALSO: bdls_filesystemutil
//
//@DESCRIPTION: This subordinate component to 'bdls_filesystemutil' provides a
// utility 'struct' template, 'bdls::FilesystemUtil_WindowsImpUtil' for
// implementing some of 'bdls::FilesystemUtil's functions on Windows
// platforms.  'bdls::FilesystemUtil_WindowsImpUtil' accesses Windows functions
// and types through its template parameter, 'WINDOWS_INTERFACE', in order to
// allow tests to supply mock Windows interfaces.

#include <bdlt_datetime.h>
#include <bdlt_epochutil.h>

#include <bsls_assert.h>
#include <bsls_types.h>

#include <bslmt_once.h>

namespace BloombergLP {
namespace bdls {

                   // ====================================
                   // struct FilesystemUtil_WindowsImpUtil
                   // ====================================

template <class WINDOWS_INTERFACE>
struct FilesystemUtil_WindowsImpUtil {
    // This component-private utility 'struct' provides a namespace for a suite
    // of functions that 'FilesystemUtil' uses as implementation details.
    // These functions have a 'WINDOWS_INTERFACE' template parameter, which
    // provides access to the entities that Windows systems declare, and that
    // the function implementations need.
    //
    // The program is ill-formed unless the specified 'WINDOWS_INTERFACE' is
    // a class type that meets the following requirements:
    //
    //: o 'WINDOWS_INTERFACE::BOOL' is a type alias to the 'BOOL'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::DWORD' is a type alias to the 'DWORD'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::FILETIME' is a type alias to the 'FILETIME'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::HANDLE' is a type alias to the 'HANDLE'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::INT64' is a type alias to the 'IN64' type
    //:    provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::LPFILETIME' is a type alias to the
    //:   'LPFILETIME' type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::SYSTEMTIME' is a type alias to the
    //:   'LPFILETIME' type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::ULARGE_INTEGER' is a type alias to the
    //:   'ULARGE_INTEGER' type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::ULONG64' is a type alias to the 'ULONG64'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::ULONGLONG' is a type alias to the 'ULONGLONG'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::WORD' is a type alias to the 'WORD'
    //:   type provided by the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::FileTimeToSystemTime' is a public, static
    //:   member function that has
    //:   'BOOL (const FILETIME *lpFileTime, LPSYSTEMTIME lpSystemTime)' type
    //:   and whose contract is to return the result of
    //:   '::FileTimeToSystemTime(lpFileTime, lpSystemTime)', where
    //:   '::FileTimeToSystemTime' is the corresponding function declared in
    //:   the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::GetFileSize' is a public, static member
    //:   function that has 'DWORD (HANDLE hFile, LPDWORD lpFileSizeHigh)'
    //:   type and whose contract is to return the result of
    //:   '::GetFileSize(hFile, lpFileSizeHigh)', where '::GetFileSize' is the
    //:   corresponding function declared in the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::GetFileTime' is a public, static member
    //:   function that has
    //:   'BOOL (HANDLE hFile, LPFILETIME lpCreationTime,
    //:   LPFILETIME lpLastAccessTime, LPFILETIME, lpLastWriteTime)' type and
    //:   whose contract is to return the result of
    //:   '::GetFileTime(hFile, lpCreationTime, lpLastAccessTime,
    //:   lpLastWriteTime)', where '::GetFileTime' is the corresponding
    //:   function declared in the 'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::GetLastError' is a public, static
    //:    member function that has 'DWORD ()' type and whose contract
    //:    is to return the result of '::GetLastError()', where
    //:    '::GetLastError' is the corresponding function declared in the
    //:    'windows.h' header.
    //:
    //: o 'WINDOWS_INTERFACE::SystemTimeToFileTime' is a public, static
    //:   member function that has
    //:   'BOOL (const SYSTEMTIME *lpSystemTime, LPFILEMTIME lpFileTime)' type
    //:   and whose contract is to return the result of
    //:   '::SystemTimeToFileTime(lpSystemTime, lpFileTime)', where
    //:   '::SystemTimeToFileTime' is the corresponding function declared in
    //:   the 'windows.h' header.

    // TYPES
    typedef typename WINDOWS_INTERFACE::HANDLE FileDescriptor;
        // 'FileDescriptor' is an alias for operating system's native file
        // descriptor / file handle type.

    typedef typename WINDOWS_INTERFACE::INT64 Offset;
        // 'Offset' is an alias for a signed integral type, and represents the
        // offset of a location in a file.

  private:
    // PRIVATE TYPES
    typedef typename WINDOWS_INTERFACE::BOOL BOOL;
        // 'BOOL' is an alias to the unsigned integral 'BOOL' type provided
        // by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::DWORD DWORD;
        // 'DWORD' is an alias to the unsigned integral 'DWORD' type provided
        // by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::FILETIME FILETIME;
        // 'FILETIME' is an alias to the 'FILETIME' struct provided by the
        // 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::HANDLE HANDLE;
        // 'HANDLE' is an alias to the 'HANDLE' type provided by the
        // 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::INT64 INT64;
        // 'INT64' is an alias to the signed integral 'INT64' type provided by
        // the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::LPDWORD LPDWORD;
        // 'LPDWORD' is an alias to the unsigned integral 'LPDWORD' type
        // provided by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::LPFILETIME LPFILETIME;
        // 'LPFILETIME' is an alias to the 'LPFILETIME' type provided by the
        // 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::LPSYSTEMTIME LPSYSTEMTIME;
        // 'LPSYSTEMTIME' is an alias to the 'LPSYSTEMTIME' type provided by
        // the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::SYSTEMTIME SYSTEMTIME;
        // 'SYSTEMTIME' is an alias to the 'SYSTEMTIME' struct provided by the
        // 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::ULARGE_INTEGER ULARGE_INTEGER;
        // 'ULARGE_INTEGER' is an alias to the unsigned integral
        // 'ULARGE_INTEGER' type provided by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::ULONG64 ULONG64;
        // 'ULONG64' is an alias to the unsigned integral 'ULONG64' type
        // provided by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::ULONGLONG ULONGLONG;
        // 'ULONGLONG' is an alias to the unsigned integral 'ULONGLONG' type
        // provided by the 'windows.h' header.

    typedef typename WINDOWS_INTERFACE::WORD WORD;
        // 'WORD' is an alias to the unsigned integral 'WORD' type provided by
        // the 'windows.h' header.

    // PRIVATE CLASS METHODS
    static BOOL FileTimeToSystemTime(const FILETIME *lpFileTime,
                                     LPSYSTEMTIME    lpSystemTime);
        // Invoke and return the result of
        // '::FileTimeToSystemTime(lpFileTime, lpSystemTime)' with the
        // specified 'lpFileTime' and 'lpSystemTime', where
        // '::FileTimeToSystemTime' is the function provided by the 'windows.h'
        // header.

    static DWORD GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
        // Invoke and return the result of
        // '::GetFileSize(hFile, lpFileSizeHigh)' with the specified 'hFile'
        // and 'lpFileSizeHigh', where '::GetFileSize' is the function provided
        // by the 'windows.h' header.

    static BOOL GetFileTime(HANDLE     hFile,
                            LPFILETIME lpCreationTime,
                            LPFILETIME lpLastAccessTime,
                            LPFILETIME lpLastWriteTime);
        // Invoke and return the result of '::GetFileTime(hFile,
        // lpCreationTime, lpLastAccessTime, lpLastWriteTime)' with the
        // specified 'hFile', 'lpCreationTime', 'lpLasAccessTime', and
        // 'lpLastWriteTime', where '::GetFileTime' is the function provided by
        // the 'windows.h' header.

    static DWORD GetLastError();
        // Invoke and return the result of '::GetLastError()', where
        // '::GetLastError' is the function provided by the 'windows.h' header.

    static BOOL SystemTimeToFileTime(const SYSTEMTIME *lpSystemTime,
                                     LPFILETIME        lpFileTime);
        // Invoke and return the result of
        // '::SystemTimeToFileTime(lpSystemTime, lpFileTime)' with the
        // specified 'lpFileTime' and 'lpSystemTime', where
        // '::SystemTimeToFileTime' is the function provided by the 'windows.h'
        // header.

  public:
    // PUBLIC CLASS DATA
    static const bsls::Types::Int64 k_NANOSECONDS_PER_WINDOWS_TICK = 100;

    static const bsls::Types::Int64 k_WINDOWS_TICKS_PER_MICROSECOND =
                           bdlt::TimeUnitRatio::k_NANOSECONDS_PER_MICROSECOND /
                           k_NANOSECONDS_PER_WINDOWS_TICK;

    // CLASS METHODS
    static int convertFileTimeToDatetime(bdlt::Datetime   *time,
                                         const LPFILETIME  lpFileTime);
        // Load into the specified 'time' the time in the specified
        // 'lpFileTime'.  Return 0 on success, and a non-zero value otherwise.
        // Note that the time is reported in UTC.

    static Offset getFileSize(FileDescriptor descriptor);
        // Return the size, in bytes, of the file with the specified
        // 'descriptor', or a negative value if an error occurs.

    static int getLastModificationTime(bdlt::Datetime *time,
                                       FileDescriptor  descriptor);
        // Load into the specified 'time' the last modification time of the
        // file with the specified 'descriptor', as reported by the filesystem.
        // Return 0 on success, and a non-zero value otherwise.  Note that the
        // time is reported in UTC.
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                    // ------------------------------------
                    // struct FilesystemUtil_WindowsImpUtil
                    // ------------------------------------

// PRIVATE CLASS METHODS
template <class WINDOWS_INTERFACE>
typename WINDOWS_INTERFACE::BOOL
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::FileTimeToSystemTime(
                                                  const FILETIME *lpFileTime,
                                                  LPSYSTEMTIME    lpSystemTime)
{
    return WINDOWS_INTERFACE::FileTimeToSystemTime(lpFileTime, lpSystemTime);
}

template <class WINDOWS_INTERFACE>
typename WINDOWS_INTERFACE::DWORD
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::GetFileSize(
                                                        HANDLE  hFile,
                                                        LPDWORD lpFileSizeHigh)
{
    return WINDOWS_INTERFACE::GetFileSize(hFile, lpFileSizeHigh);
}

template <class WINDOWS_INTERFACE>
typename WINDOWS_INTERFACE::BOOL
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::GetFileTime(
                                                   HANDLE     hFile,
                                                   LPFILETIME lpCreationTime,
                                                   LPFILETIME lpLastAccessTime,
                                                   LPFILETIME lpLastWriteTime)
{
    return WINDOWS_INTERFACE::GetFileTime(
        hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
}

template <class WINDOWS_INTERFACE>
typename WINDOWS_INTERFACE::DWORD
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::GetLastError()
{
    return WINDOWS_INTERFACE::GetLastError();
}

template <class WINDOWS_INTERFACE>
typename WINDOWS_INTERFACE::BOOL
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::SystemTimeToFileTime(
                                                const SYSTEMTIME *lpSystemTime,
                                                LPFILETIME        lpFileTime)
{
    return WINDOWS_INTERFACE::SystemTimeToFileTime(lpSystemTime, lpFileTime);
}

// CLASS METHODS
template <class WINDOWS_INTERFACE>
int
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::convertFileTimeToDatetime(
                                                  bdlt::Datetime   *time,
                                                  const LPFILETIME  lpFileTime)
{
    BSLS_ASSERT(time);
    BSLS_ASSERT(lpFileTime);

    // We avoid unneccesary system calls by storing the offset between the
    // Windows 'FILETIME' epoch and the unix epoch, in microseconds, as
    // calculated using Windows system calls.  Note that this differs from the
    // actual number of microseconds between 1601-01-01 and
    // 1970-01-01 as the Windows system calls do not correctly consider
    // the conversion, in 1752, from the Julian to the Gregorian calendar,
    // hence the prefix 'adjusted'.
    static ULONGLONG adjustedFiletimeEpochToUnixEpochInMicros;

    BSLMT_ONCE_DO
    {
        const SYSTEMTIME unixEpochSystemTimeUtc =
        {
            1970, // year
            1,    // month
            4,    // day of week (4=Thursday)
            1,    // day of month
            0,    // hour
            0,    // minute
            0,    // second
            0     // microsecond
        };

        FILETIME unixEpochFileTimeUtc;
        const BOOL systemTimeToFileTimeStatus = SystemTimeToFileTime(
                                                       &unixEpochSystemTimeUtc,
                                                       &unixEpochFileTimeUtc);
        if (!systemTimeToFileTimeStatus) {
            return -1;                                                // RETURN
        }

        // Copy the individual parts per the Microsoft recommendation at
        // https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
        ULARGE_INTEGER unixEpochFileTimeInTicks;
        unixEpochFileTimeInTicks.u.HighPart =
                                           unixEpochFileTimeUtc.dwHighDateTime;
        unixEpochFileTimeInTicks.u.LowPart =
                                            unixEpochFileTimeUtc.dwLowDateTime;

        adjustedFiletimeEpochToUnixEpochInMicros =
                                            unixEpochFileTimeInTicks.QuadPart /
                                            k_WINDOWS_TICKS_PER_MICROSECOND;
    }

    // Copy the individual parts per the Microsoft recommendation.
    ULARGE_INTEGER lastWriteTimeInTicks;
    lastWriteTimeInTicks.u.HighPart = lpFileTime->dwHighDateTime;
    lastWriteTimeInTicks.u.LowPart  = lpFileTime->dwLowDateTime;

    ULONGLONG lastWriteTimeInMicroseconds = lastWriteTimeInTicks.QuadPart /
                                            k_WINDOWS_TICKS_PER_MICROSECOND;

    if (lastWriteTimeInMicroseconds <
        adjustedFiletimeEpochToUnixEpochInMicros) {
        // Timestamps prior to the unix epoch are not currently supported.
        return -1;                                                    // RETURN
    }

    bdlt::Datetime result = bdlt::EpochUtil::epoch();

    int rc = result.addMicrosecondsIfValid(lastWriteTimeInMicroseconds -
                                     adjustedFiletimeEpochToUnixEpochInMicros);

    if (0 != rc) {
        return -1;                                                    // RETURN
    }

    *time = result;
    return 0;
}

template <class WINDOWS_INTERFACE>
typename FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::Offset
FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::getFileSize(
                                                     FileDescriptor descriptor)
{
    BSLMF_ASSERT(bsl::is_integral<DWORD>::value);
    BSLS_ASSERT(0            == bsl::numeric_limits<DWORD>::min());
    BSLS_ASSERT(0xFFFFFFFFul == bsl::numeric_limits<DWORD>::max());

    BSLMF_ASSERT(bsl::is_integral<ULONG64>::value);
    BSLS_ASSERT(0                     == bsl::numeric_limits<ULONG64>::min());
    BSLS_ASSERT(0xFFFFFFFFFFFFFFFFull == bsl::numeric_limits<ULONG64>::max());

    // The Windows implementation of this function uses 'GetFileSize' on
    // purpose, even though the Win32 API documentation instructs the reader to
    // use 'GetFileSizeEx' instead.  'GetFileSizeEx' returns a 'LARGE_INTEGER',
    // which is a union of two or more layout-incompatible, 64-bit integer
    // representations, and there is no way to know which member is active.
    // This forces the programmer to do "union type punning," which is jargon
    // for invoking undefined behavior by accessing an inactive union member
    // that is not layout-compatible with the active member.
    //
    // 'GetFileSize' has an awkward interface, but it requires no type punning
    // and isn't deprecated.

    DWORD sizeHigh32Bits;
    const DWORD sizeLow32Bits = GetFileSize(descriptor, &sizeHigh32Bits);
    // 'GetFileSize' returns the maximum unsigned, 32-bit integer to indicate
    // that it could not get the file size.  However, this is also a legal
    // value for the file size's low 32-bits.  To remove the ambiguity,
    // this function calls 'GetLastError', which returns non-zero to indicate
    // the last system call had an error, and 0 otherwise.

    static const DWORD k_INVALID_FILE_SIZE = 0xFFFFFFFFul;
    if (k_INVALID_FILE_SIZE == sizeLow32Bits) {
        const DWORD lastError = GetLastError();

        static const DWORD k_NO_ERROR = 0;
        if (k_NO_ERROR != lastError) {
            return -1;                                                // RETURN
        }
    }

    const ULONG64 uSizeHigh32Bits = static_cast<ULONG64>(sizeHigh32Bits);
    const ULONG64 uSizeLow32Bits  = static_cast<ULONG64>(sizeLow32Bits);
    const ULONG64 uSize64Bits     = (uSizeHigh32Bits << 32) | uSizeLow32Bits;

    return static_cast<Offset>(uSize64Bits);
}

template <class WINDOWS_INTERFACE>
int FilesystemUtil_WindowsImpUtil<WINDOWS_INTERFACE>::getLastModificationTime(
                                                    bdlt::Datetime *time,
                                                    FileDescriptor  descriptor)
{
    static const LPFILETIME s_IGNORED_CREATION_TIME    = 0;
    static const LPFILETIME s_IGNORED_LAST_ACCESS_TIME = 0;

    FILETIME lastWriteTime;
    const BOOL getFileTimeSuccessFlag = GetFileTime(descriptor,
                                                    s_IGNORED_CREATION_TIME,
                                                    s_IGNORED_LAST_ACCESS_TIME,
                                                    &lastWriteTime);
    if (!getFileTimeSuccessFlag) {
        return -1;                                                    // RETURN
    }

    return convertFileTimeToDatetime(time, &lastWriteTime);
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2020 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------