/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */

#include <systools/win32/uwinapi.h>

#include <osl/file.hxx>
#include <rtl/alloc.h>
#include <rtl/byteseq.h>
#include <sal/log.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
#include <o3tl/typed_flags_set.hxx>

#include "file-impl.hxx"
#include "file_url.hxx"
#include "file_error.hxx"

#include <atomic>
#include <cassert>
#include <algorithm>
#include <limits>
#include <mutex>

#ifdef max /* conflict w/ std::numeric_limits<T>::max() */
#undef max
#endif
#ifdef min
#undef min
#endif

namespace {

/** State
 */
enum class StateBits
{
    Seekable  = 1, /*< open() sets, iff regular file */
    Readable  = 2, /*< open() sets, read() requires */
    Writeable = 4, /*< open() sets, write() requires */
    Modified  = 8  /* write() sets, flush() resets */
};

}

template<> struct o3tl::typed_flags<StateBits>: o3tl::is_typed_flags<StateBits, 0xF> {};

namespace {

/** File handle implementation.
*/
struct FileHandle_Impl
{
    std::mutex       m_mutex;
    HANDLE           m_hFile;

    StateBits m_state;

    sal_uInt64    m_size;    /*< file size */
    LONGLONG      m_offset;  /*< physical offset from begin of file */
    // m_filepos is hit hard in some situations, where the overhead of a mutex starts to show up, so use an atomic
    std::atomic<LONGLONG> m_filepos; /*< logical offset from begin of file */

    LONGLONG      m_bufptr;  /*< buffer offset from begin of file */
    SIZE_T        m_buflen;  /*< buffer filled [0, m_bufsiz - 1] */

    SIZE_T        m_bufsiz;
    sal_uInt8 *   m_buffer;

    explicit      FileHandle_Impl (HANDLE hFile);
                  ~FileHandle_Impl();

    static SIZE_T getpagesize();

    sal_uInt64    getPos() const;
    oslFileError  setPos (sal_uInt64 uPos);

    sal_uInt64    getSize() const;
    oslFileError  setSize (sal_uInt64 uPos);

    oslFileError  readAt(
        LONGLONG     nOffset,
        void *       pBuffer,
        DWORD        nBytesRequested,
        sal_uInt64 * pBytesRead);

    oslFileError  writeAt(
        LONGLONG     nOffset,
        void const * pBuffer,
        DWORD        nBytesToWrite,
        sal_uInt64 * pBytesWritten);

    oslFileError  readFileAt(
        LONGLONG     nOffset,
        void *       pBuffer,
        sal_uInt64   uBytesRequested,
        sal_uInt64 * pBytesRead);

    oslFileError  writeFileAt(
        LONGLONG     nOffset,
        void const * pBuffer,
        sal_uInt64   uBytesToWrite,
        sal_uInt64 * pBytesWritten);

    oslFileError  readLineAt(
        LONGLONG        nOffset,
        sal_Sequence ** ppSequence,
        sal_uInt64 *    pBytesRead);

    static oslFileError writeSequence_Impl (
        sal_Sequence ** ppSequence,
        SIZE_T *        pnOffset,
        const void *    pBuffer,
        SIZE_T          nBytes);

    oslFileError syncFile();
};

}


FileHandle_Impl::FileHandle_Impl(HANDLE hFile)
    : m_hFile   (hFile),
      m_state   (StateBits::Readable | StateBits::Writeable),
      m_size    (0),
      m_offset  (0),
      m_filepos (0),
      m_bufptr  (-1),
      m_buflen  (0),
      m_bufsiz  (getpagesize()),
      m_buffer  (nullptr)
{
    m_buffer = static_cast<sal_uInt8 *>(calloc(m_bufsiz, 1));
}

FileHandle_Impl::~FileHandle_Impl()
{
    free(m_buffer);
    m_buffer = nullptr;
}

SIZE_T FileHandle_Impl::getpagesize()
{
    SYSTEM_INFO info;
    ::GetSystemInfo(&info);
    return sal::static_int_cast< SIZE_T >(info.dwPageSize);
}

sal_uInt64 FileHandle_Impl::getPos() const
{
    return sal::static_int_cast< sal_uInt64 >(m_filepos.load());
}

oslFileError FileHandle_Impl::setPos(sal_uInt64 uPos)
{
    m_filepos = sal::static_int_cast< LONGLONG >(uPos);
    return osl_File_E_None;
}

