/* ***** BEGIN LICENSE BLOCK *****
 * This file is part of Natron <https://natrongithub.github.io/>,
 * (C) 2018-2021 The Natron developers
 * (C) 2013-2018 INRIA and Alexandre Gauthier-Foichat
 *
 * Natron is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Natron is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Natron.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
 * ***** END LICENSE BLOCK ***** */


/// @file  fstream_mingw.h
///
/// @brief Utilities for dealing with fstream on MingW.
/// Basically accepting wchar_t* filenames in the std::ifstream::open function
/// is a Windows MSVC extension and does not work on MingW. This file implements
/// ifstream and ofstream so that they work with UTF-16 filenames.


#ifndef ENGINE_FSTREAM_MINGW_H
#define ENGINE_FSTREAM_MINGW_H

#include <cassert>
#include <istream>
#include <ostream>

#if defined(_WIN32) && defined(__GLIBCXX__)
#include <ext/stdio_filebuf.h> // __gnu_cxx::stdio_filebuf
#include <fcntl.h>
#include <sys/stat.h>
#include <Share.h>


NATRON_NAMESPACE_ENTER


template <class _CharT, class _Traits = std::char_traits<_CharT> >
class basic_ifstream
    : public std::basic_istream<_CharT, _Traits>
{
public:
    typedef _CharT char_type;
    typedef _Traits traits_type;
    typedef typename traits_type::int_type int_type;
    typedef typename traits_type::pos_type pos_type;
    typedef typename traits_type::off_type off_type;
    typedef typename __gnu_cxx::stdio_filebuf<char_type, traits_type> stdio_filebuf;


    basic_ifstream();
    explicit basic_ifstream(const std::wstring& path,
                            std::ios_base::openmode __mode = std::ios_base::in);

    virtual ~basic_ifstream();

    stdio_filebuf* rdbuf() const;
    bool is_open() const;
    void open(const std::wstring& path, std::ios_base::openmode __mode = std::ios_base::in);
    void close();

private:

    void open_internal(const std::wstring& path, std::ios_base::openmode mode);

    stdio_filebuf* __sb_;
};

template <class _CharT, class _Traits>
inline
basic_ifstream<_CharT, _Traits>::basic_ifstream()
    : std::basic_istream<char_type, traits_type>(0)
    , __sb_(0)
{
}

template <class _CharT, class _Traits>
inline
basic_ifstream<_CharT, _Traits>::basic_ifstream(const std::wstring& path,
                                                std::ios_base::openmode __mode)
    : std::basic_istream<char_type, traits_type>(0)
    , __sb_(0)
{
    open_internal(path, __mode | std::ios_base::in);
}

template <class _CharT, class _Traits>
inline
basic_ifstream<_CharT, _Traits>::~basic_ifstream()
{
    delete __sb_;
}

inline int
ios_open_mode_to_oflag(std::ios_base::openmode mode)
{
    int f = 0;

    if (mode & std::ios_base::in) {
        f |= _O_RDONLY;
    }
    if (mode & std::ios_base::out) {
        f |= _O_WRONLY;
        f |= _O_CREAT;
        if (mode & std::ios_base::app) {
            f |= _O_APPEND;
        }
        if (mode & std::ios_base::trunc) {
            f |= _O_TRUNC;
        }
    }
    if (mode & std::ios_base::binary) {
        f |= _O_BINARY;
    } else {
        f |= _O_TEXT;
    }

    return f;
}

template <class _CharT, class _Traits>
inline
void
basic_ifstream<_CharT, _Traits>::open_internal(const std::wstring& path,
                                               std::ios_base::openmode mode)
{
    if ( is_open() ) {
        // if the stream is already associated with a file (i.e., it is already open), calling this function fails.
        this->setstate(std::ios_base::failbit);

        return;
    }
    int fd;
    int oflag = ios_open_mode_to_oflag(mode);
    errno_t errcode = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE);
    if (errcode != 0) {
        this->setstate(std::ios_base::failbit);

        return;
    }
    __sb_ = new stdio_filebuf(fd, mode, 1);
    if (__sb_ == 0) {
        this->setstate(std::ios_base::failbit);

        return;
    }
    // 409. Closing an fstream should clear error state
    this->clear();
    assert(__sb_);

    // In init() the rdstate() is set to badbit if __sb_ is NULL and
    // goodbit otherwise. The assert afterwards ensures this.
    this->init(__sb_);
    assert( this->good() && !this->fail() );
}

template <class _CharT, class _Traits>
inline
typename basic_ifstream<_CharT, _Traits>::stdio_filebuf *
basic_ifstream<_CharT, _Traits>::rdbuf() const
{
    return const_cast<stdio_filebuf*>(__sb_);
}


