/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MIO_MMAP_HEADER
#define MIO_MMAP_HEADER

// #include "mio/page.hpp"
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MIO_PAGE_HEADER
    #define MIO_PAGE_HEADER

    #ifdef _WIN32
        #include <windows.h>
    #else
        #include <unistd.h>
    #endif

namespace mio {

/**
 * This is used by `basic_mmap` to determine whether to create a read-only or
 * a read-write memory mapping.
 */
enum class access_mode {
    read,
    write
};

/**
 * Determines the operating system's page allocation granularity.
 *
 * On the first call to this function, it invokes the operating system specific syscall
 * to determine the page size, caches the value, and returns it. Any subsequent call to
 * this function serves the cached value, so no further syscalls are made.
 */
inline size_t page_size() {
    static const size_t page_size = [] {
    #ifdef _WIN32
        SYSTEM_INFO SystemInfo;
        GetSystemInfo(&SystemInfo);
        return SystemInfo.dwAllocationGranularity;
    #else
        return sysconf(_SC_PAGE_SIZE);
    #endif
    }();
    return page_size;
}

/**
 * Alligns `offset` to the operating's system page size such that it subtracts the
 * difference until the nearest page boundary before `offset`, or does nothing if
 * `offset` is already page aligned.
 */
inline size_t make_offset_page_aligned(size_t offset) noexcept {
    const size_t page_size_ = page_size();
    // Use integer division to round down to the nearest page alignment.
    return offset / page_size_ * page_size_;
}

}  // namespace mio

#endif  // MIO_PAGE_HEADER

#include <cstdint>
#include <iterator>
#include <string>
#include <system_error>

#ifdef _WIN32
    #ifndef WIN32_LEAN_AND_MEAN
        #define WIN32_LEAN_AND_MEAN
    #endif  // WIN32_LEAN_AND_MEAN
    #include <windows.h>
#else  // ifdef _WIN32
    #define INVALID_HANDLE_VALUE -1
#endif  // ifdef _WIN32

namespace mio {

// This value may be provided as the `length` parameter to the constructor or
// `map`, in which case a memory mapping of the entire file is created.
enum {
    map_entire_file = 0
};

#ifdef _WIN32
using file_handle_type = HANDLE;
#else
using file_handle_type = int;
#endif

// This value represents an invalid file handle type. This can be used to
// determine whether `basic_mmap::file_handle` is valid, for example.
const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;

template<access_mode AccessMode, typename ByteT>
struct basic_mmap {
    using value_type             = ByteT;
    using size_type              = size_t;
    using reference              = value_type&;
    using const_reference        = const value_type&;
    using pointer                = value_type*;
    using const_pointer          = const value_type*;
    using difference_type        = std::ptrdiff_t;
    using iterator               = pointer;
    using const_iterator         = const_pointer;
    using reverse_iterator       = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
    using iterator_category      = std::random_access_iterator_tag;
    using handle_type            = file_handle_type;

    static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char.");

   private:

    // Points to the first requested byte, and not to the actual start of the mapping.
    pointer data_ = nullptr;

    // Length--in bytes--requested by user (which may not be the length of the
    // full mapping) and the length of the full mapping.
    size_type length_        = 0;
    size_type mapped_length_ = 0;

    // Letting user map a file using both an existing file handle and a path
    // introcudes some complexity (see `is_handle_internal_`).
    // On POSIX, we only need a file handle to create a mapping, while on
    // Windows systems the file handle is necessary to retrieve a file mapping
    // handle, but any subsequent operations on the mapped region must be done
    // through the latter.
    handle_type file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
    handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif

    // Letting user map a file using both an existing file handle and a path
    // introcudes some complexity in that we must not close the file handle if
    // user provided it, but we must close it if we obtained it using the
    // provided path. For this reason, this flag is used to determine when to
    // close `file_handle_`.
    bool is_handle_internal_;

   public:

    /**
     * The default constructed mmap object is in a non-mapped state, that is,
     * any operation that attempts to access nonexistent underlying data will
     * result in undefined behaviour/segmentation faults.
     */
    basic_mmap() = default;

#ifdef __cpp_exceptions
    /**
     * The same as invoking the `map` function, except any error that may occur
     * while establishing the mapping is wrapped in a `std::system_error` and is
     * thrown.
     */
    template<typename String>
    basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) {
        std::error_code error;
        map(path, offset, length, error);
        if (error) {
            throw std::system_error(error);
        }
    }

    /**
     * The same as invoking the `map` function, except any error that may occur
     * while establishing the mapping is wrapped in a `std::system_error` and is
     * thrown.
     */
    basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) {
        std::error_code error;
        map(handle, offset, length, error);
        if (error) {
            throw std::system_error(error);
        }
    }