sal_uInt64 FileHandle_Impl::getSize() const
{
    LONGLONG bufend = std::max(LONGLONG(0), m_bufptr) + m_buflen;
    return std::max(m_size, sal::static_int_cast< sal_uInt64 >(bufend));
}

oslFileError FileHandle_Impl::setSize(sal_uInt64 uSize)
{
    LARGE_INTEGER nDstPos; nDstPos.QuadPart = sal::static_int_cast< LONGLONG >(uSize);
    if (!::SetFilePointerEx(m_hFile, nDstPos, nullptr, FILE_BEGIN))
        return oslTranslateFileError(GetLastError());

    if (!::SetEndOfFile(m_hFile))
        return oslTranslateFileError(GetLastError());
    m_size = uSize;

    nDstPos.QuadPart = m_offset;
    if (!::SetFilePointerEx(m_hFile, nDstPos, nullptr, FILE_BEGIN))
        return oslTranslateFileError(GetLastError());

    return osl_File_E_None;
}

oslFileError FileHandle_Impl::readAt(
    LONGLONG     nOffset,
    void *       pBuffer,
    DWORD        nBytesRequested,
    sal_uInt64 * pBytesRead)
{
    SAL_WARN_IF(!(m_state & StateBits::Seekable), "sal.osl", "FileHandle_Impl::readAt(): not seekable");
    if (!(m_state & StateBits::Seekable))
        return osl_File_E_SPIPE;

    SAL_WARN_IF(!(m_state & StateBits::Readable), "sal.osl", "FileHandle_Impl::readAt(): not readable");
    if (!(m_state & StateBits::Readable))
        return osl_File_E_BADF;

    if (nOffset != m_offset)
    {
        LARGE_INTEGER liOffset; liOffset.QuadPart = nOffset;
        if (!::SetFilePointerEx(m_hFile, liOffset, nullptr, FILE_BEGIN))
            return oslTranslateFileError(GetLastError());
        m_offset = nOffset;
    }

    DWORD dwDone = 0;
    if (!::ReadFile(m_hFile, pBuffer, nBytesRequested, &dwDone, nullptr))
        return oslTranslateFileError(GetLastError());
    m_offset += dwDone;

    *pBytesRead = dwDone;
    return osl_File_E_None;
}

oslFileError FileHandle_Impl::writeAt(
    LONGLONG     nOffset,
    void const * pBuffer,
    DWORD        nBytesToWrite,
    sal_uInt64 * pBytesWritten)
{
    SAL_WARN_IF(!(m_state & StateBits::Seekable), "sal.osl", "FileHandle_Impl::writeAt(): not seekable");
    if (!(m_state & StateBits::Seekable))
        return osl_File_E_SPIPE;

    SAL_WARN_IF(!(m_state & StateBits::Writeable), "sal.osl", "FileHandle_Impl::writeAt(): not writeable");
    if (!(m_state & StateBits::Writeable))
        return osl_File_E_BADF;

    if (nOffset != m_offset)
    {
        LARGE_INTEGER liOffset; liOffset.QuadPart = nOffset;
        if (!::SetFilePointerEx (m_hFile, liOffset, nullptr, FILE_BEGIN))
            return oslTranslateFileError(GetLastError());
        m_offset = nOffset;
    }

    DWORD dwDone = 0;
    if (!::WriteFile(m_hFile, pBuffer, nBytesToWrite, &dwDone, nullptr))
        return oslTranslateFileError(GetLastError());
    m_offset += dwDone;

    m_size = std::max(m_size, sal::static_int_cast< sal_uInt64 >(m_offset));

    *pBytesWritten = dwDone;
    return osl_File_E_None;
}

