/*
Author: Jelle Geerts

Usage of the works is permitted provided that this instrument is
retained with the works, so that any entity that uses the works is
notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
*/

#include "nowide.h"

#include <windows.h>

#ifdef DEBUG
# include <cstdio>
#endif
#include <vector>

static bool is_windows_NT6_or_higher()
{
    static int cached_result = -1;
    if (cached_result < 0)
    {
        OSVERSIONINFO v;
        v.dwOSVersionInfoSize = sizeof(v);
        if (!GetVersionEx(&v))
            cached_result = true;
        else
            cached_result = v.dwMajorVersion >= 6;
#ifdef DEBUG
        if (!cached_result)
        {
            std::fprintf(
                stderr,
                "WARNING: Pre-NT6 operating system detected.\n"
                "         nowide::narrow() may silently drop illegal code points.\n");
        }
#endif
    }
    return cached_result != false;
}

namespace nowide
{

ConversionError::ConversionError(const std::string & what_arg)
    : std::runtime_error(what_arg)
{
}

ConversionError::ConversionError(const char * what_arg)
    : std::runtime_error(what_arg)
{
}

std::string narrow(const std::wstring & s)
{
    return narrow(s.c_str());
}

std::string narrow(const wchar_t * s)
{
    const UINT code_page = CP_UTF8;
#if !defined(WC_ERR_INVALID_CHARS) || WC_ERR_INVALID_CHARS != 0x80
# undef WC_ERR_INVALID_CHARS
# define WC_ERR_INVALID_CHARS 0x80
#endif
    const DWORD flags = is_windows_NT6_or_higher() ? WC_ERR_INVALID_CHARS : 0;
    size_t buffer_size = WideCharToMultiByte(
        code_page, flags, s, -1, NULL, 0, NULL, NULL);
    if (buffer_size == 0)
        throw ConversionError("invalid input string");
    std::vector<char> buffer(buffer_size);
    if (WideCharToMultiByte(
            code_page, flags, s, -1, &buffer[0], buffer.size(), NULL,
            NULL) == 0)
    {
        throw ConversionError("invalid input string");
    }
    return std::string(&buffer[0]);
}

std::wstring widen(const std::string & s)
{
    return widen(s.c_str());
}

std::wstring widen(const char * s)
{
    const UINT code_page = CP_UTF8;
    const DWORD flags = MB_ERR_INVALID_CHARS;
    size_t buffer_size = MultiByteToWideChar(
        code_page, flags, s, -1, NULL, 0);
    if (buffer_size == 0)
        throw ConversionError("invalid input string");
    std::vector<WCHAR> buffer(buffer_size);
    if (MultiByteToWideChar(
            code_page, flags, s, -1, &buffer[0], buffer.size()) == 0)
    {
        throw ConversionError("invalid input string");
    }
    return std::wstring(&buffer[0]);
}

}