#endif  // __cpp_exceptions

    /**
     * `basic_mmap` has single-ownership semantics, so transferring ownership
     * may only be accomplished by moving the object.
     */
    basic_mmap(const basic_mmap&) = delete;
    basic_mmap(basic_mmap&&);
    basic_mmap& operator=(const basic_mmap&) = delete;
    basic_mmap& operator=(basic_mmap&&);

    /**
     * If this is a read-write mapping, the destructor invokes sync. Regardless
     * of the access mode, unmap is invoked as a final step.
     */
    ~basic_mmap();

    /**
     * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
     * however, a mapped region of a file gets its own handle, which is returned by
     * 'mapping_handle'.
     */
    handle_type file_handle() const noexcept {
        return file_handle_;
    }

    handle_type mapping_handle() const noexcept;

    /** Returns whether a valid memory mapping has been created. */
    bool is_open() const noexcept {
        return file_handle_ != invalid_handle;
    }

    /**
     * Returns true if no mapping was established, that is, conceptually the
     * same as though the length that was mapped was 0. This function is
     * provided so that this class has Container semantics.
     */
    bool empty() const noexcept {
        return length() == 0;
    }

    /** Returns true if a mapping was established. */
    bool is_mapped() const noexcept;

    /**
     * `size` and `length` both return the logical length, i.e. the number of bytes
     * user requested to be mapped, while `mapped_length` returns the actual number of
     * bytes that were mapped which is a multiple of the underlying operating system's
     * page allocation granularity.
     */
    size_type size() const noexcept {
        return length();
    }

    size_type length() const noexcept {
        return length_;
    }

    size_type mapped_length() const noexcept {
        return mapped_length_;
    }

    /** Returns the offset relative to the start of the mapping. */
    size_type mapping_offset() const noexcept {
        return mapped_length_ - length_;
    }

    /**
     * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
     * exists.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    pointer data() noexcept {
        return data_;
    }

    const_pointer data() const noexcept {
        return data_;
    }

    /**
     * Returns an iterator to the first requested byte, if a valid memory mapping
     * exists, otherwise this function call is undefined behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    iterator begin() noexcept {
        return data();
    }

    const_iterator begin() const noexcept {
        return data();
    }

    const_iterator cbegin() const noexcept {
        return data();
    }

    /**
     * Returns an iterator one past the last requested byte, if a valid memory mapping
     * exists, otherwise this function call is undefined behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    iterator end() noexcept {
        return data() + length();
    }

    const_iterator end() const noexcept {
        return data() + length();
    }

    const_iterator cend() const noexcept {
        return data() + length();
    }

    /**
     * Returns a reverse iterator to the last memory mapped byte, if a valid
     * memory mapping exists, otherwise this function call is undefined
     * behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    reverse_iterator rbegin() noexcept {
        return reverse_iterator(end());
    }

    const_reverse_iterator rbegin() const noexcept {
        return const_reverse_iterator(end());
    }

    const_reverse_iterator crbegin() const noexcept {
        return const_reverse_iterator(end());
    }

    /**
     * Returns a reverse iterator past the first mapped byte, if a valid memory
     * mapping exists, otherwise this function call is undefined behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    reverse_iterator rend() noexcept {
        return reverse_iterator(begin());
    }

    const_reverse_iterator rend() const noexcept {
        return const_reverse_iterator(begin());
    }

    const_reverse_iterator crend() const noexcept {
        return const_reverse_iterator(begin());
    }

    /**
     * Returns a reference to the `i`th byte from the first requested byte (as returned
     * by `data`). If this is invoked when no valid memory mapping has been created
     * prior to this call, undefined behaviour ensues.
     */
    reference operator[](const size_type i) noexcept {
        return data_[i];
    }

    const_reference operator[](const size_type i) const noexcept {
        return data_[i];
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `path`, which must be a path to an existing file, is used to retrieve a file
     * handle (which is closed when the object destructs or `unmap` is called), which is
     * then used to memory map the requested region. Upon failure, `error` is set to
     * indicate the reason and the object remains in an unmapped state.
     *
     * `offset` is the number of bytes, relative to the start of the file, where the
     * mapping should begin. When specifying it, there is no need to worry about
     * providing a value that is aligned with the operating system's page allocation
     * granularity. This is adjusted by the implementation such that the first requested
     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
     * `offset` from the start of the file.
     *
     * `length` is the number of bytes to map. It may be `map_entire_file`, in which
     * case a mapping of the entire file is created.
     */
    template<typename String>
    void map(const String& path, const size_type offset, const size_type length, std::error_code& error);

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `path`, which must be a path to an existing file, is used to retrieve a file
     * handle (which is closed when the object destructs or `unmap` is called), which is
     * then used to memory map the requested region. Upon failure, `error` is set to
     * indicate the reason and the object remains in an unmapped state.
     *
     * The entire file is mapped.
     */
    template<typename String>
    void map(const String& path, std::error_code& error) {
        map(path, 0, map_entire_file, error);
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is
     * unsuccesful, the reason is reported via `error` and the object remains in
     * a state as if this function hadn't been called.
     *
     * `handle`, which must be a valid file handle, which is used to memory map the
     * requested region. Upon failure, `error` is set to indicate the reason and the
     * object remains in an unmapped state.
     *
     * `offset` is the number of bytes, relative to the start of the file, where the
     * mapping should begin. When specifying it, there is no need to worry about
     * providing a value that is aligned with the operating system's page allocation
     * granularity. This is adjusted by the implementation such that the first requested
     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
     * `offset` from the start of the file.
     *
     * `length` is the number of bytes to map. It may be `map_entire_file`, in which
     * case a mapping of the entire file is created.
     */
    void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error);

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is
     * unsuccesful, the reason is reported via `error` and the object remains in
     * a state as if this function hadn't been called.
     *
     * `handle`, which must be a valid file handle, which is used to memory map the
     * requested region. Upon failure, `error` is set to indicate the reason and the
     * object remains in an unmapped state.
     *
     * The entire file is mapped.
     */
    void map(const handle_type handle, std::error_code& error) {
        map(handle, 0, map_entire_file, error);
    }

    /**
     * If a valid memory mapping has been created prior to this call, this call
     * instructs the kernel to unmap the memory region and disassociate this object
     * from the file.
     *
     * The file handle associated with the file that is mapped is only closed if the
     * mapping was created using a file path. If, on the other hand, an existing
     * file handle was used to create the mapping, the file handle is not closed.
     */
    void unmap();

    void swap(basic_mmap& other);

    /** Flushes the memory mapped page to disk. Errors are reported via `error`. */
    template<access_mode A = AccessMode>
    typename std::enable_if<A == access_mode::write, void>::type sync(std::error_code& error);

    /**
     * All operators compare the address of the first byte and size of the two mapped
     * regions.
     */

   private:

    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    pointer get_mapping_start() noexcept {
        return !data() ? nullptr : data() - mapping_offset();
    }

    const_pointer get_mapping_start() const noexcept {
        return !data() ? nullptr : data() - mapping_offset();
    }

    /**
     * The destructor syncs changes to disk if `AccessMode` is `write`, but not
     * if it's `read`, but since the destructor cannot be templated, we need to
     * do SFINAE in a dedicated function, where one syncs and the other is a noop.
     */
    template<access_mode A = AccessMode>
    typename std::enable_if<A == access_mode::write, void>::type conditional_sync();
    template<access_mode A = AccessMode>
    typename std::enable_if<A == access_mode::read, void>::type conditional_sync();
};