oslFileError FileHandle_Impl::readFileAt(
    LONGLONG     nOffset,
    void *       pBuffer,
    sal_uInt64   uBytesRequested,
    sal_uInt64 * pBytesRead)
{
    static sal_uInt64 const g_limit_dword = std::numeric_limits< DWORD >::max();
    if (g_limit_dword < uBytesRequested)
        return osl_File_E_OVERFLOW;
    DWORD nBytesRequested = sal::static_int_cast< DWORD >(uBytesRequested);

    if (!(m_state & StateBits::Seekable))
    {
        // not seekable (pipe)
        DWORD dwDone = 0;
        if (!::ReadFile(m_hFile, pBuffer, nBytesRequested, &dwDone, nullptr))
            return oslTranslateFileError(GetLastError());
        *pBytesRead = dwDone;
        return osl_File_E_None;
    }
    else if (!m_buffer)
    {
        // not buffered
        return readAt (nOffset, pBuffer, nBytesRequested, pBytesRead);
    }
    else
    {
        sal_uInt8 * buffer = static_cast< sal_uInt8* >(pBuffer);
        for (*pBytesRead = 0; nBytesRequested > 0;)
        {
            LONGLONG const bufptr = (nOffset / m_bufsiz) * m_bufsiz;
            SIZE_T   const bufpos = nOffset % m_bufsiz;

            if (bufptr != m_bufptr)
            {
                // flush current buffer
                oslFileError result = syncFile();
                if (result != osl_File_E_None)
                    return result;
                m_bufptr = -1;
                m_buflen = 0;

                if (nBytesRequested >= m_bufsiz)
                {
                    // buffer too small, read through from file
                    sal_uInt64 uDone = 0;
                    result = readAt (nOffset, &(buffer[*pBytesRead]), nBytesRequested, &uDone);
                    if (result != osl_File_E_None)
                        return result;

                    nBytesRequested -= sal::static_int_cast< DWORD >(uDone);
                    *pBytesRead += uDone;
                    return osl_File_E_None;
                }

                // update buffer (pointer)
                sal_uInt64 uDone = 0;
                result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
                if (result != osl_File_E_None)
                    return result;
                m_bufptr = bufptr;
                m_buflen = sal::static_int_cast< SIZE_T >(uDone);
            }
            if (bufpos >= m_buflen)
            {
                // end of file
                return osl_File_E_None;
            }

            SIZE_T const bytes = std::min(m_buflen - bufpos, static_cast<SIZE_T>(nBytesRequested));
            memcpy(&(buffer[*pBytesRead]), &(m_buffer[bufpos]), bytes);
            nBytesRequested -= bytes;
            *pBytesRead += bytes;
            nOffset += bytes;
        }
        return osl_File_E_None;
    }
}

oslFileError FileHandle_Impl::writeFileAt(
    LONGLONG     nOffset,
    void const * pBuffer,
    sal_uInt64   uBytesToWrite,
    sal_uInt64 * pBytesWritten)
{
    static sal_uInt64 const g_limit_dword = std::numeric_limits< DWORD >::max();
    if (g_limit_dword < uBytesToWrite)
        return osl_File_E_OVERFLOW;
    DWORD nBytesToWrite = sal::static_int_cast< DWORD >(uBytesToWrite);

    if (!(m_state & StateBits::Seekable))
    {
        // not seekable (pipe)
        DWORD dwDone = 0;
        if (!::WriteFile(m_hFile, pBuffer, nBytesToWrite, &dwDone, nullptr))
            return oslTranslateFileError(GetLastError());
        *pBytesWritten = dwDone;
        return osl_File_E_None;
    }
    else if (!m_buffer)
    {
        // not buffered
        return writeAt(nOffset, pBuffer, nBytesToWrite, pBytesWritten);
    }
    else
    {
        sal_uInt8 const * buffer = static_cast< sal_uInt8 const* >(pBuffer);
        for (*pBytesWritten = 0; nBytesToWrite > 0;)
        {
            LONGLONG const bufptr = (nOffset / m_bufsiz) * m_bufsiz;
            SIZE_T   const bufpos = nOffset % m_bufsiz;
            if (bufptr != m_bufptr)
            {
                // flush current buffer
                oslFileError result = syncFile();
                if (result != osl_File_E_None)
                    return result;
                m_bufptr = -1;
                m_buflen = 0;

                if (nBytesToWrite >= m_bufsiz)
                {
                    // buffer too small, write through to file
                    sal_uInt64 uDone = 0;
                    result = writeAt(nOffset, &(buffer[*pBytesWritten]), nBytesToWrite, &uDone);
                    if (result != osl_File_E_None)
                        return result;
                    if (uDone != nBytesToWrite)
                        return osl_File_E_IO;

                    nBytesToWrite -= sal::static_int_cast< DWORD >(uDone);
                    *pBytesWritten += uDone;
                    return osl_File_E_None;
                }

                // update buffer (pointer)
                sal_uInt64 uDone = 0;
                result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
                if (result != osl_File_E_None)
                    return result;
                m_bufptr = bufptr;
                m_buflen = sal::static_int_cast< SIZE_T >(uDone);
            }

            SIZE_T const bytes = std::min(m_bufsiz - bufpos, static_cast<SIZE_T>(nBytesToWrite));
            memcpy(&(m_buffer[bufpos]), &(buffer[*pBytesWritten]), bytes);
            nBytesToWrite -= bytes;
            *pBytesWritten += bytes;
            nOffset += bytes;

            m_buflen = std::max(m_buflen, bufpos + bytes);
            m_state |= StateBits::Modified;
        }
        return osl_File_E_None;
    }
}