template <class _CharT, class _Traits>
inline
bool
basic_ifstream<_CharT, _Traits>::is_open() const
{
    return __sb_ && __sb_->is_open();
}

template <class _CharT, class _Traits>
void
basic_ifstream<_CharT, _Traits>::open(const std::wstring& path,
                                      std::ios_base::openmode __mode)
{
    open_internal(path, __mode | std::ios_base::in);
}

template <class _CharT, class _Traits>
inline
void
basic_ifstream<_CharT, _Traits>::close()
{
    if (!__sb_) {
        return;
    }
    if (__sb_->close() == 0) {
        this->setstate(std::ios_base::failbit);
    }

    delete __sb_;
    __sb_ = 0;
}

template <class _CharT, class _Traits = std::char_traits<_CharT> >
class basic_ofstream
    : public std::basic_ostream<_CharT, _Traits>
{
public:
    typedef _CharT char_type;
    typedef _Traits traits_type;
    typedef typename traits_type::int_type int_type;
    typedef typename traits_type::pos_type pos_type;
    typedef typename traits_type::off_type off_type;
    typedef typename __gnu_cxx::stdio_filebuf<char_type, traits_type> stdio_filebuf;


    basic_ofstream();
    explicit basic_ofstream(const std::wstring& path,
                            std::ios_base::openmode __mode = std::ios_base::out);

    virtual ~basic_ofstream();

    stdio_filebuf* rdbuf() const;
    bool is_open() const;
    void open(const std::wstring& path, std::ios_base::openmode __mode = std::ios_base::out);
    void close();

private:

    void open_internal(const std::wstring& path, std::ios_base::openmode mode);

    stdio_filebuf* __sb_;
};

template <class _CharT, class _Traits>
inline
basic_ofstream<_CharT, _Traits>::basic_ofstream()
    : std::basic_ostream<char_type, traits_type>(0)
    , __sb_(0)
{
}

template <class _CharT, class _Traits>
inline
basic_ofstream<_CharT, _Traits>::basic_ofstream(const std::wstring& path,
                                                std::ios_base::openmode __mode)
    : std::basic_ostream<char_type, traits_type>(0)
    , __sb_(0)
{
    open_internal(path, __mode  | std::ios_base::out);
}

template <class _CharT, class _Traits>
inline
basic_ofstream<_CharT, _Traits>::~basic_ofstream()
{
    delete __sb_;
}

template <class _CharT, class _Traits>
inline
void
basic_ofstream<_CharT, _Traits>::open_internal(const std::wstring& path,
                                               std::ios_base::openmode mode)
{
    if ( is_open() ) {
        // if the stream is already associated with a file (i.e., it is already open), calling this function fails.
        this->setstate(std::ios_base::failbit);

        return;
    }
    int fd;
    int oflag = ios_open_mode_to_oflag(mode);
    errno_t errcode = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE);
    if (errcode != 0) {
        this->setstate(std::ios_base::failbit);

        return;
    }
    __sb_ = new stdio_filebuf(fd, mode, 1);
    if (__sb_ == 0) {
        this->setstate(std::ios_base::failbit);

        return;
    }
    // 409. Closing an fstream should clear error state
    this->clear();
    assert(__sb_);

    // In init() the rdstate() is set to badbit if __sb_ is NULL and
    // goodbit otherwise. The assert afterwards ensures this.
    this->init(__sb_);
    assert( this->good() && !this->fail() );
}

template <class _CharT, class _Traits>
inline
typename basic_ofstream<_CharT, _Traits>::stdio_filebuf *
basic_ofstream<_CharT, _Traits>::rdbuf() const
{
    return const_cast<stdio_filebuf*>(__sb_);
}


template <class _CharT, class _Traits>
inline
bool
basic_ofstream<_CharT, _Traits>::is_open() const
{
    return __sb_ && __sb_->is_open();
}

template <class _CharT, class _Traits>
void
basic_ofstream<_CharT, _Traits>::open(const std::wstring& path,
                                      std::ios_base::openmode __mode)
{
    open_internal(path, __mode | std::ios_base::out);
}

template <class _CharT, class _Traits>
inline
void
basic_ofstream<_CharT, _Traits>::close()
{
    if (!__sb_) {
        return;
    }
    if (__sb_->close() == 0) {
        this->setstate(std::ios_base::failbit);
    }

    delete __sb_;
    __sb_ = 0;
}

// basic_fstream

NATRON_NAMESPACE_EXIT


#endif // #if defined(_WIN32) && defined(__GLIBCXX__)


#endif // ENGINE_FSTREAM_MINGW_H
