// 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_POSIX_ENVIRONMENT_HPP_
#define BOOST_PROCESS_DETAIL_POSIX_ENVIRONMENT_HPP_

#include <string>
#include <vector>
#include <unordered_map>
#include <boost/process/v1/detail/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <boost/process/v1/locale.hpp>


namespace boost { namespace process { BOOST_PROCESS_V1_INLINE namespace v1 { namespace detail { namespace posix {

template<typename Char>
class native_environment_impl
{
    static std::vector<std::basic_string<Char>>  _load()
    {
        std::vector<std::basic_string<Char>> val;
        auto p = environ;
        while (*p != nullptr)
        {
            std::string str = *p;
            val.push_back(::boost::process::v1::detail::convert(str));
            p++;
        }
        return val;
    }
    static std::vector<Char*> _load_var(std::vector<std::basic_string<Char>> & vec)
    {
        std::vector<Char*> val;
        val.resize(vec.size() + 1);
        std::transform(vec.begin(), vec.end(), val.begin(),
                [](std::basic_string<Char> & str)
                {
                    return &str.front();
                });
        val.back() = nullptr;
        return val;
    }
    std::vector<std::basic_string<Char>> _buffer = _load();
    std::vector<Char*> _impl = _load_var(_buffer);
public:
    using char_type = Char;
    using pointer_type = const char_type*;
    using string_type = std::basic_string<char_type>;
    using native_handle_type = char_type **;

    void reload()
    {
        _buffer = _load();
        _impl = _load_var(_buffer);
        _env_impl = _impl.data();
    }

    string_type get(const pointer_type id) { return get(string_type(id)); }
    void        set(const pointer_type id, const pointer_type value)
    {
        set(string_type(id), string_type(value));
    }
    void      reset(const pointer_type id) { reset(string_type(id)); }

    string_type get(const string_type & id)
    {
        std::string id_c = ::boost::process::v1::detail::convert(id);
        std::string g = ::getenv(id_c.c_str());
        return ::boost::process::v1::detail::convert(g.c_str());
    }
    void        set(const string_type & id, const string_type & value)
    {
        std::string id_c    = ::boost::process::v1::detail::convert(id.c_str());
        std::string value_c = ::boost::process::v1::detail::convert(value.c_str());
        auto res = ::setenv(id_c.c_str(), value_c.c_str(), true);
        if (res != 0)
            boost::process::v1::detail::throw_last_error();
    }
    void      reset(const string_type & id)
    {
        std::string id_c = ::boost::process::v1::detail::convert(id.c_str());
        auto res = ::unsetenv(id_c.c_str());
        if (res != 0)
            ::boost::process::v1::detail::throw_last_error();
    }

    native_environment_impl() = default;
    native_environment_impl(const native_environment_impl& ) = delete;
    native_environment_impl(native_environment_impl && ) = default;
    native_environment_impl & operator=(const native_environment_impl& ) = delete;
    native_environment_impl & operator=(native_environment_impl && ) = default;
    native_handle_type _env_impl = _impl.data();

    native_handle_type native_handle() const {return _env_impl;}
};

template<>
class native_environment_impl<char>
{
public:
    using char_type = char;
    using pointer_type = const char_type*;
    using string_type = std::basic_string<char_type>;
    using native_handle_type = char_type **;

    void reload() {this->_env_impl = environ;}

    string_type get(const pointer_type id) { return getenv(id); }
    void        set(const pointer_type id, const pointer_type value)
    {
        auto res = ::setenv(id, value, 1);
        if (res != 0)
            boost::process::v1::detail::throw_last_error();
        reload();
    }
    void      reset(const pointer_type id)
    {
        auto res = ::unsetenv(id);
        if (res != 0)
            boost::process::v1::detail::throw_last_error();
        reload();
    }

    string_type get(const string_type & id) {return get(id.c_str());}
    void        set(const string_type & id, const string_type & value) {set(id.c_str(), value.c_str()); }
    void      reset(const string_type & id) {reset(id.c_str());}

    native_environment_impl() = default;
    native_environment_impl(const native_environment_impl& ) = delete;
    native_environment_impl(native_environment_impl && ) = default;
    native_environment_impl & operator=(const native_environment_impl& ) = delete;
    native_environment_impl & operator=(native_environment_impl && ) = default;
    native_handle_type _env_impl = environ;

    native_handle_type native_handle() const {return environ;}
};



template<typename Char>
struct basic_environment_impl
{
    std::vector<std::basic_string<Char>> _data {};
    static std::vector<Char*> _load_var(std::vector<std::basic_string<Char>> & data);
    std::vector<Char*> _env_arr{_load_var(_data)};
public:
    using char_type = Char;
    using pointer_type = const char_type*;
    using string_type = std::basic_string<char_type>;
    using native_handle_type = Char**;
    void reload()
    {
        _env_arr = _load_var(_data);
        _env_impl = _env_arr.data();
    }