template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b);

/**
 * This is the basis for all read-only mmap objects and should be preferred over
 * directly using `basic_mmap`.
 */
template<typename ByteT>
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;

/**
 * This is the basis for all read-write mmap objects and should be preferred over
 * directly using `basic_mmap`.
 */
template<typename ByteT>
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;

/**
 * These aliases cover the most common use cases, both representing a raw byte stream
 * (either with a char or an unsigned char/uint8_t).
 */
using mmap_source  = basic_mmap_source<char>;
using ummap_source = basic_mmap_source<unsigned char>;

using mmap_sink  = basic_mmap_sink<char>;
using ummap_sink = basic_mmap_sink<unsigned char>;

/**
 * Convenience factory method that constructs a mapping for any `basic_mmap` or
 * `basic_mmap` type.
 */
template< typename MMap, typename MappingToken >
MMap make_mmap(const MappingToken& token, int64_t offset, int64_t length, std::error_code& error) {
    MMap mmap;
    mmap.map(token, offset, length, error);
    return mmap;
}

/**
 * Convenience factory method.
 *
 * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
 * `std::filesystem::path`, `std::vector<char>`, or similar), or a
 * `mmap_source::handle_type`.
 */
template<typename MappingToken>
mmap_source make_mmap_source(
    const MappingToken& token, mmap_source::size_type offset, mmap_source::size_type length, std::error_code& error
) {
    return make_mmap<mmap_source>(token, offset, length, error);
}

template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) {
    return make_mmap_source(token, 0, map_entire_file, error);
}

/**
 * Convenience factory method.
 *
 * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
 * `std::filesystem::path`, `std::vector<char>`, or similar), or a
 * `mmap_sink::handle_type`.
 */
template<typename MappingToken>
mmap_sink make_mmap_sink(
    const MappingToken& token, mmap_sink::size_type offset, mmap_sink::size_type length, std::error_code& error
) {
    return make_mmap<mmap_sink>(token, offset, length, error);
}

template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) {
    return make_mmap_sink(token, 0, map_entire_file, error);
}

}  // namespace mio

// #include "detail/mmap.ipp"
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MIO_BASIC_MMAP_IMPL
    #define MIO_BASIC_MMAP_IMPL

    // #include "mio/mmap.hpp"

    // #include "mio/page.hpp"

    // #include "mio/detail/string_util.hpp"
    /* Copyright 2017 https://github.com/mandreyel
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of this
     * software and associated documentation files (the "Software"), to deal in the Software
     * without restriction, including without limitation the rights to use, copy, modify,
     * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
     * permit persons to whom the Software is furnished to do so, subject to the following
     * conditions:
     *
     * The above copyright notice and this permission notice shall be included in all copies
     * or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
     * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
     * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
     * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
     * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
     * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     */

    #ifndef MIO_STRING_UTIL_HEADER
        #define MIO_STRING_UTIL_HEADER

        #include <type_traits>

namespace mio {
namespace detail {

    template<
        typename S, typename C = typename std::decay<S>::type, typename = decltype(std::declval<C>().data()),
        typename = typename std::enable_if<
            std::is_same<typename C::value_type, char>::value
        #ifdef _WIN32
            || std::is_same<typename C::value_type, wchar_t>::value
        #endif
            >::type >
    struct char_type_helper {
        using type = typename C::value_type;
    };

    template<class T>
    struct char_type {
        using type = typename char_type_helper<T>::type;
    };

