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

#include <boost/process/v1/detail/traits/wchar_t.hpp>
#include <boost/process/v1/environment.hpp>
#include <boost/none.hpp>

#if defined(BOOST_POSIX_API)
#include <boost/process/v1/detail/posix/env_init.hpp>
#elif defined(BOOST_WINDOWS_API)
#include <boost/process/v1/detail/windows/env_init.hpp>
#endif

/** \file boost/process/env.hpp
 *
 *    This header which provides the `env` property. It allows the modification of the
 *    environment the child process will run in, in a functional style.
 *
 *  \xmlonly
<programlisting>
namespace boost {
  namespace process { BOOST_PROCESS_V1_INLINE namespace v1 {
    <emphasis>unspecified</emphasis> <globalname alt="boost::process::v1::env">env</globalname>;
  }
}
</programlisting>
 *  \endxmlonly
 *
 *  For additional information see the platform documentations:
 *
 *   - [windows](https://msdn.microsoft.com/en-US/library/windows/desktop/ms682653.aspx)
 *   - [posix](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html)
 *
 */


namespace boost {

namespace process { BOOST_PROCESS_V1_INLINE namespace v1 { namespace detail {


template<typename Char>
std::size_t make_env_string_size(const std::basic_string<Char> & ch)
{
    return ch.size() + 1;
}

template<typename Char>
std::size_t make_env_string_size(const Char * ch)
{
    std::size_t sz = 0;
    while (ch[sz] != null_char<Char>())
        sz++;

    sz++;
    return sz;
}

template<typename Char, typename Container>
inline std::basic_string<Char> make_env_string(const Container & value)
{
    std::size_t sz = 0;
    for (auto & v : value)
        sz += make_env_string_size(v);

    std::basic_string<Char> s;
    s.reserve(sz); //+1 for ;, end doesn't have one.

    for (auto & val : value)
        (s += val) += api::env_seperator<Char>();

    s.resize(s.size() -1); //remove last ';'
    return s;
}


template<typename Char>
struct env_set
{
    using string_type = std::basic_string<Char>;
    string_type key;
    string_type value;
};

template<typename Char>
struct env_append
{
    using string_type = std::basic_string<Char>;
    string_type key;
    string_type value;
};



template<typename Char>
struct env_reset
{
    using string_type = std::basic_string<Char>;
    string_type key;
};


template<> struct is_wchar_t<env_set<wchar_t>>           : std::true_type {};
template<> struct is_wchar_t<env_append<wchar_t>>        : std::true_type {};
template<> struct is_wchar_t<env_reset<wchar_t>>         : std::true_type {};
template<> struct is_wchar_t<basic_environment<wchar_t>> : std::true_type {};


template<>
struct char_converter<char, env_set<wchar_t>>
{
    static env_set<char> conv(const env_set<wchar_t> & in)
    {
        return {::boost::process::v1::detail::convert(in.key),
                ::boost::process::v1::detail::convert(in.value)};
    }
};

template<>
struct char_converter<wchar_t, env_set<char>>
{
    static env_set<wchar_t> conv(const env_set<char> & in)
    {
        return {::boost::process::v1::detail::convert(in.key),
                ::boost::process::v1::detail::convert(in.value)};
    }
};

template<>
struct char_converter<char, env_append<wchar_t>>
{
    static env_append<char> conv(const env_append<wchar_t> & in)
    {
        return {::boost::process::v1::detail::convert(in.key),
                ::boost::process::v1::detail::convert(in.value)};
    }
};

template<>
struct char_converter<wchar_t, env_append<char>>
{
    static env_append<wchar_t> conv(const env_append<char> & in)
    {
        return {::boost::process::v1::detail::convert(in.key),
                ::boost::process::v1::detail::convert(in.value)};
    }
};

template<>
struct char_converter<char, env_reset<wchar_t>>
{
    static env_reset<char> conv(const env_reset<wchar_t> & in)
    {
        return {::boost::process::v1::detail::convert(in.key)};
    }
};

template<>
struct char_converter<wchar_t, env_reset<char>>
{
    static env_reset<wchar_t> conv(const env_reset<char> & in)
    {
        return {::boost::process::v1::detail::convert(in.key)};
    }
};


template<typename Char>
struct env_init
{
    basic_environment<Char> env;
};

template<>
struct char_converter<char, env_init<wchar_t>>
{
    static env_init<char> conv(const env_init<wchar_t> & in)
    {
        return {basic_environment<char>(in.env)};
    }
};

template<>
struct char_converter<wchar_t, env_init<char>>
{
    static env_init<wchar_t> conv(const env_init<char> & in)
    {
        return {basic_environment<wchar_t>(in.env)};
    }
};

template<>
struct char_converter<char, basic_environment<wchar_t>>
{
    static basic_environment<char> conv(const basic_environment<wchar_t> & in)
    {
        return { basic_environment<char>(in) };
    }
};

template<>
struct char_converter<wchar_t, basic_environment<char>>
{
    static basic_environment<wchar_t> conv(const basic_environment<char> & in)
    {
        return { basic_environment<wchar_t>(in) };
    }
};

template<typename Char>
struct env_proxy
{
    using string_type = std::basic_string<Char>;
    string_type key;