oslFileError FileHandle_Impl::readLineAt(
    LONGLONG        nOffset,
    sal_Sequence ** ppSequence,
    sal_uInt64 *    pBytesRead)
{
    oslFileError result = osl_File_E_None;

    LONGLONG bufptr = (nOffset / m_bufsiz) * m_bufsiz;
    if (bufptr != m_bufptr)
    {
        /* flush current buffer */
        result = syncFile();
        if (result != osl_File_E_None)
            return result;

        /* update buffer (pointer) */
        sal_uInt64 uDone = 0;
        result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
        if (result != osl_File_E_None)
            return result;

        m_bufptr = bufptr;
        m_buflen = sal::static_int_cast< SIZE_T >(uDone);
    }

    static int const LINE_STATE_BEGIN = 0;
    static int const LINE_STATE_CR    = 1;
    static int const LINE_STATE_LF    = 2;

    SIZE_T bufpos = sal::static_int_cast< SIZE_T >(nOffset - m_bufptr), curpos = bufpos, dstpos = 0;
    int    state  = (bufpos >= m_buflen) ? LINE_STATE_LF : LINE_STATE_BEGIN;

    for (; state != LINE_STATE_LF;)
    {
        if (curpos >= m_buflen)
        {
            /* buffer examined */
            if (curpos > bufpos) // actually, curpos can't become less than bufpos, so != could do
            {
                /* flush buffer to sequence */
                result = writeSequence_Impl(
                    ppSequence, &dstpos, &(m_buffer[bufpos]), curpos - bufpos);
                if (result != osl_File_E_None)
                    return result;
                *pBytesRead += curpos - bufpos;
                nOffset += curpos - bufpos;
            }

            bufptr = nOffset / m_bufsiz * m_bufsiz;
            if (bufptr != m_bufptr)
            {
                /* update buffer (pointer) */
                sal_uInt64 uDone = 0;
                result = readAt(bufptr, m_buffer, m_bufsiz, &uDone);
                if (result != osl_File_E_None)
                    return result;
                m_bufptr = bufptr;
                m_buflen = sal::static_int_cast< SIZE_T >(uDone);
            }

            bufpos = sal::static_int_cast< SIZE_T >(nOffset - m_bufptr);
            curpos = bufpos;
            if (bufpos >= m_buflen)
                break;
        }
        switch (state)
        {
        case LINE_STATE_CR:
            state = LINE_STATE_LF;
            switch (m_buffer[curpos])
            {
            case 0x0A: /* CRLF */
                /* eat current char */
                curpos++;
                break;
            default: /* single CR */
                /* keep current char */
                break;
            }
            break;
        default:
            /* determine next state */
            switch (m_buffer[curpos])
            {
            case 0x0A: /* single LF */
                state = LINE_STATE_LF;
                break;
            case 0x0D: /* CR */
                state = LINE_STATE_CR;
                break;
            default: /* advance to next char */
                curpos++;
                break;
            }
            if (state != LINE_STATE_BEGIN)
            {
                /* store (and eat) the newline char */
                m_buffer[curpos] = 0x0A;
                curpos++;

                /* flush buffer to sequence */
                result = writeSequence_Impl(
                    ppSequence, &dstpos, &(m_buffer[bufpos]), curpos - bufpos - 1);
                if (result != osl_File_E_None)
                    return result;
                *pBytesRead += curpos - bufpos;
                nOffset += curpos - bufpos;
            }
            break;
        }
    }

    result = writeSequence_Impl(ppSequence, &dstpos, nullptr, 0);
    if (result != osl_File_E_None)
        return result;
    if (dstpos > 0)
        return osl_File_E_None;
    if (bufpos >= m_buflen)
        return osl_File_E_AGAIN;
    return osl_File_E_None;
}