    // TODO: can we avoid this brute force approach?
    template<>
    struct char_type<char*> {
        using type = char;
    };

    template<>
    struct char_type<const char*> {
        using type = char;
    };

    template<size_t N>
    struct char_type<char[N]> {
        using type = char;
    };

    template<size_t N>
    struct char_type<const char[N]> {
        using type = char;
    };

        #ifdef _WIN32
    template<>
    struct char_type<wchar_t*> {
        using type = wchar_t;
    };

    template<>
    struct char_type<const wchar_t*> {
        using type = wchar_t;
    };

    template<size_t N>
    struct char_type<wchar_t[N]> {
        using type = wchar_t;
    };

    template<size_t N>
    struct char_type<const wchar_t[N]> {
        using type = wchar_t;
    };
        #endif  // _WIN32

    template<typename CharT, typename S>
    struct is_c_str_helper {
        static constexpr bool value = std::is_same<
            CharT*,
            // TODO: I'm so sorry for this... Can this be made cleaner?
            typename std::add_pointer< typename std::remove_cv<
                typename std::remove_pointer< typename std::decay< S >::type >::type >::type >::type >::value;
    };

    template<typename S>
    struct is_c_str {
        static constexpr bool value = is_c_str_helper<char, S>::value;
    };

        #ifdef _WIN32
    template<typename S>
    struct is_c_wstr {
        static constexpr bool value = is_c_str_helper<wchar_t, S>::value;
    };
        #endif  // _WIN32

    template<typename S>
    struct is_c_str_or_c_wstr {
        static constexpr bool value = is_c_str<S>::value
        #ifdef _WIN32
                                      || is_c_wstr<S>::value
        #endif
            ;
    };

    template<
        typename String, typename = decltype(std::declval<String>().data()),
        typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type >
    const typename char_type<String>::type* c_str(const String& path) {
        return path.data();
    }

    template<
        typename String, typename = decltype(std::declval<String>().empty()),
        typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type >
    bool empty(const String& path) {
        return path.empty();
    }

    template< typename String, typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type >
    const typename char_type<String>::type* c_str(String path) {
        return path;
    }

    template< typename String, typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type >
    bool empty(String path) {
        return !path || (*path == 0);
    }

}  // namespace detail
}  // namespace mio

    #endif  // MIO_STRING_UTIL_HEADER

    #include <algorithm>

    #ifndef _WIN32
        #include <fcntl.h>
        #include <sys/mman.h>
        #include <sys/stat.h>
        #include <unistd.h>
    #endif

namespace mio {
namespace detail {

    #ifdef _WIN32
    namespace win {

        /** Returns the 4 upper bytes of an 8-byte integer. */
        inline DWORD int64_high(int64_t n) noexcept {
            return n >> 32;
        }

        /** Returns the 4 lower bytes of an 8-byte integer. */
        inline DWORD int64_low(int64_t n) noexcept {
            return n & 0xffffffff;
        }

        std::wstring s_2_ws(const std::string& s) {
            if (s.empty())
                return {};
            const auto s_length        = static_cast<int>(s.length());
            auto       buf             = std::vector<wchar_t>(s_length);
            const auto wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length);
            return std::wstring(buf.data(), wide_char_count);
        }

        template<
            typename String,
            typename = typename std::enable_if< std::is_same<typename char_type<String>::type, char>::value >::type >
        file_handle_type open_file_helper(const String& path, const access_mode mode) {
            return ::CreateFileW(
                s_2_ws(path).c_str(), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
            );
        }

        template<typename String>
        typename std::enable_if< std::is_same<typename char_type<String>::type, wchar_t>::value, file_handle_type >::type
        open_file_helper(const String& path, const access_mode mode) {
            return ::CreateFileW(
                c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
            );
        }

    }  // namespace win
    #endif  // _WIN32

    /**
     * Returns the last platform specific system error (errno on POSIX and
     * GetLastError on Win) as a `std::error_code`.
     */
    inline std::error_code last_error() noexcept {
        std::error_code error;
    #ifdef _WIN32
        error.assign(GetLastError(), std::system_category());
    #else
        error.assign(errno, std::system_category());
    #endif
        return error;
    }