    string_type get(const pointer_type id) {return get(string_type(id));}
    void        set(const pointer_type id, const pointer_type value) {set(string_type(id), value);}
    void      reset(const pointer_type id)  {reset(string_type(id));}

    string_type get(const string_type & id);
    void        set(const string_type & id, const string_type & value);
    void      reset(const string_type & id);

    basic_environment_impl(const native_environment_impl<Char> & nei);
    basic_environment_impl() = default;
    basic_environment_impl(const basic_environment_impl& rhs)
        : _data(rhs._data)
    {

    }
    basic_environment_impl(basic_environment_impl && ) = default;
    basic_environment_impl & operator=(const basic_environment_impl& rhs)
    {
        _data = rhs._data;
        _env_arr = _load_var(_data);
        _env_impl = &*_env_arr.begin();
        return *this;
    }
    basic_environment_impl & operator=(basic_environment_impl && ) = default;

    template<typename CharR>
    explicit inline  basic_environment_impl(
                const basic_environment_impl<CharR>& rhs,
                const ::boost::process::v1::codecvt_type & cv = ::boost::process::v1::codecvt())
        : _data(rhs._data.size())
    {
        std::transform(rhs._data.begin(), rhs._data.end(), _data.begin(),
                [&](const std::basic_string<CharR> & st)
                {
                    return ::boost::process::v1::detail::convert(st, cv);
                }

            );
        reload();
    }

    template<typename CharR>
    basic_environment_impl & operator=(const basic_environment_impl<CharR>& rhs)
    {
        _data = ::boost::process::v1::detail::convert(rhs._data);
        _env_arr = _load_var(&*_data.begin());
        _env_impl = &*_env_arr.begin();
        return *this;
    }

    Char ** _env_impl = &*_env_arr.data();

    native_handle_type native_handle() const {return &_data.front();}
};


template<typename Char>
basic_environment_impl<Char>::basic_environment_impl(const native_environment_impl<Char> & nei)
{
    auto beg = nei.native_handle();

    auto end = beg;
    while (*end != nullptr)
        end++;
    this->_data.assign(beg, end);
    reload();
}


template<typename Char>
inline auto basic_environment_impl<Char>::get(const string_type &id) -> string_type
{
    auto itr = std::find_if(_data.begin(), _data.end(),
            [&](const string_type & st) -> bool
            {
                if (st.size() <= id.size())
                    return false;
                return std::equal(id.begin(), id.end(), st.begin()) && (st[id.size()] == equal_sign<Char>());
            }
        );

    if (itr == _data.end())
    {
        return "";
    }
    else return
        itr->data() + id.size(); //id=Thingy -> +2 points to T
}

template<typename Char>
inline void basic_environment_impl<Char>::set(const string_type &id, const string_type &value)
{
    auto itr = std::find_if(_data.begin(), _data.end(),
        [&](const string_type & st) -> bool
        {
            if (st.size() <= id.size())
                return false;
            return std::equal(id.begin(), id.end(), st.begin()) && (st[id.size()] == equal_sign<Char>());
        }
    );

    if (itr != _data.end())
        *itr = id + equal_sign<Char>() + value;
    else
        _data.push_back(id + equal_sign<Char>() + value);

    reload();
}

template<typename Char>
inline void  basic_environment_impl<Char>::reset(const string_type &id)
{
    auto itr = std::find_if(_data.begin(), _data.end(),
        [&](const string_type & st) -> bool
        {
            if (st.size() <= id.size())
                return false;
            return std::equal(id.begin(), id.end(), st.begin()) && (st[id.size()] == equal_sign<Char>());
        }
    );
    if (itr != _data.end())
    {
        _data.erase(itr);//and remove it
    }

    reload();


}

template<typename Char>
std::vector<Char*> basic_environment_impl<Char>::_load_var(std::vector<std::basic_string<Char>> & data)
{
    std::vector<Char*> ret;
    ret.reserve(data.size() +1);

    for (auto & val : data)
    {
        if (val.empty())
            val.push_back(0);
        ret.push_back(&val.front());
    }

    ret.push_back(nullptr);
    return ret;
}

template<typename T> constexpr T env_seperator();
template<> constexpr   char   env_seperator() {return  ':'; }
template<> constexpr  wchar_t env_seperator() {return L':'; }


typedef int native_handle_t;

inline int  get_id()        {return getpid(); }
inline int native_handle()  {return getpid(); }

}

}
}
}
}



#endif /* BOOST_PROCESS_DETAIL_WINDOWS_ENV_STORAGE_HPP_ */