oslFileError FileHandle_Impl::writeSequence_Impl(
    sal_Sequence ** ppSequence,
    SIZE_T *        pnOffset,
    const void *    pBuffer,
    SIZE_T          nBytes)
{
    sal_Int32 nElements = *pnOffset + nBytes;
    if (!*ppSequence)
    {
        /* construct sequence */
        rtl_byte_sequence_constructNoDefault(ppSequence, nElements);
    }
    else if (nElements != (*ppSequence)->nElements)
    {
        /* resize sequence */
        rtl_byte_sequence_realloc(ppSequence, nElements);
    }
    if (*ppSequence)
    {
        /* fill sequence */
        memcpy(&((*ppSequence)->elements[*pnOffset]), pBuffer, nBytes);
        *pnOffset += nBytes;
    }
    return (*ppSequence) ? osl_File_E_None : osl_File_E_NOMEM;
}

oslFileError FileHandle_Impl::syncFile()
{
    oslFileError result = osl_File_E_None;
    if (m_state & StateBits::Modified)
    {
        sal_uInt64 uDone = 0;
        result = writeAt(m_bufptr, m_buffer, m_buflen, &uDone);
        if (result != osl_File_E_None)
            return result;
        if (uDone != m_buflen)
            return osl_File_E_IO;
        m_state &= ~StateBits::Modified;
    }
    return result;
}

extern "C" oslFileHandle osl_createFileHandleFromOSHandle(
    HANDLE     hFile,
    sal_uInt32 uFlags)
{
    if (!IsValidHandle(hFile))
        return nullptr; // EINVAL

    FileHandle_Impl * pImpl = new FileHandle_Impl(hFile);

    /* check for regular file */
    if (GetFileType(hFile) == FILE_TYPE_DISK)
    {
        /* mark seekable */
        pImpl->m_state |= StateBits::Seekable;

        /* init current size */
        LARGE_INTEGER uSize = { { 0, 0 } };
        (void) ::GetFileSizeEx(hFile, &uSize);
        pImpl->m_size = (sal::static_int_cast<sal_uInt64>(uSize.HighPart) << 32) + uSize.LowPart;
    }

    if (!(uFlags & osl_File_OpenFlag_Read))
        pImpl->m_state &= ~StateBits::Readable;
    if (!(uFlags & osl_File_OpenFlag_Write))
        pImpl->m_state &= ~StateBits::Writeable;

    SAL_WARN_IF(
        !((uFlags & osl_File_OpenFlag_Read) || (uFlags & osl_File_OpenFlag_Write)),
        "sal.osl",
        "osl_createFileHandleFromOSHandle(): missing read/write access flags");
    return static_cast<oslFileHandle>(pImpl);
}

oslFileError SAL_CALL osl_openFile(
    rtl_uString *   strPath,
    oslFileHandle * pHandle,
    sal_uInt32      uFlags)
{
    OUString strSysPath;
    oslFileError result = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);
    if (result != osl_File_E_None)
        return result;

    // tdf126742 use FILE_SHARE_WRITE to get closer to non-Windows platform behaviour,
    // for details and discussion see task please
    DWORD dwAccess = GENERIC_READ, dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE, dwCreation = 0;

    if (uFlags & osl_File_OpenFlag_Write)
        dwAccess |= GENERIC_WRITE;

    if (uFlags & osl_File_OpenFlag_NoLock)
        dwShare  |= FILE_SHARE_DELETE;

    if (uFlags & osl_File_OpenFlag_Create)
        dwCreation |= CREATE_NEW;
    else
        dwCreation |= OPEN_EXISTING;

    HANDLE hFile = CreateFileW(
        o3tl::toW(strSysPath.getStr()),
        dwAccess, dwShare, nullptr, dwCreation, 0, nullptr);

    // @@@ ERROR HANDLING @@@
    if (!IsValidHandle(hFile))
        result = oslTranslateFileError(GetLastError());

    *pHandle = osl_createFileHandleFromOSHandle(hFile, uFlags | osl_File_OpenFlag_Read);

    return result;
}