    template<typename String>
    file_handle_type open_file(const String& path, const access_mode mode, std::error_code& error) {
        error.clear();
        if (detail::empty(path)) {
            error = std::make_error_code(std::errc::invalid_argument);
            return invalid_handle;
        }
    #ifdef _WIN32
        const auto handle = win::open_file_helper(path, mode);
    #else  // POSIX
        const auto handle = ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR);
    #endif
        if (handle == invalid_handle) {
            error = detail::last_error();
        }
        return handle;
    }

    inline size_t query_file_size(file_handle_type handle, std::error_code& error) {
        error.clear();
    #ifdef _WIN32
        LARGE_INTEGER file_size;
        if (::GetFileSizeEx(handle, &file_size) == 0) {
            error = detail::last_error();
            return 0;
        }
        return static_cast<int64_t>(file_size.QuadPart);
    #else  // POSIX
        struct stat sbuf;
        if (::fstat(handle, &sbuf) == -1) {
            error = detail::last_error();
            return 0;
        }
        return sbuf.st_size;
    #endif
    }

    struct mmap_context {
        char*   data;
        int64_t length;
        int64_t mapped_length;
    #ifdef _WIN32
        file_handle_type file_mapping_handle;
    #endif
    };

    inline mmap_context memory_map(
        const file_handle_type file_handle, const int64_t offset, const int64_t length, const access_mode mode,
        std::error_code& error
    ) {
        const int64_t aligned_offset = make_offset_page_aligned(offset);
        const int64_t length_to_map  = offset - aligned_offset + length;
    #ifdef _WIN32
        const int64_t max_file_size       = offset + length;
        const auto    file_mapping_handle = ::CreateFileMapping(
            file_handle, 0, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, win::int64_high(max_file_size),
            win::int64_low(max_file_size), 0
        );
        if (file_mapping_handle == invalid_handle) {
            error = detail::last_error();
            return {};
        }
        char* mapping_start = static_cast<char*>(::MapViewOfFile(
            file_mapping_handle, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, win::int64_high(aligned_offset),
            win::int64_low(aligned_offset), length_to_map
        ));
        if (mapping_start == nullptr) {
            // Close file handle if mapping it failed.
            ::CloseHandle(file_mapping_handle);
            error = detail::last_error();
            return {};
        }
    #else  // POSIX
        char* mapping_start = static_cast<char*>(::mmap(
            0,  // Don't give hint as to where to map.
            length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, MAP_SHARED, file_handle, aligned_offset
        ));
        if (mapping_start == MAP_FAILED) {
            error = detail::last_error();
            return {};
        }
    #endif
        mmap_context ctx;
        ctx.data          = mapping_start + offset - aligned_offset;
        ctx.length        = length;
        ctx.mapped_length = length_to_map;
    #ifdef _WIN32
        ctx.file_mapping_handle = file_mapping_handle;
    #endif
        return ctx;
    }

}  // namespace detail

// -- basic_mmap --

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::~basic_mmap() {
    conditional_sync();
    unmap();
}

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other) :
    data_(std::move(other.data_)),
    length_(std::move(other.length_)),
    mapped_length_(std::move(other.mapped_length_)),
    file_handle_(std::move(other.file_handle_))
    #ifdef _WIN32
    ,
    file_mapping_handle_(std::move(other.file_mapping_handle_))
    #endif
    ,
    is_handle_internal_(std::move(other.is_handle_internal_)) {
    other.data_   = nullptr;
    other.length_ = other.mapped_length_ = 0;
    other.file_handle_                   = invalid_handle;
    #ifdef _WIN32
    other.file_mapping_handle_ = invalid_handle;
    #endif
}

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>& basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other) {
    if (this != &other) {
        // First the existing mapping needs to be removed.
        unmap();
        data_          = std::move(other.data_);
        length_        = std::move(other.length_);
        mapped_length_ = std::move(other.mapped_length_);
        file_handle_   = std::move(other.file_handle_);
    #ifdef _WIN32
        file_mapping_handle_ = std::move(other.file_mapping_handle_);
    #endif
        is_handle_internal_ = std::move(other.is_handle_internal_);

        // The moved from basic_mmap's fields need to be reset, because
        // otherwise other's destructor will unmap the same mapping that was
        // just moved into this.
        other.data_   = nullptr;
        other.length_ = other.mapped_length_ = 0;
        other.file_handle_                   = invalid_handle;
    #ifdef _WIN32
        other.file_mapping_handle_ = invalid_handle;
    #endif
        other.is_handle_internal_ = false;
    }
    return *this;
}

template<access_mode AccessMode, typename ByteT>
typename basic_mmap<AccessMode, ByteT>::handle_type basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept {
    #ifdef _WIN32
    return file_mapping_handle_;
    #else
    return file_handle_;
    #endif
}

