// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) Contributors to the OpenEXR Project.

#pragma once

#ifndef INCLUDE_TestUtilFStream_h_
#    define INCLUDE_TestUtilFStream_h_ 1

#    include <ImfMisc.h>

#    include <fstream>
#    include <string>

#    ifdef _WIN32
#        ifndef NOMINMAX
#            define NOMINMAX
#        endif
#        define VC_EXTRALEAN
#        include <fcntl.h>
#        include <io.h>
#        include <share.h>
#        include <string.h>
#        include <sys/stat.h>
#        include <sys/types.h>
#        include <windows.h>
#    endif

namespace testutil
{
#    ifdef _WIN32
// This is a big work around mechanism for compiling using mingw / gcc under windows
// until mingw 9 where they add the wide filename version of open
#        if (                                                                  \
            defined(__GLIBCXX__) &&                                            \
            !(defined(_GLIBCXX_HAVE_WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T)))
#            define USE_WIDEN_FILEBUF 1
template <typename CharT, typename TraitsT>
class WidenFilebuf : public std::basic_filebuf<CharT, TraitsT>
{
    inline int mode_to_flags (std::ios_base::openmode mode)
    {
        int flags = 0;
        if (mode & std::ios_base::in) flags |= _O_RDONLY;
        if (mode & std::ios_base::out)
        {
            flags |= _O_WRONLY;
            flags |= _O_CREAT;
            if (mode & std::ios_base::app) flags |= _O_APPEND;
            if (mode & std::ios_base::trunc) flags |= _O_TRUNC;
        }
        if (mode & std::ios_base::binary)
            flags |= _O_BINARY;
        else
            flags |= _O_TEXT;
        return flags;
    }

public:
    using base_filebuf = std::basic_filebuf<CharT, TraitsT>;
    inline base_filebuf* wide_open (std::wstring& fn, std::ios_base::openmode m)
    {
        if (this->is_open () || fn.empty ()) return nullptr;

        int     fd;
        errno_t e = _wsopen_s (
            &fd,
            fn.c_str (),
            mode_to_flags (m),
            _SH_DENYNO,
            _S_IREAD | _S_IWRITE);
        if (e != 0) return nullptr;

        // sys_open will do an fdopen internally which will then clean up the fd upon close
        this->_M_file.sys_open (fd, m);
        if (this->is_open ())
        {
            // reset the internal state, these members are consistent between gcc versions 4.3 - 9
            // but at 9, the wfopen stuff should become available, such that this will no longer be
            // active
            this->_M_allocate_internal_buffer ();
            this->_M_mode    = m;
            this->_M_reading = false;
            this->_M_writing = false;
            this->_M_set_buffer (-1);
            this->_M_state_last = this->_M_state_cur = this->_M_state_beg;

            if ((m & std::ios_base::ate) &&
                this->seekoff (0, std::ios_base::end, m) ==
                    static_cast<typename base_filebuf::pos_type> (-1))
            {
                this->close ();
                return nullptr;
            }
        }
        return this;
    }
};
#        endif // __GLIBCXX__
#    endif     // _WIN32

template <typename StreamType>
inline void
OpenStreamWithUTF8Name (
    StreamType& is, const char* filename, std::ios_base::openmode mode)
{

#   ifdef _WIN32

// TODO: Remove pragmas for pushing/popping deprecation warnings when WidenFilename is removed from API
#   ifdef _MSC_VER
#       pragma warning(push)
#       pragma warning(disable: 4996)
#   elif defined(__clang__) || defined(__GNUC__)
#       pragma GCC diagnostic push
#       pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#   endif

    std::wstring wfn = OPENEXR_IMF_INTERNAL_NAMESPACE::WidenFilename (filename);

#   ifdef _MSC_VER
#       pragma warning(pop)
#   elif defined(__clang__) || defined(__GNUC__)
#       pragma GCC diagnostic pop
#   endif

#        ifdef USE_WIDEN_FILEBUF
    using CharT   = typename StreamType::char_type;
    using TraitsT = typename StreamType::traits_type;
    using wbuf    = WidenFilebuf<CharT, TraitsT>;
    if (!static_cast<wbuf*> (is.rdbuf ())->wide_open (wfn, mode))
        is.setstate (std::ios_base::failbit);
    else
        is.clear ();
#        else
    is.rdbuf ()->open (wfn.c_str (), mode);
#        endif
#    else
    is.rdbuf ()->open (filename, mode);
#    endif
}

} // namespace testutil

#endif // INCLUDE_TestUtilFStream_h_