    env_set<Char> operator=(const string_type & value)
    {
        return {std::move(key), value};
    }
    env_set<Char> operator=(const std::vector<string_type> & value)
    {
        return {std::move(key), make_env_string<Char>(value)};
    }
    env_set<Char> operator=(const std::initializer_list<const Char*> & value)
    {
        return {std::move(key), make_env_string<Char>(value)};
    }

    env_append<Char> operator+=(const string_type & value)
    {
        return {std::move(key), value};
    }
    env_append<Char> operator+=(const std::vector<string_type> & value)
    {
        return {std::move(key), make_env_string<Char>(value)};
    }
    env_append<Char> operator+=(const std::initializer_list<const Char*> & value)
    {
        return {std::move(key), make_env_string<Char>(value)};
    }
    env_reset<Char> operator=(boost::none_t)
    {
        return {std::move(key)};
    }
};

struct env_
{
    constexpr env_() {};

    template<typename Char>
    env_set<Char> operator()(const std::basic_string<Char> & key,
                             const std::basic_string<Char> & value) const
    {
        return {key, value};
    }
    template<typename Char>
    env_set<Char> operator()(const std::basic_string<Char> & key,
                                const std::vector<std::basic_string<Char>> & value) const
    {
        return {key, make_env_string<Char>(value)};
    }
    template<typename Char>
    env_set<Char> operator()(const std::basic_string<Char> & key,
                             const std::initializer_list<Char*> & value) const
    {
        return {key, make_env_string<Char>(value)};
    }
    template<typename Char>
    env_reset<Char> operator()(const std::basic_string<Char> & key, boost::none_t)
    {
        return {key};
    }
    template<typename Char>
    env_proxy<Char> operator[](const std::basic_string<Char> & key) const
    {
        return {key};
    }
    template<typename Char>
    env_proxy<Char> operator[](const Char* key) const
    {
        return {key};
    }
    template<typename Char>
    env_init<Char> operator()(const basic_environment<Char> & env) const
    {
        return {env};
    }
    template<typename Char>
    env_init<Char> operator= (const basic_environment<Char> & env) const
    {
        return {env};
    }
};

template<typename Char>
struct env_builder
{
    basic_environment<Char> env;
    env_builder() : env{basic_native_environment<Char>()} {}

    void operator()(const basic_environment<Char> & e)
    {
        env = e;
    }

    void operator()(env_init<Char> & ei)
    {
        env = std::move(ei.env);
    }
    void operator()(env_set<Char> & es)
    {
        env[es.key] = es.value;
    }
    void operator()(env_reset<Char> & es)
    {
        env.erase(es.key);
    }
    template<typename T>
    void operator()(env_append<T> & es)
    {
        env[es.key] += es.value;
    }

