//
// Copyright (c) 2015-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

#ifndef BOOST_BEAST_CORE_IMPL_FILE_STDIO_IPP
#define BOOST_BEAST_CORE_IMPL_FILE_STDIO_IPP

#include <boost/beast/core/file_stdio.hpp>
#include <boost/beast/core/detail/win32_unicode_path.hpp>
#include <boost/config/workaround.hpp>
#include <boost/core/exchange.hpp>
#include <limits>

namespace boost {
namespace beast {

file_stdio::
~file_stdio()
{
    if(f_)
        fclose(f_);
}

file_stdio::
file_stdio(file_stdio&& other)
    : f_(boost::exchange(other.f_, nullptr))
{
}

file_stdio&
file_stdio::
operator=(file_stdio&& other)
{
    if(&other == this)
        return *this;
    if(f_)
        fclose(f_);
    f_ = other.f_;
    other.f_ = nullptr;
    return *this;
}

void
file_stdio::
native_handle(std::FILE* f)
{
    if(f_)
        fclose(f_);
    f_ = f;
}

void
file_stdio::
close(error_code& ec)
{
    if(f_)
    {
        int failed = fclose(f_);
        f_ = nullptr;
        if(failed)
        {
            ec.assign(errno, generic_category());
            return;
        }
    }
    ec = {};
}

void
file_stdio::
open(char const* path, file_mode mode, error_code& ec)
{
    if(f_)
    {
        fclose(f_);
        f_ = nullptr;
    }
    ec = {};
#if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
    boost::winapi::WCHAR_ const* s;
    detail::win32_unicode_path unicode_path(path, ec);
    if (ec)
        return;
#else
    char const* s;
#endif
    switch(mode)
    {
    default:
    case file_mode::read:
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"rb";
    #else
        s = "rb";
    #endif
        break;

    case file_mode::scan:
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"rbS";
    #else
        s = "rb";
    #endif
        break;

    case file_mode::write:
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"wb+";
    #else
        s = "wb+";
    #endif
        break;

    case file_mode::write_new:
    {
#if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
# if (defined(BOOST_MSVC) && BOOST_MSVC >= 1910) || (defined(_MSVC_STL_VERSION) && _MSVC_STL_VERSION >= 141)
        s = L"wbx";
# else
        std::FILE* f0;
        auto const ev = ::_wfopen_s(&f0, unicode_path.c_str(), L"rb");
        if(! ev)
        {
            std::fclose(f0);
            ec = make_error_code(errc::file_exists);
            return;
        }
        else if(ev !=
            errc::no_such_file_or_directory)
        {
            ec.assign(ev, generic_category());
            return;
        }
        s = L"wb";
# endif
#else
        s = "wbx";
#endif
        break;
    }

    case file_mode::write_existing:
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"rb+";
    #else
        s = "rb+";
    #endif
        break;

    case file_mode::append:
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"ab";
    #else
        s = "ab";
    #endif
        break;

    case file_mode::append_existing:
    {
#if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        std::FILE* f0;
        auto const ev =
            ::_wfopen_s(&f0, unicode_path.c_str(), L"rb+");
        if(ev)
        {
            ec.assign(ev, generic_category());
            return;
        }
#else
        auto const f0 =
            std::fopen(path, "rb+");
        if(! f0)
        {
            ec.assign(errno, generic_category());
            return;
        }
#endif
        std::fclose(f0);
    #if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
        s = L"ab";
    #else
        s = "ab";
    #endif
        break;
    }
    }

#if defined(BOOST_MSVC) || defined(_MSVC_STL_VERSION)
    auto const ev = ::_wfopen_s(&f_, unicode_path.c_str(), s);
    if(ev)
    {
        f_ = nullptr;
        ec.assign(ev, generic_category());
        return;
    }
#else
    f_ = std::fopen(path, s);
    if(! f_)
    {
        ec.assign(errno, generic_category());
        return;
    }
#endif
}

std::uint64_t
file_stdio::
size(error_code& ec) const
{
    if(! f_)
    {
        ec = make_error_code(errc::bad_file_descriptor);
        return 0;
    }
    long pos = std::ftell(f_);
    if(pos == -1L)
    {
        ec.assign(errno, generic_category());
        return 0;
    }
    int result = std::fseek(f_, 0, SEEK_END);
    if(result != 0)
    {
        ec.assign(errno, generic_category());
        return 0;
    }
    long size = std::ftell(f_);
    if(size == -1L)
    {
        ec.assign(errno, generic_category());
        std::fseek(f_, pos, SEEK_SET);
        return 0;
    }
    result = std::fseek(f_, pos, SEEK_SET);
    if(result != 0)
        ec.assign(errno, generic_category());
    else
        ec = {};
    return size;
}

std::uint64_t
file_stdio::
pos(error_code& ec) const
{
    if(! f_)
    {
        ec = make_error_code(errc::bad_file_descriptor);
        return 0;
    }
    long pos = std::ftell(f_);
    if(pos == -1L)
    {
        ec.assign(errno, generic_category());
        return 0;
    }
    ec = {};
    return pos;
}

void
file_stdio::
seek(std::uint64_t offset, error_code& ec)
{
    if(! f_)
    {
        ec = make_error_code(errc::bad_file_descriptor);
        return;
    }
    if(offset > static_cast<std::uint64_t>((std::numeric_limits<long>::max)()))
    {
        ec = make_error_code(errc::invalid_seek);
        return;
    }
    int result = std::fseek(f_,
        static_cast<long>(offset), SEEK_SET);
    if(result != 0)
        ec.assign(errno, generic_category());
    else
        ec = {};
}

std::size_t
file_stdio::
read(void* buffer, std::size_t n, error_code& ec) const
{
    if(! f_)
    {
        ec = make_error_code(errc::bad_file_descriptor);
        return 0;
    }
    auto nread = std::fread(buffer, 1, n, f_);
    if(std::ferror(f_))
    {
        ec.assign(errno, generic_category());
        return 0;
    }
    ec = {};
    return nread;
}

std::size_t
file_stdio::
write(void const* buffer, std::size_t n, error_code& ec)
{
    if(! f_)
    {
        ec = make_error_code(errc::bad_file_descriptor);
        return 0;
    }
    auto nwritten = std::fwrite(buffer, 1, n, f_);
    if(std::ferror(f_))
    {
        ec.assign(errno, generic_category());
        return 0;
    }
    ec = {};
    return nwritten;
}

} // beast
} // boost

#endif