oslFileError SAL_CALL osl_syncFile(oslFileHandle Handle)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile))
        return osl_File_E_INVAL;

    std::lock_guard lock(pImpl->m_mutex);

    oslFileError result = pImpl->syncFile();
    if (result != osl_File_E_None)
        return result;

    if (!FlushFileBuffers(pImpl->m_hFile))
        return oslTranslateFileError(GetLastError());

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_closeFile(oslFileHandle Handle)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile))
        return osl_File_E_INVAL;

    oslFileError result;
    {
        std::lock_guard lock(pImpl->m_mutex);

        result = pImpl->syncFile();
        if (result != osl_File_E_None)
        {
            /* ignore double failure */
            (void)::CloseHandle(pImpl->m_hFile);
        }
        else if (!::CloseHandle(pImpl->m_hFile))
        {
            /* translate error code */
            result = oslTranslateFileError(GetLastError());
        }
    }

    delete pImpl;
    return result;
}

namespace {

template<typename T> bool exceedsMaxSIZE_T(T n)
{ return n > std::numeric_limits< SIZE_T >::max(); }

}

oslFileError SAL_CALL osl_mapFile(
    oslFileHandle Handle,
    void**        ppAddr,
    sal_uInt64    uLength,
    sal_uInt64    uOffset,
    sal_uInt32    uFlags)
{
    struct FileMapping
    {
        HANDLE m_handle;

        explicit FileMapping(HANDLE hMap)
            : m_handle(hMap)
        {}

        ~FileMapping()
        {
            (void)::CloseHandle(m_handle);
        }
    };

    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!ppAddr))
        return osl_File_E_INVAL;
    *ppAddr = nullptr;

    if (exceedsMaxSIZE_T(uLength))
        return osl_File_E_OVERFLOW;
    SIZE_T const nLength = sal::static_int_cast< SIZE_T >(uLength);

    FileMapping aMap(::CreateFileMappingW(pImpl->m_hFile, nullptr, SEC_COMMIT | PAGE_READONLY, 0, 0, nullptr));
    if (!IsValidHandle(aMap.m_handle))
        return oslTranslateFileError(GetLastError());

    DWORD const dwOffsetHi = sal::static_int_cast<DWORD>(uOffset >> 32);
    DWORD const dwOffsetLo = sal::static_int_cast<DWORD>(uOffset & 0xFFFFFFFF);

    *ppAddr = ::MapViewOfFile(aMap.m_handle, FILE_MAP_READ, dwOffsetHi, dwOffsetLo, nLength);
    if (!*ppAddr)
        return oslTranslateFileError(GetLastError());

    if (uFlags & osl_File_MapFlag_RandomAccess)
    {
        // Determine memory pagesize.
        SYSTEM_INFO info;
        ::GetSystemInfo(&info);
        DWORD const dwPageSize = info.dwPageSize;

        /*
         * Pagein, touching first byte of each memory page.
         * Note: volatile disables optimizing the loop away.
         */
        BYTE volatile * pData(static_cast<BYTE*>(*ppAddr));
        SIZE_T nSize(nLength);

        while (nSize > dwPageSize)
        {
            pData[0];
            pData += dwPageSize;
            nSize -= dwPageSize;
        }
        if (nSize > 0)
        {
            pData[0];
        }
    }
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_unmapFile(void* pAddr, sal_uInt64 /* uLength */)
{
    if (!pAddr)
        return osl_File_E_INVAL;

    if (!::UnmapViewOfFile(pAddr))
        return oslTranslateFileError(GetLastError());

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_unmapMappedFile(oslFileHandle /* Handle */, void* pAddr, sal_uInt64 uLength)
{
    return osl_unmapFile(pAddr, uLength);
}

oslFileError
SAL_CALL osl_readLine(
    oslFileHandle   Handle,
    sal_Sequence ** ppSequence)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!ppSequence))
        return osl_File_E_INVAL;
    sal_uInt64 uBytesRead = 0;

    // read at current filepos; filepos += uBytesRead;
    std::lock_guard lock(pImpl->m_mutex);
    oslFileError result = pImpl->readLineAt(
        pImpl->m_filepos, ppSequence, &uBytesRead);
    if (result == osl_File_E_None)
        pImpl->m_filepos += uBytesRead;
    return result;
}

oslFileError SAL_CALL osl_readFile(
    oslFileHandle Handle,
    void *        pBuffer,
    sal_uInt64    uBytesRequested,
    sal_uInt64 *  pBytesRead)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesRead))
        return osl_File_E_INVAL;

    // read at current filepos; filepos += *pBytesRead;
    std::lock_guard lock(pImpl->m_mutex);
    oslFileError result = pImpl->readFileAt(
        pImpl->m_filepos, pBuffer, uBytesRequested, pBytesRead);
    if (result == osl_File_E_None)
        pImpl->m_filepos += *pBytesRead;
    return result;
}