    typedef api::env_init<Char> result_type;
    api::env_init<Char> get_initializer()
    {
        return api::env_init<Char>(std::move(env));
    }
};

template<>
struct initializer_builder<env_tag<char>>
{
    typedef env_builder<char> type;
};

template<>
struct initializer_builder<env_tag<wchar_t>>
{
    typedef env_builder<wchar_t> type;
};

}

/**

The `env` property provides a functional way to modify the environment used by
the child process. If none is passed the environment is inherited from the father
process. Appending means that the environment will be interpreted as a ';' or ':'
separated list as used in `PATH`.

On both `posix` and `windows` the environment variables can be lists of strings,
separated by ';'. This is typically used for the `PATH` variable.

By default the environment will be inherited from the launching process,
which is also true if environment are modified with this initializer.

\section env_details Details

\subsection env_operations Operations

\subsubsection env_set_var Setting variables

To set a variable `id` the value `value` the following syntax can be used.

\code{.cpp}
env[id] = value;
env(id, value);
\endcode

`std::initializer_list` is among the allowed types, so the following syntax is also possible.

\code{.cpp}
env[id] = {value1, value2};
env(id, {value1, value2});
\endcode

\note Creates the variable if it does not exist.

The following lists contain possible value types, with `char_type` being either `char` or `wchar_t`
for both `id` and `value`.

\paragraph id id

 - `std::basic_string<char_type>`
 - `const char_type *`

\paragraph env_set_var_value value

 - `std::basic_string<char_type>`
 - `const char_type * `
 - `std::initializer_list<const char_type *>`
 - `std::vector<std::basic_string<char_type>>`


\note Using `std::vector` or `std::initializer_list`

\subsubsection env_append_var Append variables

Appending means, that a variable will be interpreted as a
To append  a variable `id` the value `value` the following syntax can be used:

\code{.cpp}
env[id] += value;
\endcode

`std::initializer_list` is among the allowed types, so the following syntax is also possible.

\code{.cpp}
env[id] += {value1, value2};
\endcode

\note Creates the variable if it does not exist.

The following lists contain possible value types, with `char_type` being either `char` or `wchar_t`
for both `id` and `value`.

\paragraph env_append_var_id id

 - `std::basic_string<char_type>`
 - `const char_type *`

\paragraph env_append_var_value value

 - `std::basic_string<char_type>`
 - `const char_type *`
 - `std::initializer_list<const char_type *>`
 - `std::vector<std::basic_string<char_type>>`


\subsubsection env_reset Reset variables

Resetting single variables can be done in the following way:

\code{.cpp}
env[id] = boost::none;
env(id, boost::none);
\endcode

\note This does not set the value empty, but removes it from the list.

The following lists contain possible value types, with `char_type` being either `char` or `wchar_t`:

\paragraph env_reset_var_id id

 - `std::basic_string<char_type>`
 - `const char_type *`

\subsubsection env_init Initialize the environment

The whole environment can be initialized from an object of type
\xmlonly <classname>boost::process::v1::environment</classname> \endxmlonly

\code{.cpp}
env=env;
env(env);
\endcode

\note The passed `environment` can also be default-constructed to get an empty environment.

\paragraph env_init_var_id id

 - `std::basic_string<char_type>`
 - `const char_type *`

\paragraph env_init_var_value value

 - `boost::process::v1::basic_environment<char_type>`

\subsection env_example Example

\code{.cpp}
spawn("b2", env["PATH"]+="F:/boost", env["SOME_VAR"]=boost::none, env["NEW_VAR"]="VALUE");
\endcode

If the overload style should be done by passing an instance of
\xmlonly <classname>boost::process::v1::environment</classname> \endxmlonly
the above example would look like this.

\code{.cpp}
environment e = this_process::environment();
e["PATH"]   += "F:/boost";
e.erase("SOME_VAR");
e["NEW_VAR"] = "VALUE";
spawn("b2", e);
\endcode

\warning Passing an empty environment will cause undefined behaviour.

 */
constexpr boost::process::v1::detail::env_ env{};


}}}

#endif /* INCLUDE_BOOST_PROCESS_DETAIL_ENV_HPP_ */