template<access_mode AccessMode, typename ByteT>
template<typename String>
void basic_mmap<AccessMode, ByteT>::map(
    const String& path, const size_type offset, const size_type length, std::error_code& error
) {
    error.clear();
    if (detail::empty(path)) {
        error = std::make_error_code(std::errc::invalid_argument);
        return;
    }
    const auto handle = detail::open_file(path, AccessMode, error);
    if (error) {
        return;
    }

    map(handle, offset, length, error);
    // This MUST be after the call to map, as that sets this to true.
    if (!error) {
        is_handle_internal_ = true;
    }
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::map(
    const handle_type handle, const size_type offset, const size_type length, std::error_code& error
) {
    error.clear();
    if (handle == invalid_handle) {
        error = std::make_error_code(std::errc::bad_file_descriptor);
        return;
    }

    const auto file_size = detail::query_file_size(handle, error);
    if (error) {
        return;
    }

    if (offset + length > file_size) {
        error = std::make_error_code(std::errc::invalid_argument);
        return;
    }

    const auto ctx =
        detail::memory_map(handle, offset, length == map_entire_file ? (file_size - offset) : length, AccessMode, error);
    if (!error) {
        // We must unmap the previous mapping that may have existed prior to this call.
        // Note that this must only be invoked after a new mapping has been created in
        // order to provide the strong guarantee that, should the new mapping fail, the
        // `map` function leaves this instance in a state as though the function had
        // never been invoked.
        unmap();
        file_handle_        = handle;
        is_handle_internal_ = false;
        data_               = std::bit_cast<pointer>(ctx.data);
        length_             = ctx.length;
        mapped_length_      = ctx.mapped_length;
    #ifdef _WIN32
        file_mapping_handle_ = ctx.file_mapping_handle;
    #endif
    }
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type basic_mmap<AccessMode, ByteT>::sync(std::error_code& error) {
    error.clear();
    if (!is_open()) {
        error = std::make_error_code(std::errc::bad_file_descriptor);
        return;
    }

    if (data()) {
    #ifdef _WIN32
        if (::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0)
    #else  // POSIX
        if (::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
    #endif
        {
            error = detail::last_error();
            return;
        }
    }
    #ifdef _WIN32
    if (::FlushFileBuffers(file_handle_) == 0) {
        error = detail::last_error();
    }
    #endif
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::unmap() {
    if (!is_open()) {
        return;
    }
        // TODO do we care about errors here?
    #ifdef _WIN32
    if (is_mapped()) {
        ::UnmapViewOfFile(get_mapping_start());
        ::CloseHandle(file_mapping_handle_);
    }
    #else  // POSIX
    if (data_) {
        ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_);
    }
    #endif

    // If `file_handle_` was obtained by our opening it (when map is called with
    // a path, rather than an existing file handle), we need to close it,
    // otherwise it must not be closed as it may still be used outside this
    // instance.
    if (is_handle_internal_) {
    #ifdef _WIN32
        ::CloseHandle(file_handle_);
    #else  // POSIX
        ::close(file_handle_);
    #endif
    }

    // Reset fields to their default values.
    data_   = nullptr;
    length_ = mapped_length_ = 0;
    file_handle_             = invalid_handle;
    #ifdef _WIN32
    file_mapping_handle_ = invalid_handle;
    #endif
}

template<access_mode AccessMode, typename ByteT>
bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept {
    #ifdef _WIN32
    return file_mapping_handle_ != invalid_handle;
    #else  // POSIX
    return is_open();
    #endif
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other) {
    if (this != &other) {
        using std::swap;
        swap(data_, other.data_);
        swap(file_handle_, other.file_handle_);
    #ifdef _WIN32
        swap(file_mapping_handle_, other.file_mapping_handle_);
    #endif
        swap(length_, other.length_);
        swap(mapped_length_, other.mapped_length_);
        swap(is_handle_internal_, other.is_handle_internal_);
    }
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type basic_mmap<AccessMode, ByteT>::conditional_sync() {
    // This is invoked from the destructor, so not much we can do about
    // failures here.
    std::error_code ec;
    sync(ec);
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::read, void>::type basic_mmap<AccessMode, ByteT>::conditional_sync() {
    // noop
}

template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    return a.data() == b.data() && a.size() == b.size();
}

template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    return !(a == b);
}

template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    if (a.data() == b.data()) {
        return a.size() < b.size();
    }
    return a.data() < b.data();
}

template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    return !(a > b);
}

template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    if (a.data() == b.data()) {
        return a.size() > b.size();
    }
    return a.data() > b.data();
}

template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) {
    return !(a < b);
}

}  // namespace mio

#endif  // MIO_BASIC_MMAP_IMPL

#endif  // MIO_MMAP_HEADER
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MIO_PAGE_HEADER
#define MIO_PAGE_HEADER

#ifdef _WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

namespace mio {

/**
 * This is used by `basic_mmap` to determine whether to create a read-only or
 * a read-write memory mapping.
 */
enum class access_mode {
    read,
    write
};

/**
 * Determines the operating system's page allocation granularity.
 *
 * On the first call to this function, it invokes the operating system specific syscall
 * to determine the page size, caches the value, and returns it. Any subsequent call to
 * this function serves the cached value, so no further syscalls are made.
 */
inline size_t page_size() {
    static const size_t page_size = [] {
#ifdef _WIN32
        SYSTEM_INFO SystemInfo;
        GetSystemInfo(&SystemInfo);
        return SystemInfo.dwAllocationGranularity;
#else
        return sysconf(_SC_PAGE_SIZE);
#endif
    }();
    return page_size;
}

/**
 * Alligns `offset` to the operating's system page size such that it subtracts the
 * difference until the nearest page boundary before `offset`, or does nothing if
 * `offset` is already page aligned.
 */
inline size_t make_offset_page_aligned(size_t offset) noexcept {
    const size_t page_size_ = page_size();
    // Use integer division to round down to the nearest page alignment.
    return offset / page_size_ * page_size_;
}

}  // namespace mio

#endif  // MIO_PAGE_HEADER
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MIO_SHARED_MMAP_HEADER
#define MIO_SHARED_MMAP_HEADER

// #include "mio/mmap.hpp"

#include <memory>        // std::shared_ptr
#include <system_error>  // std::error_code

namespace mio {

/**
 * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with
 * `std::shared_ptr` semantics.
 *
 * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if
 * shared semantics are not required.
 */
template< access_mode AccessMode, typename ByteT >
class basic_shared_mmap {
    using impl_type = basic_mmap<AccessMode, ByteT>;
    std::shared_ptr<impl_type> pimpl_;

   public:

