// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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)
#ifndef BOOST_PROCESS_V2_SHELL_HPP
#define BOOST_PROCESS_V2_SHELL_HPP

#include <boost/core/exchange.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#include <boost/process/v2/detail/throw_error.hpp>
#include <boost/process/v2/environment.hpp>
#include <memory>
#include <string>

BOOST_PROCESS_V2_BEGIN_NAMESPACE

/// Error category used by the shell parser.
extern BOOST_PROCESS_V2_DECL const error_category& get_shell_category();
static const error_category& shell_category = get_shell_category();

/// Utility to parse commands 
/** This utility class parses command lines into tokens
 * and allows users to executed based on textual inputs.
 * 
 * In v1, this was possible directly when starting a process,
 * but has been removed based on the security risks associated with this.
 * 
 * By making the shell parsing explicitly, it encourages
 * a user to run a sanity check on the executable before launching it.
 * 
 * @par Example
 * @code {.cpp}
 * asio::io_context ctx;
 * 
 * auto cmd = shell("my-app --help");
 * auto exe = cmd.exe();
 * check_if_malicious(exe);
 * 
 * process proc{ctx, exe, cmd.args()};
 * 
 * @endcode
 * 
 * 
 */
struct shell
{    
#if defined(BOOST_PROCESS_V2_WINDOWS)
    using char_type = wchar_t; 
    using args_type = const wchar_t *;
#else
    using char_type = char;
    using args_type = const char **;
#endif

    shell() = default;

    template<typename Char, typename Traits>
    shell(basic_string_view<Char, Traits> input) 
        : buffer_(detail::conv_string<char_type>(input.data(), input.size())) 
    {
        parse_();
    }

    shell(basic_cstring_ref<char_type> input) : input_(input) {parse_();}
    shell(basic_string_view<
                    typename std::conditional<
                        std::is_same<char_type, char>::value,
                        wchar_t, char>::type> input) : buffer_(detail::conv_string<char_type>(input.data(), input.size())) 
    {
        parse_();
    }

    shell(const shell &) = delete;
    shell& operator=(const shell &) = delete;

    shell(shell && lhs) noexcept 
        : buffer_(std::move(lhs.buffer_)),
          input_(std::move(lhs.input_)),
          argc_(boost::exchange(lhs.argc_, 0)),
          argv_(boost::exchange(lhs.argv_, nullptr))
#if defined(BOOST_PROCESS_V2_POSIX)
        , free_argv_(boost::exchange(lhs.free_argv_, nullptr))
#endif
    {
    }
    shell& operator=(shell && lhs) noexcept
    {
        shell tmp(std::move(*this));
        buffer_ = std::move(lhs.buffer_);
        input_ = std::move(lhs.input_);
        argc_  = boost::exchange(lhs.argc_, 0);
        argv_ = boost::exchange(lhs.argv_, nullptr);
#if defined(BOOST_PROCESS_V2_POSIX)
        free_argv_ = boost::exchange(lhs.free_argv_, nullptr);
#endif
        return *this;
    }

    // the length of the parsed shell, including the executable
    int argc() const { return argc_; }
    char_type** argv() const { return argv_; }
        
    char_type** begin() const {return argv();}
    char_type** end()   const {return argv() + argc();}

    bool empty() const {return argc() == 0;}
    std::size_t size() const {return static_cast<std::size_t>(argc()); }
    /// Native representation of the arguments to be used - excluding the executable
    BOOST_PROCESS_V2_DECL args_type args() const;
    template<typename Environment = environment::current_view>
    filesystem::path exe(Environment && env = environment::current()) const
    {
        if (argc() == 0)
            return "";
        else
            return environment::find_executable(0[argv()], std::forward<Environment>(env)); 
    }
    BOOST_PROCESS_V2_DECL ~shell();

  private:

    friend struct make_cmd_shell_;

    BOOST_PROCESS_V2_DECL void parse_();
    
    // storage in case we need a conversion
    std::basic_string<char_type> buffer_;
    basic_cstring_ref<char_type> input_{buffer_}; 
    // impl details
    int argc_ = 0;
    char_type  ** argv_ = nullptr;

#if defined(BOOST_PROCESS_V2_POSIX)
    void(*free_argv_)(int, char **);
#endif
    
};

BOOST_PROCESS_V2_END_NAMESPACE

#if defined(BOOST_PROCESS_V2_HEADER_ONLY)

#include <boost/process/v2/impl/shell.ipp>

#endif

#endif //BOOST_PROCESS_V2_ERROR_HPP
