/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program 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.
 *
 * This program 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 this program. If not, see .
 */
#include "StartProcess.h"
#include "Errors.h"
#include "Log.h"
#include "Optional.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace boost::process;
using namespace boost::iostreams;
namespace Trinity
{
template
class TCLogSink
{
    T callback_;
public:
    typedef char      char_type;
    typedef sink_tag  category;
    // Requires a callback type which has a void(std::string) signature
    TCLogSink(T callback)
        : callback_(std::move(callback)) { }
    std::streamsize write(char const* str, std::streamsize size)
    {
        std::string_view consoleStr(str, size);
        size_t lineEnd = consoleStr.find_first_of("\r\n");
        std::streamsize processedCharacters = size;
        if (lineEnd != std::string_view::npos)
        {
            consoleStr = consoleStr.substr(0, lineEnd);
            processedCharacters = lineEnd + 1;
        }
        if (!consoleStr.empty())
            callback_(consoleStr);
        return processedCharacters;
    }
};
template
auto MakeTCLogSink(T&& callback)
    -> TCLogSink::type>
{
    return { std::forward(callback) };
}
template
static int CreateChildProcess(T waiter, std::string const& executable,
                              std::vector const& argsVector,
                              std::string const& logger, std::string const& input,
                              bool secure)
{
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
#pragma warning(push)
#pragma warning(disable:4297)
/*
  Silence warning with boost 1.83
    boost/process/pipe.hpp(132,5): warning C4297: 'boost::process::basic_pipebuf>::~basic_pipebuf': function assumed not to throw an exception but does
    boost/process/pipe.hpp(132,5): message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
    boost/process/pipe.hpp(124,6): message : while compiling class template member function 'boost::process::basic_pipebuf>::~basic_pipebuf(void)'
    boost/process/pipe.hpp(304,42): message : see reference to class template instantiation 'boost::process::basic_pipebuf>' being compiled
*/
#endif
    ipstream outStream;
    ipstream errStream;
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
#pragma warning(pop)
#endif
    if (!secure)
    {
        TC_LOG_TRACE(logger, "Starting process \"{}\" with arguments: \"{}\".",
                executable, boost::algorithm::join(argsVector, " "));
    }
    // prepare file with only read permission (boost process opens with read_write)
    std::shared_ptr inputFile(!input.empty() ? fopen(input.c_str(), "rb") : nullptr, [](FILE* ptr)
    {
        if (ptr != nullptr)
            fclose(ptr);
    });
    // Start the child process
    child c = [&]()
    {
        if (inputFile)
        {
            // With binding stdin
            return child{
                exe = boost::filesystem::absolute(executable).string(),
                args = argsVector,
                env = environment(boost::this_process::environment()),
                std_in = inputFile.get(),
                std_out = outStream,
                std_err = errStream
            };
        }
        else
        {
            // Without binding stdin
            return child{
                exe = boost::filesystem::absolute(executable).string(),
                args = argsVector,
                env = environment(boost::this_process::environment()),
                std_in = boost::process::close,
                std_out = outStream,
                std_err = errStream
            };
        }
    }();
    auto outInfo = MakeTCLogSink([&](std::string_view msg)
    {
        TC_LOG_INFO(logger, "{}", msg);
    });
    auto outError = MakeTCLogSink([&](std::string_view msg)
    {
        TC_LOG_ERROR(logger, "{}", msg);
    });
    copy(outStream, outInfo);
    copy(errStream, outError);
    // Call the waiter in the current scope to prevent
    // the streams from closing too early on leaving the scope.
    int const result = waiter(c);
    if (!secure)
    {
        TC_LOG_TRACE(logger, ">> Process \"{}\" finished with return value {}.",
                executable, result);
    }
    return result;
}
int StartProcess(std::string const& executable, std::vector const& args,
                 std::string const& logger, std::string input_file, bool secure)
{
    return CreateChildProcess([](child& c) -> int
    {
        try
        {
            c.wait();
            return c.exit_code();
        }
        catch (...)
        {
            return EXIT_FAILURE;
        }
    }, executable, args, logger, input_file, secure);
}
class AsyncProcessResultImplementation
    : public AsyncProcessResult
{
    std::string const executable;
    std::vector const args;
    std::string const logger;
    std::string const input_file;
    bool const is_secure;
    std::atomic was_terminated;
    // Workaround for missing move support in boost < 1.57
    Optional>> result;
    Optional> my_child;
public:
    explicit AsyncProcessResultImplementation(std::string executable_, std::vector args_,
                                     std::string logger_, std::string input_file_,
                                     bool secure)
        : executable(std::move(executable_)), args(std::move(args_)),
          logger(std::move(logger_)), input_file(input_file_),
          is_secure(secure), was_terminated(false) { }
    AsyncProcessResultImplementation(AsyncProcessResultImplementation const&) = delete;
    AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation const&) = delete;
    AsyncProcessResultImplementation(AsyncProcessResultImplementation&&) = delete;
    AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation&&) = delete;
    int StartProcess()
    {
        ASSERT(!my_child, "Process started already!");
        return CreateChildProcess([&](child& c) -> int
        {
            int result;
            my_child = std::reference_wrapper(c);
            try
            {
                c.wait();
                result = c.exit_code();
            }
            catch (...)
            {
                result = EXIT_FAILURE;
            }
            my_child.reset();
            return was_terminated ? EXIT_FAILURE : result;
        }, executable, args, logger, input_file, is_secure);
    }
    void SetFuture(std::future result_)
    {
        result = std::make_shared>(std::move(result_));
    }
    /// Returns the future which contains the result of the process
    /// as soon it is finished.
    std::future& GetFutureResult() override
    {
        ASSERT(*result, "The process wasn't started!");
        return **result;
    }
    /// Tries to terminate the process
    void Terminate() override
    {
        if (my_child)
        {
            was_terminated = true;
            try
            {
                my_child->get().terminate();
            }
            catch(...)
            {
                // Do nothing
            }
        }
    }
};
std::shared_ptr
    StartAsyncProcess(std::string executable, std::vector args,
                      std::string logger, std::string input_file, bool secure)
{
    auto handle = std::make_shared(
        std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure);
    handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); }));
    return handle;
}
std::string SearchExecutableInPath(std::string const& filename)
{
    try
    {
        return search_path(filename).string();
    }
    catch (...)
    {
        return "";
    }
}
} // namespace Trinity