oslFileError SAL_CALL osl_writeFile(
    oslFileHandle Handle,
    const void *  pBuffer,
    sal_uInt64    uBytesToWrite,
    sal_uInt64 *  pBytesWritten)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesWritten))
        return osl_File_E_INVAL;

    // write at current filepos; filepos += *pBytesWritten;
    std::lock_guard lock(pImpl->m_mutex);
    oslFileError result = pImpl->writeFileAt(
        pImpl->m_filepos, pBuffer, uBytesToWrite, pBytesWritten);
    if (result == osl_File_E_None)
        pImpl->m_filepos += *pBytesWritten;
    return result;
}

LONGLONG const g_limit_longlong = std::numeric_limits< LONGLONG >::max();

namespace {

template<typename T> bool exceedsMaxLONGLONG(T n)
{ return n > g_limit_longlong; }

template<typename T> bool exceedsMinLONGLONG(T n)
{ return n < std::numeric_limits<LONGLONG>::min(); }

}

oslFileError SAL_CALL osl_readFileAt(
    oslFileHandle Handle,
    sal_uInt64    uOffset,
    void*         pBuffer,
    sal_uInt64    uBytesRequested,
    sal_uInt64*   pBytesRead)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesRead))
        return osl_File_E_INVAL;
    if (!(pImpl->m_state & StateBits::Seekable))
        return osl_File_E_SPIPE;

    if (exceedsMaxLONGLONG(uOffset))
        return osl_File_E_OVERFLOW;
    LONGLONG const nOffset = sal::static_int_cast< LONGLONG >(uOffset);

    // read at specified fileptr
    std::lock_guard lock (pImpl->m_mutex);
    return pImpl->readFileAt(nOffset, pBuffer, uBytesRequested, pBytesRead);
}

oslFileError SAL_CALL osl_writeFileAt(
    oslFileHandle Handle,
    sal_uInt64    uOffset,
    const void*   pBuffer,
    sal_uInt64    uBytesToWrite,
    sal_uInt64*   pBytesWritten)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pBuffer) || (!pBytesWritten))
        return osl_File_E_INVAL;
    if (!(pImpl->m_state & StateBits::Seekable))
        return osl_File_E_SPIPE;

    if (exceedsMaxLONGLONG(uOffset))
        return osl_File_E_OVERFLOW;
    LONGLONG const nOffset = sal::static_int_cast< LONGLONG >(uOffset);

    // write at specified fileptr
    std::lock_guard lock(pImpl->m_mutex);
    return pImpl->writeFileAt(nOffset, pBuffer, uBytesToWrite, pBytesWritten);
}

oslFileError SAL_CALL osl_isEndOfFile(oslFileHandle Handle, sal_Bool *pIsEOF)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pIsEOF))
        return osl_File_E_INVAL;

    std::lock_guard lock(pImpl->m_mutex);
    *pIsEOF = (pImpl->getPos() == pImpl->getSize());
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_getFilePos(oslFileHandle Handle, sal_uInt64 *pPos)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pPos))
        return osl_File_E_INVAL;

    // no need to lock because pos is atomic
    *pPos = pImpl->getPos();

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFilePos(oslFileHandle Handle, sal_uInt32 uHow, sal_Int64 uOffset)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);
    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile))
        return osl_File_E_INVAL;

    if (exceedsMaxLONGLONG(uOffset) || exceedsMinLONGLONG(uOffset))
        return osl_File_E_OVERFLOW;
    LONGLONG nPos = 0, nOffset = sal::static_int_cast< LONGLONG >(uOffset);

    std::lock_guard lock(pImpl->m_mutex);
    switch (uHow)
    {
        case osl_Pos_Absolut:
            if (nOffset < 0)
                return osl_File_E_INVAL;
            break;

        case osl_Pos_Current:
            nPos = sal::static_int_cast< LONGLONG >(pImpl->getPos());
            if ((nOffset < 0) && (nPos < -1*nOffset))
                return osl_File_E_INVAL;
            assert(nPos >= 0);
            if (nOffset > g_limit_longlong - nPos)
                return osl_File_E_OVERFLOW;
            break;

        case osl_Pos_End:
            nPos = sal::static_int_cast< LONGLONG >(pImpl->getSize());
            if ((nOffset < 0) && (nPos < -1*nOffset))
                return osl_File_E_INVAL;
            assert(nPos >= 0);
            if (nOffset > g_limit_longlong - nPos)
                return osl_File_E_OVERFLOW;
            break;

        default:
            return osl_File_E_INVAL;
    }

    return pImpl->setPos(nPos + nOffset);
}

