//
// Copyright (c) 2016-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_FILE_TEST_HPP
#define BOOST_BEAST_FILE_TEST_HPP

#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/file_base.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iterator>
#include <string>
#include <type_traits>
#include <vector>

#if defined(BOOST_GCC) && BOOST_GCC >= 130000
#pragma GCC diagnostic ignored "-Wself-move"
#endif

namespace boost {
namespace beast {

template<class File, bool append_unicode_suffix = false>
void
test_file()
{
    BOOST_CORE_STATIC_ASSERT(
        is_file<File>::value);
    BOOST_CORE_STATIC_ASSERT(
        ! std::is_copy_constructible<File>::value);
    BOOST_CORE_STATIC_ASSERT(
        ! std::is_copy_assignable<File>::value);

    namespace fs = boost::filesystem;

    static constexpr
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
    boost::winapi::WCHAR_ unicode_suffix[] = { 0xd83e, 0xdd84, 0x0000 }; // UTF-16-LE unicorn
#else
    char                  unicode_suffix[] = { '\xf0', '\x9f', '\xa6', '\x84', '\x00' }; // UTF-8 unicorn
#endif

    class temp_path
    {
        fs::path path_;
        std::vector<char> utf8_str_;

    public:
        temp_path()
            : path_(fs::unique_path())
        {
            if (append_unicode_suffix)
                path_ += unicode_suffix;
#ifdef _WIN32
            constexpr auto cp = boost::winapi::CP_UTF8_;
            constexpr auto flags = boost::winapi::WC_ERR_INVALID_CHARS_;
            auto sz = boost::winapi::WideCharToMultiByte(
                cp, flags, path_.c_str(), -1, nullptr, 0,
                nullptr, nullptr);
            BEAST_EXPECT(sz != 0);
            utf8_str_.resize(sz);
            auto ret = boost::winapi::WideCharToMultiByte(
                cp, flags, path_.c_str(), -1,
                utf8_str_.data(), sz,
                nullptr, nullptr);
            BEAST_EXPECT(ret == sz);
#endif
        }

        operator fs::path const&()
        {
            return path_;
        }

        operator char const*()
        {
#ifdef _WIN32
            return utf8_str_.data();
#else
            return path_.c_str();
#endif
        }
    };

    auto const create =
        [](fs::path const& path, std::string const& data = "")
        {
            BEAST_EXPECT(! fs::exists(path));
            fs::ofstream out(path);
            BEAST_EXPECT(out.is_open());
            if (data.size())
                out.write(data.c_str(), data.size());
        };

    auto const remove =
        [](fs::path const& path)
        {
            fs::remove(path);
            BEAST_EXPECT(! fs::exists(path));
        };

    auto const consume_file =
        [](fs::path const& path)
        {
            // no exceptions - failure will result in an empty string
            fs::ifstream in;
            in.open(path);
            noskipws(in);
            auto s = std::string(
                std::istream_iterator<char>(in),
                std::istream_iterator<char>());
            in.close();
            return s;
        };

    temp_path path;

    // bad file descriptor
    {
        File f;
        char buf[1];
        BEAST_EXPECT(! f.is_open());
        BEAST_EXPECT(! fs::exists(path));
        {
            error_code ec;
            f.size(ec);
            BEAST_EXPECT(ec == errc::bad_file_descriptor);
        }
        {
            error_code ec;
            f.pos(ec);
            BEAST_EXPECT(ec == errc::bad_file_descriptor);
        }
        {
            error_code ec;
            f.seek(0, ec);
            BEAST_EXPECT(ec == errc::bad_file_descriptor);
        }
        {
            error_code ec;
            f.read(buf, 0, ec);
            BEAST_EXPECT(ec == errc::bad_file_descriptor);
        }
        {
            error_code ec;
            f.write(buf, 0, ec);
            BEAST_EXPECT(ec == errc::bad_file_descriptor);
        }
    }

    // file_mode::read
    {
        {
            File f;
            error_code ec;
            create(path);
            f.open(path, file_mode::read, ec);
            BEAST_EXPECT(! ec);
        }
        remove(path);
    }
    
    // file_mode::scan
    {
        {
            File f;
            error_code ec;
            create(path);
            f.open(path, file_mode::scan, ec);
            BEAST_EXPECT(! ec);
        }
        remove(path);
    }
    
    // file_mode::write
    {
        {
            File f;
            error_code ec;
            BEAST_EXPECT(! fs::exists(path));
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(fs::exists(path));
        }
        {
            File f;
            error_code ec;
            BEAST_EXPECT(fs::exists(path));
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(fs::exists(path));
        }
        remove(path);
    }
    
    // file_mode::write_new
    {
        {
            File f;
            error_code ec;
            BEAST_EXPECT(! fs::exists(path));
            f.open(path, file_mode::write_new, ec);
            BEAST_EXPECTS(! ec, ec.message());
            BEAST_EXPECT(fs::exists(path));
        }
        {
            File f;
            error_code ec;
            BEAST_EXPECT(fs::exists(path));
            f.open(path, file_mode::write_new, ec);
            BEAST_EXPECT(ec);
        }
        remove(path);
    }
    
    // file_mode::write_existing
    {
        {
            File f;
            error_code ec;
            BEAST_EXPECT(! fs::exists(path));
            f.open(path, file_mode::write_existing, ec);
            BEAST_EXPECT(ec);
            BEAST_EXPECT(! fs::exists(path));
        }
        {
            File f;
            error_code ec;
            create(path);
            BEAST_EXPECT(fs::exists(path));
            f.open(path, file_mode::write_existing, ec);
            BEAST_EXPECT(! ec);
        }
        remove(path);
    }
    
    // file_mode::append
    {
        {
            File f;
            error_code ec;
            BEAST_EXPECT(! fs::exists(path));
            f.open(path, file_mode::append, ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(fs::exists(path));
            static const std::string extra = "the";
            f.write(extra.c_str(), extra.size(), ec);
            BEAST_EXPECT(!ec);
            f.close(ec);
            auto s = consume_file(path);
            BEAST_EXPECT(s == "the");
        }

        {
            File f;
            error_code ec;
            BEAST_EXPECT(fs::exists(path));
            f.open(path, file_mode::append, ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(fs::exists(path));
            static const std::string extra = " cat";
            f.write(extra.c_str(), extra.size(), ec);
            BEAST_EXPECT(!ec);
            f.close(ec);
            auto s = consume_file(path);
            BEAST_EXPECTS(s == "the cat", s);
        }
        remove(path);
    }
        
    // file_mode::append_existing
    {
        {
            File f;
            error_code ec;
            BEAST_EXPECT(! fs::exists(path));
            f.open(path, file_mode::append_existing, ec);
            BEAST_EXPECT(ec);
            BEAST_EXPECT(! fs::exists(path));
        }
        remove(path);
        {
            File f;
            error_code ec;
            create(path, "the cat");
            f.open(path, file_mode::append_existing, ec);
            BEAST_EXPECT(! ec);
            static std::string const extra = " sat";
            f.write(extra.c_str(), extra.size(), ec);
            BEAST_EXPECT(!ec);
            f.close(ec);
            BEAST_EXPECT(!ec);
            auto s = consume_file(path);
            BEAST_EXPECTS(s == "the cat sat", s);
        }
        remove(path);
    }

    // special members
    {
        {
            File f1;
            error_code ec;
            f1.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(f1.is_open());

            // move constructor
            File f2(std::move(f1));
            BEAST_EXPECT(! f1.is_open());
            BEAST_EXPECT(f2.is_open());

            // move assignment
            File f3;
            f3 = std::move(f2);
            BEAST_EXPECT(! f2.is_open());
            BEAST_EXPECT(f3.is_open());
        }
        remove(path);
    }

    // re-open
    {
        {
            File f;
            error_code ec;
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
        }
        remove(path);
    }

    // re-assign
    {
        temp_path path2;
        {
            error_code ec;

            File f1;
            f1.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);

            File f2;
            f2.open(path2, file_mode::write, ec);
            BEAST_EXPECT(! ec);

            f2 = std::move(f1);
            BEAST_EXPECT(! f1.is_open());
            BEAST_EXPECT(f2.is_open());
        }
        remove(path);
        remove(path2);
    }

    // self-move
    {
        {
            File f;
            error_code ec;
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            f = std::move(f);
            BEAST_EXPECT(f.is_open());
        }
        remove(path);
    }

    // native_handle
    {
        {
            File f;
            auto none = f.native_handle();
            error_code ec;
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);
            auto fd = f.native_handle();
            BEAST_EXPECT(fd != none);
            f.native_handle(none);
            BEAST_EXPECT(! f.is_open());
        }
        remove(path);
    }

    // read and write
    {
        string_view const s = "Hello, world!";

        // write
        {
            File f;
            error_code ec;
            f.open(path, file_mode::write, ec);
            BEAST_EXPECT(! ec);

            f.write(s.data(), s.size(), ec);
            BEAST_EXPECT(! ec);

            auto size = f.size(ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(size == s.size());

            auto pos = f.pos(ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(pos == size);

            f.close(ec);
            BEAST_EXPECT(! ec);
        }

        // read
        {
            File f;
            error_code ec;
            f.open(path, file_mode::read, ec);
            BEAST_EXPECT(! ec);

            std::string buf;
            buf.resize(s.size());
            f.read(&buf[0], buf.size(), ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(buf == s);

            f.seek(1, ec);
            BEAST_EXPECT(! ec);
            buf.resize(3);
            f.read(&buf[0], buf.size(), ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(buf == "ell");

            auto pos = f.pos(ec);
            BEAST_EXPECT(! ec);
            BEAST_EXPECT(pos == 4);
        }
        remove(path);
    }

    // https://github.com/boostorg/beast/issues/3035
    //
    // A successful operation must clear `ec`.
    {
        string_view const s = "Hello, world!";

        // write
        {
            File f;
            {
                error_code ec = error::timeout;
                f.open(path, file_mode::write, ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                error_code ec = error::timeout;
                f.write(s.data(), s.size(), ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                error_code ec = error::timeout;
                f.write(s.data(), 0, ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                error_code ec = error::timeout;
                auto n = f.size(ec);
                BEAST_EXPECTS(! ec, ec.message());
                BEAST_EXPECT(n == s.size());
            }
            {
                error_code ec = error::timeout;
                f.pos(ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                error_code ec = error::timeout;
                f.seek(0, ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                error_code ec = error::timeout;
                f.close(ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
        }

        // read
        {
            File f;
            {
                error_code ec = error::timeout;
                f.open(path, file_mode::read, ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                std::string buf;
                buf.resize(s.size());
                error_code ec = error::timeout;
                auto n = f.read(&buf[0], buf.size(), ec);
                BEAST_EXPECTS(! ec, ec.message());
                BEAST_EXPECT(n == s.size());
                BEAST_EXPECT(buf == s);
            }
            {
                char c;
                error_code ec = error::timeout;
                f.read(&c, 0, ec);
                BEAST_EXPECTS(! ec, ec.message());
            }
            {
                char c;
                error_code ec = error::timeout;
                auto n = f.read(&c, 1, ec);
                BEAST_EXPECTS(! ec, ec.message());
                BEAST_EXPECT(n == 0);
            }
        }
        remove(path);
    }

    BEAST_EXPECT(! fs::exists(path));
}

} // beast
} // boost

#endif