    using value_type             = typename impl_type::value_type;
    using size_type              = typename impl_type::size_type;
    using reference              = typename impl_type::reference;
    using const_reference        = typename impl_type::const_reference;
    using pointer                = typename impl_type::pointer;
    using const_pointer          = typename impl_type::const_pointer;
    using difference_type        = typename impl_type::difference_type;
    using iterator               = typename impl_type::iterator;
    using const_iterator         = typename impl_type::const_iterator;
    using reverse_iterator       = typename impl_type::reverse_iterator;
    using const_reverse_iterator = typename impl_type::const_reverse_iterator;
    using iterator_category      = typename impl_type::iterator_category;
    using handle_type            = typename impl_type::handle_type;
    using mmap_type              = impl_type;

    basic_shared_mmap()                                    = default;
    basic_shared_mmap(const basic_shared_mmap&)            = default;
    basic_shared_mmap& operator=(const basic_shared_mmap&) = default;
    basic_shared_mmap(basic_shared_mmap&&)                 = default;
    basic_shared_mmap& operator=(basic_shared_mmap&&)      = default;

    /** Takes ownership of an existing mmap object. */
    basic_shared_mmap(mmap_type&& mmap) : pimpl_(std::make_shared<mmap_type>(std::move(mmap))) {
    }

    /** Takes ownership of an existing mmap object. */
    basic_shared_mmap& operator=(mmap_type&& mmap) {
        pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
        return *this;
    }

    /** Initializes this object with an already established shared mmap. */
    basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {
    }

    /** Initializes this object with an already established shared mmap. */
    basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap) {
        pimpl_ = std::move(mmap);
        return *this;
    }

#ifdef __cpp_exceptions
    /**
     * The same as invoking the `map` function, except any error that may occur
     * while establishing the mapping is wrapped in a `std::system_error` and is
     * thrown.
     */
    template<typename String>
    basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) {
        std::error_code error;
        map(path, offset, length, error);
        if (error) {
            throw std::system_error(error);
        }
    }

    /**
     * The same as invoking the `map` function, except any error that may occur
     * while establishing the mapping is wrapped in a `std::system_error` and is
     * thrown.
     */
    basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) {
        std::error_code error;
        map(handle, offset, length, error);
        if (error) {
            throw std::system_error(error);
        }
    }
