// 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_WINDOWS_ENV_STORAGE_HPP_
#define BOOST_PROCESS_DETAIL_WINDOWS_ENV_STORAGE_HPP_

#include <string>
#include <vector>
#include <unordered_map>
#include <boost/winapi/error_codes.hpp>
#include <boost/winapi/environment.hpp>
#include <boost/winapi/get_current_process.hpp>
#include <boost/winapi/get_current_process_id.hpp>
#include <boost/process/detail/config.hpp>
#include <algorithm>
#include <boost/process/locale.hpp>

namespace boost { namespace process { namespace detail { namespace windows {

template<typename Char>
class native_environment_impl
{
    static void _deleter(Char* p) {boost::winapi::free_environment_strings(p);};
    std::unique_ptr<Char[], void(*)(Char*)> _buf{boost::winapi::get_environment_strings<Char>(), &native_environment_impl::_deleter};
    static inline std::vector<Char*> _load_var(Char* p);
    std::vector<Char*> _env_arr{_load_var(_buf.get())};
public:
    using char_type = Char;
    using pointer_type = const char_type*;
    using string_type = std::basic_string<char_type>;
    using native_handle_type = pointer_type;
    void reload()
    {
        _buf.reset(boost::winapi::get_environment_strings<Char>());
        _env_arr = _load_var(_buf.get());
        _env_impl = &*_env_arr.begin();
    }

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

    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;
    Char ** _env_impl = &*_env_arr.begin();

    native_handle_type native_handle() const {return _buf.get();}
};

template<typename Char>
inline auto native_environment_impl<Char>::get(const pointer_type id) -> string_type
{
    Char buf[4096];
    auto size = boost::winapi::get_environment_variable(id, buf, sizeof(buf));
    if (size == 0) //failed
    {
        auto err =  ::boost::winapi::GetLastError();
        if (err == ::boost::winapi::ERROR_ENVVAR_NOT_FOUND_)//well, then we consider that an empty value
            return "";
        else
            throw process_error(std::error_code(err, std::system_category()),
                               "GetEnvironmentVariable() failed");
    }

    if (size == sizeof(buf)) //the return size gives the size without the null, so I know this went wrong
    {
        /*limit defined here https://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx
         * but I used 32768 so it is a multiple of 4096.
         */
        constexpr static std::size_t max_size = 32768;
        //Handle variables longer then buf.
        std::size_t buf_size = sizeof(buf);
        while (buf_size <= max_size)
        {
            std::vector<Char> buf(buf_size);
            auto size = boost::winapi::get_environment_variable(id, buf.data(), buf.size());

            if (size == buf_size) //buffer to small
                buf_size *= 2;
            else if (size == 0)
                ::boost::process::detail::throw_last_error("GetEnvironmentVariable() failed");
            else
                return std::basic_string<Char>(
                        buf.data(), buf.data()+ size + 1);

        }

    }
    return std::basic_string<Char>(buf, buf+size+1);
}

template<typename Char>
inline void native_environment_impl<Char>::set(const pointer_type id, const pointer_type value)
{
    boost::winapi::set_environment_variable(id, value);
}

template<typename Char>
inline void  native_environment_impl<Char>::reset(const pointer_type id)
{
    boost::winapi::set_environment_variable(id, nullptr);
}

template<typename Char>
std::vector<Char*> native_environment_impl<Char>::_load_var(Char* p)
{
    std::vector<Char*> ret;
    if (*p != null_char<Char>())
    {
        ret.push_back(p);
        while ((*p != null_char<Char>()) || (*(p+1) !=  null_char<Char>()))
        {
            if (*p==null_char<Char>())
            {
                p++;
                ret.push_back(p);
            }
            else
                p++;
        }
    }
    p++;
    ret.push_back(nullptr);

    return ret;
}


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

    std::size_t size() const { return _data.size();}

