#include <iostream>
#include <string>
#include <vector>

#include <boost/locale.hpp>

namespace yekneb
{

namespace detail
{
namespace string_cast
{

inline bool IsUTF8(const std::locale &loc)
{
    std::string locName = loc.name();
    if (! locName.empty() && std::string::npos != locName.find("UTF-8"))
    {
        return true;
    }
    return false;
}

inline std::string w2s(const std::wstring& ws, const std::locale& loc)
{
    typedef std::codecvt<wchar_t, char, std::mbstate_t> converter_type;
    typedef std::ctype<wchar_t> wchar_facet;
    std::string return_value;
    if (ws.empty())
    {
        return "";
    }
    if (IsUTF8(loc))
    {
        return_value = boost::locale::conv::utf_to_utf<char>(ws);
        if (! return_value.empty())
        {
            return return_value;
        }
    }
    const wchar_t* from = ws.c_str();
    size_t len = ws.length();
    size_t converterMaxLength = 6;
    size_t vectorSize = ((len + 6) * converterMaxLength);
    if (std::has_facet<converter_type>(loc))
    {
        const converter_type& converter = std::use_facet<converter_type>(loc);
        if (converter.always_noconv())
        {
            converterMaxLength = converter.max_length();
            if (converterMaxLength != 6)
            {
                vectorSize = ((len + 6) * converterMaxLength);
            }
            std::mbstate_t state;
            const wchar_t* from_next = nullptr;
            std::vector<char> to(vectorSize, 0);
            std::vector<char>::pointer toPtr = to.data();
            std::vector<char>::pointer to_next = nullptr;
            const converter_type::result result = converter.out(
                state, from, from + len, from_next,
                toPtr, toPtr + vectorSize, to_next);
            if (
              (converter_type::ok == result || converter_type::noconv == result)
              && 0 != toPtr[0]
              )
            {
              return_value.assign(toPtr, to_next);
            }
        }
    }
    if (return_value.empty() && std::has_facet<wchar_facet>(loc))
    {
        std::vector<char> to(vectorSize, 0);
        std::vector<char>::pointer toPtr = to.data();
        const wchar_facet& facet = std::use_facet<wchar_facet>(loc);
        if (facet.narrow(from, from + len, '?', toPtr) != 0)
        {
            return_value = toPtr;
        }
    }
    return return_value;
}

inline std::wstring s2w(const std::string& s, const std::locale& loc)
{
    typedef std::ctype<wchar_t> wchar_facet;
    std::wstring return_value;
    if (s.empty())
    {
        return L"";
    }
    if (std::has_facet<wchar_facet>(loc))
    {
        std::vector<wchar_t> to(s.size() + 2, 0);
        std::vector<wchar_t>::pointer toPtr = to.data();
        const wchar_facet& facet = std::use_facet<wchar_facet>(loc);
        if (facet.widen(s.c_str(), s.c_str() + s.size(), toPtr) != 0)
        {
            return_value = to.data();
        }
    }
    return return_value;
}

}
}

template<typename Target, typename Source>
inline Target string_cast(const Source& source)
{
    return source;
}

template<>
inline std::wstring string_cast(const std::string& source)
{
    std::locale loc;
    return ::yekneb::detail::string_cast::s2w(source, loc);
}

template<>
inline std::string string_cast(const std::wstring& source)
{
    std::locale loc;
    return ::yekneb::detail::string_cast::w2s(source, loc);
}

}

int main()
{
    std::string s1("Buenos días.");
    std::wstring ws1 = yekneb::string_cast<std::wstring>(s1);
    std::wcout << ws1 << L"\n";
    
    std::wstring ws2(L"Buenos días.");
    std::string s2 = yekneb::string_cast<std::string>(ws2);
    std::cout << s1 << "\n";
    
    return 0;
}