#endif  // __cpp_exceptions

    /**
     * If this is a read-write mapping and the last reference to the mapping,
     * the destructor invokes sync. Regardless of the access mode, unmap is
     * invoked as a final step.
     */
    ~basic_shared_mmap() = default;

    /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */
    std::shared_ptr<mmap_type> get_shared_ptr() {
        return pimpl_;
    }

    /**
     * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
     * however, a mapped region of a file gets its own handle, which is returned by
     * 'mapping_handle'.
     */
    handle_type file_handle() const noexcept {
        return pimpl_ ? pimpl_->file_handle() : invalid_handle;
    }

    handle_type mapping_handle() const noexcept {
        return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;
    }

    /** Returns whether a valid memory mapping has been created. */
    bool is_open() const noexcept {
        return pimpl_ && pimpl_->is_open();
    }

    /**
     * Returns true if no mapping was established, that is, conceptually the
     * same as though the length that was mapped was 0. This function is
     * provided so that this class has Container semantics.
     */
    bool empty() const noexcept {
        return !pimpl_ || pimpl_->empty();
    }

    /**
     * `size` and `length` both return the logical length, i.e. the number of bytes
     * user requested to be mapped, while `mapped_length` returns the actual number of
     * bytes that were mapped which is a multiple of the underlying operating system's
     * page allocation granularity.
     */
    size_type size() const noexcept {
        return pimpl_ ? pimpl_->length() : 0;
    }

    size_type length() const noexcept {
        return pimpl_ ? pimpl_->length() : 0;
    }

    size_type mapped_length() const noexcept {
        return pimpl_ ? pimpl_->mapped_length() : 0;
    }

    /**
     * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
     * exists.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    pointer data() noexcept {
        return pimpl_->data();
    }

    const_pointer data() const noexcept {
        return pimpl_ ? pimpl_->data() : nullptr;
    }

    /**
     * Returns an iterator to the first requested byte, if a valid memory mapping
     * exists, otherwise this function call is undefined behaviour.
     */
    iterator begin() noexcept {
        return pimpl_->begin();
    }

    const_iterator begin() const noexcept {
        return pimpl_->begin();
    }

    const_iterator cbegin() const noexcept {
        return pimpl_->cbegin();
    }

    /**
     * Returns an iterator one past the last requested byte, if a valid memory mapping
     * exists, otherwise this function call is undefined behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    iterator end() noexcept {
        return pimpl_->end();
    }

    const_iterator end() const noexcept {
        return pimpl_->end();
    }

    const_iterator cend() const noexcept {
        return pimpl_->cend();
    }

    /**
     * Returns a reverse iterator to the last memory mapped byte, if a valid
     * memory mapping exists, otherwise this function call is undefined
     * behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    reverse_iterator rbegin() noexcept {
        return pimpl_->rbegin();
    }

    const_reverse_iterator rbegin() const noexcept {
        return pimpl_->rbegin();
    }

    const_reverse_iterator crbegin() const noexcept {
        return pimpl_->crbegin();
    }

    /**
     * Returns a reverse iterator past the first mapped byte, if a valid memory
     * mapping exists, otherwise this function call is undefined behaviour.
     */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    reverse_iterator rend() noexcept {
        return pimpl_->rend();
    }

    const_reverse_iterator rend() const noexcept {
        return pimpl_->rend();
    }

    const_reverse_iterator crend() const noexcept {
        return pimpl_->crend();
    }

    /**
     * Returns a reference to the `i`th byte from the first requested byte (as returned
     * by `data`). If this is invoked when no valid memory mapping has been created
     * prior to this call, undefined behaviour ensues.
     */
    reference operator[](const size_type i) noexcept {
        return (*pimpl_)[i];
    }

    const_reference operator[](const size_type i) const noexcept {
        return (*pimpl_)[i];
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `path`, which must be a path to an existing file, is used to retrieve a file
     * handle (which is closed when the object destructs or `unmap` is called), which is
     * then used to memory map the requested region. Upon failure, `error` is set to
     * indicate the reason and the object remains in an unmapped state.
     *
     * `offset` is the number of bytes, relative to the start of the file, where the
     * mapping should begin. When specifying it, there is no need to worry about
     * providing a value that is aligned with the operating system's page allocation
     * granularity. This is adjusted by the implementation such that the first requested
     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
     * `offset` from the start of the file.
     *
     * `length` is the number of bytes to map. It may be `map_entire_file`, in which
     * case a mapping of the entire file is created.
     */
    template<typename String>
    void map(const String& path, const size_type offset, const size_type length, std::error_code& error) {
        map_impl(path, offset, length, error);
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `path`, which must be a path to an existing file, is used to retrieve a file
     * handle (which is closed when the object destructs or `unmap` is called), which is
     * then used to memory map the requested region. Upon failure, `error` is set to
     * indicate the reason and the object remains in an unmapped state.
     *
     * The entire file is mapped.
     */
    template<typename String>
    void map(const String& path, std::error_code& error) {
        map_impl(path, 0, map_entire_file, error);
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `handle`, which must be a valid file handle, which is used to memory map the
     * requested region. Upon failure, `error` is set to indicate the reason and the
     * object remains in an unmapped state.
     *
     * `offset` is the number of bytes, relative to the start of the file, where the
     * mapping should begin. When specifying it, there is no need to worry about
     * providing a value that is aligned with the operating system's page allocation
     * granularity. This is adjusted by the implementation such that the first requested
     * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
     * `offset` from the start of the file.
     *
     * `length` is the number of bytes to map. It may be `map_entire_file`, in which
     * case a mapping of the entire file is created.
     */
    void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error) {
        map_impl(handle, offset, length, error);
    }

    /**
     * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
     * reason is reported via `error` and the object remains in a state as if this
     * function hadn't been called.
     *
     * `handle`, which must be a valid file handle, which is used to memory map the
     * requested region. Upon failure, `error` is set to indicate the reason and the
     * object remains in an unmapped state.
     *
     * The entire file is mapped.
     */
    void map(const handle_type handle, std::error_code& error) {
        map_impl(handle, 0, map_entire_file, error);
    }

    /**
     * If a valid memory mapping has been created prior to this call, this call
     * instructs the kernel to unmap the memory region and disassociate this object
     * from the file.
     *
     * The file handle associated with the file that is mapped is only closed if the
     * mapping was created using a file path. If, on the other hand, an existing
     * file handle was used to create the mapping, the file handle is not closed.
     */
    void unmap() {
        if (pimpl_)
            pimpl_->unmap();
    }

    void swap(basic_shared_mmap& other) {
        pimpl_.swap(other.pimpl_);
    }

    /** Flushes the memory mapped page to disk. Errors are reported via `error`. */
    template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type >
    void sync(std::error_code& error) {
        if (pimpl_)
            pimpl_->sync(error);
    }

    /** All operators compare the underlying `basic_mmap`'s addresses. */

    friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return a.pimpl_ == b.pimpl_;
    }

    friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return !(a == b);
    }

    friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return a.pimpl_ < b.pimpl_;
    }

    friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return a.pimpl_ <= b.pimpl_;
    }

    friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return a.pimpl_ > b.pimpl_;
    }

    friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) {
        return a.pimpl_ >= b.pimpl_;
    }

   private:

    template<typename MappingToken>
    void map_impl(const MappingToken& token, const size_type offset, const size_type length, std::error_code& error) {
        if (!pimpl_) {
            mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);
            if (error) {
                return;
            }
            pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
        } else {
            pimpl_->map(token, offset, length, error);
        }
    }
};

/**
 * This is the basis for all read-only mmap objects and should be preferred over
 * directly using basic_shared_mmap.
 */
template<typename ByteT>
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;

/**
 * This is the basis for all read-write mmap objects and should be preferred over
 * directly using basic_shared_mmap.
 */
template<typename ByteT>
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;

/**
 * These aliases cover the most common use cases, both representing a raw byte stream
 * (either with a char or an unsigned char/uint8_t).
 */
using shared_mmap_source  = basic_shared_mmap_source<char>;
using shared_ummap_source = basic_shared_mmap_source<unsigned char>;

using shared_mmap_sink  = basic_shared_mmap_sink<char>;
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;

}  // namespace mio

#endif  // MIO_SHARED_MMAP_HEADER