oslFileError SAL_CALL osl_getFileSize(oslFileHandle Handle, sal_uInt64 *pSize)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile) || (!pSize))
        return osl_File_E_INVAL;

    std::lock_guard lock(pImpl->m_mutex);
    *pSize = pImpl->getSize();
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFileSize(oslFileHandle Handle, sal_uInt64 uSize)
{
    FileHandle_Impl * pImpl = static_cast<FileHandle_Impl*>(Handle);

    if ((!pImpl) || !IsValidHandle(pImpl->m_hFile))
        return osl_File_E_INVAL;
    if (!(pImpl->m_state & StateBits::Writeable))
        return osl_File_E_BADF;

    if (exceedsMaxLONGLONG(uSize))
        return osl_File_E_OVERFLOW;

    std::lock_guard lock(pImpl->m_mutex);
    oslFileError result = pImpl->syncFile();
    if (result != osl_File_E_None)
        return result;
    pImpl->m_bufptr = -1;
    pImpl->m_buflen = 0;

    return pImpl->setSize(uSize);
}

oslFileError SAL_CALL osl_removeFile(rtl_uString* strPath)
{
    OUString strSysPath;
    oslFileError    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if (error == osl_File_E_None)
    {
        if (DeleteFileW(o3tl::toW(strSysPath.getStr())))
            error = osl_File_E_None;
        else
            error = oslTranslateFileError(GetLastError());
    }
    return error;
}

oslFileError SAL_CALL osl_copyFile(rtl_uString* strPath, rtl_uString *strDestPath)
{
    OUString strSysPath, strSysDestPath;
    oslFileError    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if (error == osl_File_E_None)
        error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false);

    if (error == osl_File_E_None)
    {
        LPCWSTR src = o3tl::toW(strSysPath.getStr());
        LPCWSTR dst = o3tl::toW(strSysDestPath.getStr());

        if (CopyFileW(src, dst, FALSE))
            error = osl_File_E_None;
        else
            error = oslTranslateFileError(GetLastError());
    }

    return error;
}

oslFileError SAL_CALL osl_moveFile(rtl_uString* strPath, rtl_uString *strDestPath)
{
    OUString strSysPath, strSysDestPath;
    oslFileError    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if (error == osl_File_E_None)
        error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false);

    if (error == osl_File_E_None)
    {
        LPCWSTR src = o3tl::toW(strSysPath.getStr());
        LPCWSTR dst = o3tl::toW(strSysDestPath.getStr());

        if (MoveFileExW(src, dst, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING))
            error = osl_File_E_None;
        else
            error = oslTranslateFileError(GetLastError());
    }

    return error;
}

oslFileError SAL_CALL osl_replaceFile(rtl_uString* strPath, rtl_uString* strDestPath)
{
    OUString strSysPath, strSysDestPath;
    oslFileError    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if (error == osl_File_E_None)
        error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDestPath), &strSysDestPath.pData, false);

    if (error == osl_File_E_None)
    {
        LPCWSTR src = o3tl::toW(strSysPath.getStr());
        LPCWSTR dst = o3tl::toW(strSysDestPath.getStr());

        if (!ReplaceFileW(dst, src, nullptr,
                          REPLACEFILE_WRITE_THROUGH | REPLACEFILE_IGNORE_MERGE_ERRORS
                              | REPLACEFILE_IGNORE_ACL_ERRORS,
                          nullptr, nullptr))
        {
            DWORD dwError = GetLastError();
            if (dwError == ERROR_FILE_NOT_FOUND // no strDestPath file?
                || dwError == ERROR_UNABLE_TO_MOVE_REPLACEMENT // e.g., files on different volumes
                || dwError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2
                || dwError == ERROR_UNABLE_TO_REMOVE_REPLACED)
                error = osl_moveFile(strPath, strDestPath);
            else
                error = oslTranslateFileError(dwError);
        }
    }

    return error;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