    void reload()
    {
        _env_arr = _load_var(_data.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);

    inline 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 && rhs)
        :    _data(std::move(rhs._data)),
            _env_arr(std::move(rhs._env_arr)),
            _env_impl(_env_arr.data())
    {
    }
    basic_environment_impl &operator=(basic_environment_impl && rhs)
    {
        _data = std::move(rhs._data);
        //reload();
        _env_arr  = std::move(rhs._env_arr);
        _env_impl = _env_arr.data();

        return *this;
    }
    basic_environment_impl & operator=(const basic_environment_impl& rhs)
    {
        _data = rhs._data;
        reload();
        return *this;
    }

    template<typename CharR>
    explicit inline  basic_environment_impl(
                const basic_environment_impl<CharR>& rhs,
                const ::boost::process::codecvt_type & cv = ::boost::process::codecvt())
        : _data(::boost::process::detail::convert(rhs._data, cv))
    {
    }

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

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

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


template<typename Char>
basic_environment_impl<Char>::basic_environment_impl(const native_environment_impl<Char> & nei)
{
    auto beg = nei.native_handle();
    auto p   = beg;
    while ((*p != null_char<Char>()) || (*(p+1) !=  null_char<Char>()))
        p++;
    p++; //pointing to the second nullchar
    p++; //to get the pointer behing the second nullchar, so it's end.

    this->_data.assign(beg, p);
    this->reload();
}


template<typename Char>
inline auto basic_environment_impl<Char>::get(const string_type &id) -> string_type
{

    if (std::equal(id.begin(), id.end(), _data.begin()) && (_data[id.size()] == equal_sign<Char>()))
        return string_type(_data.data()); //null-char is handled by the string.

    std::vector<Char> seq = {'\0'}; //using a vector, because strings might cause problems with nullchars
    seq.insert(seq.end(), id.begin(), id.end());
    seq.push_back('=');

    auto itr = std::search(_data.begin(), _data.end(), seq.begin(), seq.end());

    if (itr == _data.end()) //not found
        return "";

    itr += seq.size(); //advance to the value behind the '='; the std::string will take care of finding the null-char.

    return string_type(&*itr);
}

template<typename Char>
inline void basic_environment_impl<Char>::set(const string_type &id, const string_type &value)
{
    reset(id);

    std::vector<Char> insertion;

    insertion.insert(insertion.end(), id.begin(),    id.end());
    insertion.push_back('=');
    insertion.insert(insertion.end(), value.begin(), value.end());
    insertion.push_back('\0');

    _data.insert(_data.end() -1, insertion.begin(), insertion.end());

    reload();
}

template<typename Char>
inline void  basic_environment_impl<Char>::reset(const string_type &id)
{
    //ok, we need to check the size of data first
    if (id.size() >= _data.size()) //ok, so it's impossible id is in there.
        return;

    //check if it's the first one, spares us the search.
    if (std::equal(id.begin(), id.end(), _data.begin()) && (_data[id.size()] == equal_sign<Char>()))
    {
        auto beg = _data.begin();
        auto end = beg;

        while (*end != '\0')
           end++;

        end++; //to point behind the last null-char

        _data.erase(beg, end); //and remove the thingy

    }

    std::vector<Char> seq = {'\0'}; //using a vector, because strings might cause problems with nullchars
    seq.insert(seq.end(), id.begin(), id.end());
    seq.push_back('=');

    auto itr = std::search(_data.begin(), _data.end(), seq.begin(), seq.end());

    if (itr == _data.end())
        return;//nothing to return if it's empty anyway...

    auto end = itr;

    while (*++end != '\0');


    _data.erase(itr, end);//and remove it
    reload();


}

template<typename Char>
std::vector<Char*> basic_environment_impl<Char>::_load_var(Char* p)
{
    std::vector<Char*> ret;
    if (*p != null_char<Char>())
    {
        ret.push_back(p);
        while ((*p != null_char<Char>()) || (*(p+1) !=  null_char<Char>()))
        {
            if (*p==null_char<Char>())
            {
                p++;
                ret.push_back(p);
            }
            else
                p++;
        }
    }
    p++;
    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';'; }

inline int   get_id()         {return boost::winapi::GetCurrentProcessId();}
inline void* native_handle()  {return boost::winapi::GetCurrentProcess(); }

typedef void* native_handle_t;

}

}
}
}




#endif /* BOOST_PROCESS_DETAIL_WINDOWS_ENV_STORAGE_HPP_ */