/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2014 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif


static ON_CRT_locale_t ON_CRT_C_locale()
{
  static ON_CRT_locale_t ON_C_locale = 0;

  if (0 == ON_C_locale)
  {
#if defined(ON_RUNTIME_WIN)
    ON_C_locale = _create_locale(LC_ALL, "C");
#elif defined(ON_RUNTIME_APPLE)
    ON_C_locale = _c_locale;
#elif defined(ON_RUNTIME_ANDROID)
    ON_C_locale = 0;
#else
    ON_C_locale = _create_locale(category, locale);
#endif
  }
  
  return ON_C_locale;
}

static ON_CRT_locale_t ON_CRT_create_locale_ALL( const char * locale )
{
  if ( nullptr == locale || 0 == locale[0] )
    return ON_CRT_C_locale();

  if (
    ('C' == locale[0] || 'c' == locale[0])
    && 0 == locale[1]
    )
    return ON_CRT_C_locale();

  if (
    ('P' == locale[0] || 'p' == locale[0])
    && ('O' == locale[1] || 'o' == locale[1])
    && ('S' == locale[2] || 's' == locale[2])
    && ('I' == locale[3] || 'i' == locale[3])
    && ('X' == locale[4] || 'x' == locale[4])
    && 0 == locale[5]
    )
    return ON_CRT_C_locale();  
  
#if defined(ON_RUNTIME_WIN)
  return _create_locale(LC_ALL, locale);
#elif defined(ON_RUNTIME_APPLE)
  // Apple locale names are <language>_<REGION> (underbar)
  char language_subtag[32] = { 0 };
  const size_t language_subtag_capacity = sizeof(language_subtag)/sizeof(language_subtag[0]);
  char region_subtag[32] = { 0 };
  const size_t region_subtag_capacity = sizeof(region_subtag)/sizeof(region_subtag[0]);
  if ( false == ON_Locale::ParseName(
    locale,-1,
    language_subtag, language_subtag_capacity,
    nullptr, 0,
    nullptr, 0,
    region_subtag, region_subtag_capacity,
    nullptr, 0
    ))
  {
    ON_ERROR("locale_name is not valid");
    return ON_CRT_C_locale();
  }
  char apple_name[sizeof(language_subtag) / sizeof(language_subtag[0]) + sizeof(region_subtag) / sizeof(region_subtag[0]) + 2] = { 0 };
  for (size_t i = 0; i < language_subtag_capacity; i++)
  {
    if (0 == (apple_name[i] = language_subtag[i]))
    {
      if (0 != region_subtag[0])
      {
        apple_name[i++] = '_'; // Apple needs an underbar here
        for (size_t j = 0; j < region_subtag_capacity; j++, i++)
        {
          if (0 == (apple_name[i] = region_subtag[j]))
            break;
        }
      }
      break;
    }
  }
  return newlocale(LC_ALL_MASK, apple_name, ON_CRT_C_locale() );
#elif defined(ON_RUNTIME_ANDROID)
  return 0;
#else
  return _create_locale(category, locale);
#endif
}

ON_Locale::ON_Locale() ON_NOEXCEPT
{
  memset(this, 0, sizeof(*this));
  m_numeric_locale = ON_CRT_C_locale();
  m_string_coll_map_locale = ON_CRT_C_locale();
}

ON__UINT32 ON_Locale::WindowsLCID() const
{
  return m_windows_lcid;
}

static char* LocaleStringBuilder(
  char c,
  const char* source,
  size_t source_capacity,
  char* dest,
  char* dest_end
  )
{
  if ( nullptr == dest || nullptr == dest_end || nullptr == source || source_capacity <= 0 )
    return nullptr;
  if ( 0 != source[source_capacity - 1])
    return nullptr;
  if (dest < dest_end)
  {
    if (0 == source[0])
    {
      *dest = 0;
      return dest;
    }
    if (c > 0 )
      *dest++ = c;
    while (dest < dest_end)
    {
      if (0 == (*dest = *source++))
        return dest;
      dest++;
    }
  }
  return nullptr;
}

#define ON_LOCALE_SUBTAG(c,t) c,t,sizeof(t)/sizeof(t[0])

static char* LocaleStringBuilderDestEnd(
  char* buffer,
  size_t buffer_capacity
  )
{
  if (buffer_capacity > 0 && nullptr != buffer)
  {
    memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
    return buffer+buffer_capacity;
  }
  return nullptr;
}

static wchar_t* LocalWideStringBuider(
  const char* cbuffer,
  wchar_t* buffer,
  size_t buffer_capacity
  )
{
  if (buffer_capacity > 0 && nullptr != buffer)
  {
    memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
    if (nullptr != cbuffer)
    {
      wchar_t* dest = buffer;
      wchar_t* dest_end = dest + buffer_capacity;
      while (dest < dest_end)
      {
        if (0 == (*dest++ = (wchar_t)(*cbuffer++)))
          return buffer;
      }
      memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
    }
  }
  return nullptr;
}

const char* ON_Locale::GetBCP47LanguageTag(
  char* buffer,
  size_t buffer_capacity
  ) const
{
  if ( nullptr == buffer || buffer_capacity <= 0 )
    return nullptr;

  char* dest = buffer;
  char* dest_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
  const char* source = m_bcp47_language_tag;
  while (dest < dest_end)
  {
    if (0 == (*dest++ = *source++))
      return buffer;
  }
  memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
  return nullptr;
}

const wchar_t* ON_Locale::GetBCP47LanguageTag(
  wchar_t* buffer,
  size_t buffer_capacity
  ) const
{
  char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
  return (LocalWideStringBuider(
    GetBCP47LanguageTag(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
    buffer,
    buffer_capacity
    ));
}


const char* ON_Locale::BCP47LanguageTag() const
{
  return m_bcp47_language_tag;
}

const char* ON_Locale::GetWindowsLocaleName(
  char* buffer,
  size_t buffer_capacity
  ) const
{
  char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
  char* s = buffer;
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_script_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_region_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('_',m_windows_sortorder),s,buffer_end);
  return ( nullptr == s ) ? nullptr : buffer;
}

const wchar_t* ON_Locale::GetWindowsLocaleName(
  wchar_t* buffer,
  size_t buffer_capacity
  ) const
{
  char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
  return (LocalWideStringBuider(
    GetWindowsLocaleName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
    buffer,
    buffer_capacity
    ));
}


const char* ON_Locale::GetAppleLocaleName(
  char* buffer,
  size_t buffer_capacity
  ) const
{
  char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
  char* s = buffer;
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
  // Apple local names omit script


  // Apple local names use an underbar before <REGION>
  // s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_script_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('_',m_region_subtag),s,buffer_end);
  return ( nullptr == s ) ? nullptr : buffer;
}

const wchar_t* ON_Locale::GetAppleLocaleName(
  wchar_t* buffer,
  size_t buffer_capacity
  ) const
{
  char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
  return (LocalWideStringBuider(
    GetAppleLocaleName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
    buffer,
    buffer_capacity
    ));
}

const char* ON_Locale::GetAppleLanguageName(
  char* buffer,
  size_t buffer_capacity
  ) const
{
  char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
  char* s = buffer;
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
  if ( ON_String::EqualOrdinal("zh", 3, buffer, 3, true) || 0 != m_region_subtag[0] )
  {
    // Apple "language" names use "zh-Hans" for "zh-CN" and "zh-Hant" for "zh-TW"
    if ( 0 == m_script_subtag[0] )
    {
      const char* script_subtag = nullptr;
      if ( ON_String::EqualOrdinal("CN", -1, m_region_subtag, -1, true))
        script_subtag = "Hans";
      else if (ON_String::EqualOrdinal("TW", -1, m_region_subtag, -1, true))
        script_subtag = "Hant";
      if (0 != script_subtag)
      {
        s = LocaleStringBuilder('-', script_subtag, 5, s, buffer_end);
        return (nullptr == s) ? nullptr : buffer;
      }
    }
    s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-', m_script_subtag), s, buffer_end);
  }
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-', m_region_subtag), s, buffer_end);
  return ( nullptr == s ) ? nullptr : buffer;
}

const wchar_t* ON_Locale::GetAppleLanguageName(
  wchar_t* buffer,
  size_t buffer_capacity
  ) const
{
  char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
  return (LocalWideStringBuider(
    GetAppleLanguageName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
    buffer,
    buffer_capacity
    ));
}


const char* ON_Locale::LanguageCode() const
{
  return m_language_subtag;
}

const char* ON_Locale::ScriptCode() const
{
  return m_script_subtag;
}

const char* ON_Locale::RegionCode() const
{
  return m_region_subtag;
}

const char* ON_Locale::WindowsSortOrder() const
{
  return m_windows_sortorder;
}


static bool ZeroWideBuffer(
  wchar_t*& buffer,
  size_t& buffer_capacity,
  size_t sizeof_buffer_element
  )
{
  bool rc 
    = (nullptr != buffer && buffer_capacity > 0 && sizeof_buffer_element > 0 )
    || 0 == buffer_capacity;
  if (nullptr == buffer || buffer_capacity <= 0 || sizeof_buffer_element <= 0 )
  {
    buffer = nullptr;
    buffer_capacity = 0;
  }
  else
  {
    memset(buffer,0,buffer_capacity*sizeof_buffer_element);
  }
  return rc;
}

static bool ZeroCharBuffer(
  char*& buffer,
  size_t& buffer_capacity,
  size_t sizeof_buffer_element
  )
{
  bool rc 
    = (nullptr != buffer && buffer_capacity > 0 && sizeof_buffer_element > 0 )
    || 0 == buffer_capacity;
  if (nullptr == buffer || buffer_capacity <= 0 || sizeof_buffer_element <= 0 )
  {
    buffer = nullptr;
    buffer_capacity = 0;
  }
  else
  {
    memset(buffer,0,buffer_capacity*sizeof_buffer_element);
  }
  return rc;
}

static bool IsAlpha(char c)
{
  return (c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' );
}

static bool IsDigit(char c)
{
  return (c >= '0' && c <= '1' );
}

static bool IsAlphaOrDigit(char c)
{
  return IsAlpha(c) || IsDigit(c);
}

static bool IsHyphen(char c)
{
  return ('-' == c);
}

static bool IsUnderbar(char c)
{
  return ('_' == c);
}

static char ToUpper(char c)
{
  return (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;
}

static char ToLower(char c)
{
  return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
}

static bool IsHyphenOrUnderbar(char c)
{
  return IsHyphen(c) || IsUnderbar(c);
}

#if defined(ON_RUNTIME_WIN)
static int ASCIIWideToChar(
  const wchar_t* wASCII,
  size_t wASCII_length,
  char* sASCII,
  size_t sASCII_capacity
  )
{
  for (;;)
  {
    if (nullptr == wASCII || wASCII_length <= 0)
      break;
    if (nullptr == sASCII || sASCII_capacity <= 0)
      break;
    if (sASCII_capacity < wASCII_length)
      break;

    size_t i;
    for (i = 0; i < wASCII_length; i++)
    {
      const wchar_t c = wASCII[i];
      if (c < 0 || c > 127)
        break;
      if (0 == c)
      {
        sASCII[i++] = 0;
        wASCII_length = i;
        break;
      }
      sASCII[i] = (char)c;
    }
    if ( i < wASCII_length  )
      break;

    for(/*empty init*/;i < sASCII_capacity; i++)
      sASCII[i] = 0;

    if ( 0 != sASCII[sASCII_capacity-1])
      break;
    return true;
  }

  if ( nullptr != sASCII && sASCII_capacity > 0)
    memset(sASCII,0,sASCII_capacity*sizeof(sASCII[0]));

  return false;
}
#endif

static bool ASCIICharToWide(
  const char* sASCII,
  size_t sASCII_length,
  wchar_t* wASCII,
  size_t wASCII_capacity
  )
{
  for (;;)
  {
    if (nullptr == sASCII || sASCII_length <= 0)
      break;
    if (nullptr == wASCII || wASCII_capacity <= 0)
      break;
    if (wASCII_capacity < sASCII_length)
      break;

    size_t i;
    for (i = 0; i < sASCII_length; i++)
    {
      const char c = sASCII[i];
      if (c < 0 || c > 127)
        break;
      if (0 == c)
      {
        wASCII[i++] = 0;
        sASCII_length = i;
        break;
      }

      wASCII[i] = (wchar_t)c;
    }
    if ( i < sASCII_length )
      break;

    for(/*empty init*/;i < wASCII_capacity; i++)
      wASCII[i] = 0;

    if ( 0 != wASCII[wASCII_capacity-1])
      break;
    return true;
  }

  if ( nullptr != wASCII && wASCII_capacity > 0)
    memset(wASCII,0,wASCII_capacity*sizeof(wASCII[0]));

  return false;
}

bool ON_Locale::ParseName(
  const wchar_t* locale_name,
  int locale_name_element_count,
  wchar_t* language_code,
  size_t language_code_capacity,
  wchar_t* extlang_code,
  size_t extlang_code_capacity,
  wchar_t* script_code,
  size_t script_code_capacity,
  wchar_t* region_code,
  size_t region_code_capacity,
  wchar_t* sortorder,
  size_t sortorder_capacity
  )
{
  bool rc = true;

  if (false == ZeroWideBuffer(language_code, language_code_capacity, sizeof(language_code[0])))
    rc = false;
  if (false == ZeroWideBuffer(extlang_code, extlang_code_capacity, sizeof(extlang_code[0])))
    rc = false;
  if (false == ZeroWideBuffer(script_code, script_code_capacity, sizeof(script_code[0])))
    rc = false;
  if (false == ZeroWideBuffer(region_code, region_code_capacity, sizeof(region_code[0])))
    rc = false;
  if (false == ZeroWideBuffer(sortorder, sortorder_capacity, sizeof(sortorder[0])))
    rc = false;

  if (false == rc)
    return false;

  if ( locale_name_element_count < 0 )
    locale_name_element_count = ON_wString::Length(locale_name);

  if ( 0 == locale_name_element_count )
    return true;

  if ( nullptr == locale_name || 0 == locale_name[0] )
    return false;

  if ( locale_name_element_count < 2 )
    return false;

  ON_String buffer;
  buffer.ReserveArray(locale_name_element_count);
  buffer.SetLength(locale_name_element_count);
  char* sname = buffer.Array();
  for (int i = 0; i < locale_name_element_count; i++)
  {
    const wchar_t w = locale_name[i];
    if ( w > 127 )
      return false; // illegal symbol in language or locale name
    if (0 == w)
    {
      locale_name_element_count = i;
      break;
    }
    sname[i] = (char)w;
  }
  sname[locale_name_element_count] = 0;
  
  const size_t ON_S_CAPACITY = 64;
  char slanguage_code[ON_S_CAPACITY] = { 0 };
  char sextlang_code[ON_S_CAPACITY] = { 0 };
  char sscript_code[ON_S_CAPACITY] = { 0 };
  char sregion_code[ON_S_CAPACITY] = { 0 };
  char ssortorder[ON_S_CAPACITY] = { 0 };

  rc = ON_Locale::ParseName(
    sname,
    locale_name_element_count,
    slanguage_code, ON_S_CAPACITY,
    sextlang_code, ON_S_CAPACITY,
    sscript_code, ON_S_CAPACITY,
    sregion_code, ON_S_CAPACITY,
    ssortorder, ON_S_CAPACITY
    );

  if (!rc)
    return false;

  if ( !ASCIICharToWide(slanguage_code,ON_S_CAPACITY,language_code,language_code_capacity) )
    rc = false;
  if ( !ASCIICharToWide(sscript_code,ON_S_CAPACITY,script_code,script_code_capacity) )
    rc = false;
  if ( !ASCIICharToWide(sregion_code,ON_S_CAPACITY,region_code,region_code_capacity) )
    rc = false;
  if ( !ASCIICharToWide(ssortorder,ON_S_CAPACITY,sortorder,sortorder_capacity) )
    rc = false;

  return rc;
}

bool ON_Locale::ParseName(
  const char* locale_name,
  int locale_name_element_count,
  char* language_code,
  size_t language_code_capacity,
  char* extlang_code,
  size_t extlang_code_capacity,
  char* script_code,
  size_t script_code_capacity,
  char* region_code,
  size_t region_code_capacity,
  char* sortorder,
  size_t sortorder_capacity
  )
{
  bool rc = true;

  if ( false == ZeroCharBuffer(language_code, language_code_capacity, sizeof(language_code[0])) )
    rc = false;
  if ( false == ZeroCharBuffer(extlang_code, extlang_code_capacity, sizeof(extlang_code[0])) )
    rc = false;
  if ( false == ZeroCharBuffer(script_code,   script_code_capacity,   sizeof(script_code[0])) )
    rc = false;
  if ( false == ZeroCharBuffer(region_code,   region_code_capacity,   sizeof(region_code[0])) )
    rc = false;
  if ( false == ZeroCharBuffer(sortorder,     sortorder_capacity,     sizeof(sortorder[0])) )
    rc = false;

  if (!rc)
    return false;

  if ( nullptr == locale_name || 0 == locale_name[0] || 0 == locale_name_element_count )
    return true;

  if ( locale_name_element_count < 0 )
    locale_name_element_count = ON_String::Length(locale_name);

  if ( locale_name_element_count < 2 )
    return false;

  const char* locale_name_end = locale_name + locale_name_element_count;

  const char* s0 = locale_name;
  const char* s1 = s0;
  while( IsAlpha(*s1) )
    s1++;

  if ( s1-s0 < 2 )
    return false;

  for ( size_t i = 0; i < language_code_capacity && s0 < s1 ; i++)
    language_code[i] = ToLower(*s0++); // lower case for language code is a convention

  if ( 0 == language_code[0] || 0 == language_code[1] )
    return false;

  bool bScriptTest = true;
  bool bExtlangTest = true;
  bool bRegionTest = true;
  bool bSortOrderTest = true;

  for (int pass = 0; pass < 4 && s1 < locale_name_end; pass++)
  {
    char c0 = *s1;

    if (0 == c0)
      return true;

    if (false == IsHyphenOrUnderbar(c0))
      return false;

    s1++;
    s0 = s1;
    while (s1 < locale_name_end && IsAlphaOrDigit(*s1))
      s1++;

    if ( s1-s0 < 2 )
      return false;

    if (bExtlangTest)
    {
      bExtlangTest = false;
      if ( IsHyphen(c0)
        && 3 == s1 - s0
        && IsAlpha(s0[0])
        && IsAlpha(s0[1])
        && IsAlpha(s0[2])
        )
      {
        if ( extlang_code_capacity > 0 && extlang_code_capacity < (size_t)(s1-s0) )
          return false;
        if (extlang_code_capacity > 0)
        {
          for (size_t i = 0; i < extlang_code_capacity && s0 < s1; i++)
            extlang_code[i] = ToLower(*s0++); // lower case for extlang code is a convention
        }
        continue;
      }
    }

    if (bScriptTest)
    {
      bScriptTest = false;
      if ( IsHyphen(c0)
        && 4 == s1 - s0
        && IsAlpha(s0[0])
        && IsAlpha(s0[1])
        && IsAlpha(s0[2])
        && IsAlpha(s0[3])
        )
      {
        // ISO 15924 script code
        if ( script_code_capacity > 0 && script_code_capacity < (size_t)(s1-s0) )
          return false;
        if (script_code_capacity > 0)
        {
          // convention is for script codes is CAPITAL, small, small, small case
          script_code[0] = ToUpper(*s0++);
          for (size_t i = 1; i < script_code_capacity && s0 < s1; i++)
            script_code[i] = ToLower(*s0++);
        }
        continue;
      }
    }

    if (bRegionTest)
    {
      bRegionTest = false;
      // IsUnderbar(c0) is here to handle Apple OS X and iOS "locale id" names like "en_US" which use and underbar before region
      if (
        ( (IsHyphen(c0) || IsUnderbar(c0)) && 2 == s1 - s0 && IsAlpha(s0[0]) && IsAlpha(s0[1]) )  // ISO 3166 country/region identifier (2 alpha)
        || ( IsHyphen(c0) && 3 == s1 - s0 && IsDigit(s0[0]) && IsDigit(s0[1]) && IsDigit(s0[2]) ) // UN M.49 code (3 digits)
        )
      {
        if ( region_code_capacity > 0 && region_code_capacity <= (size_t)(s1-s0) )
          return false;
        for (size_t i = 0; i < region_code_capacity && s0 < s1; i++)
          region_code[i] = ToUpper(*s0++); // unppercase for regions is a convention
        continue;
      }
    }

    if (bSortOrderTest)
    {
      bSortOrderTest = false;
      if (IsUnderbar(c0))
      {
        // Windows sort order
        if (sortorder_capacity > 0 && sortorder_capacity <= (size_t)(s1 - s0))
          return false;
        for (size_t i = 0; i < sortorder_capacity && s0 < s1; i++)
          sortorder[i] = *s0++;
        continue;
      }
    }


    ON_ERROR("Parser needs to be enhanced or input is not valid");
    return false;
  }

  return true;

}

ON_Locale ON_Locale::FromWindowsLCID(
  ON__UINT32 windows_lcid
  )
{
  if (ON_Locale::OrdinalLCID == windows_lcid
    || 1 == windows_lcid // "Rhino locale"
    || ON_Locale::InvariantCultureLCID == windows_lcid
    )
  {
    // This code is called to initialize ON_Locale::Ordinal and ON_Locale::InvariantCulture.
    ON_Locale locale;
    locale.m_windows_lcid = (ON__UINT32)windows_lcid;
    return locale;
  }
  
#if defined(ON_RUNTIME_WIN)

  wchar_t wide_locale_name[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
  const int locale_name_capacity = (int)(sizeof(wide_locale_name) / sizeof(wide_locale_name[0]) - 1);
  int local_name_length = ::LCIDToLocaleName(
    windows_lcid,
    wide_locale_name,
    locale_name_capacity,
    LOCALE_ALLOW_NEUTRAL_NAMES
    );

  if (0 == local_name_length)
  {
    ON_ERROR("Windows ::LCIDToLocaleName() failed.");
    return ON_Locale::Ordinal;
  }

  if (local_name_length >= ON_Locale::BUFFER_MAXIMUM_CAPACITY)
  {
    ON_ERROR("Windows locale name is too long.");
    return ON_Locale::Ordinal;
  }

  wide_locale_name[locale_name_capacity] = 0;
  char locale_name[ON_Locale::BUFFER_MAXIMUM_CAPACITY + 1] = { 0 };
  if (false == ASCIIWideToChar(wide_locale_name, local_name_length, locale_name, sizeof(locale_name) / sizeof(locale_name[0])) )
  {
    ON_ERROR("Windows locale name contains invalid wchar_t element.");
    return ON_Locale::Ordinal;
  }
  return ON_Locale::FromWindowsLCIDAndName(windows_lcid,locale_name);

#else
  switch (windows_lcid)
  {
  case ON_Locale::WindowsLCID::cs_CZ_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "cs-CZ" );
    break;
  case ON_Locale::WindowsLCID::de_DE_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "de-DE" );
    break;
  case ON_Locale::WindowsLCID::en_US_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "en-US" );
    break;
  case ON_Locale::WindowsLCID::es_ES_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "es-ES" );
    break;
  case ON_Locale::WindowsLCID::es_ES_tradnl_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "es-ES_tradnl" );
    break;
  case ON_Locale::WindowsLCID::fr_FR_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "fr-FR" );
    break;
  case ON_Locale::WindowsLCID::it_IT_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "it-IT" );
    break;
  case ON_Locale::WindowsLCID::ja_JP_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "ja-JP" );
    break;
  case ON_Locale::WindowsLCID::ko_KR_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "ko-KR" );
    break;
  case ON_Locale::WindowsLCID::pl_PL_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "pl-PL" );
    break;
  case ON_Locale::WindowsLCID::pt_PT_LCID:
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "pt-PT" );
    break;
  case ON_Locale::WindowsLCID::zh_CN_LCID:
    // "Hans" script is implied by BCP 47 and should not be in the language tag
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "zh-CN" );
    break;
  case ON_Locale::WindowsLCID::zh_TW_LCID:
    // "Hant" script is implied by BCP 47 and should not be in the language tag
    return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "zh-TW" );
    break;
  }

  ON_ERROR("No case for this windows_lcid in the generic runtime code.");
  return ON_Locale::Ordinal;

#endif
}

ON_Locale ON_Locale::FromWindowsLCIDAndName(
  ON__UINT32 windows_lcid,
  const char* name
  )
{
  if ( windows_lcid == ON_Locale::OrdinalLCID )
    return ON_Locale::Ordinal;

  if ( windows_lcid == ON_Locale::InvariantCultureLCID )
    return ON_Locale::InvariantCulture;

  if (0 == name || 0 == name[0])
  {
    return ON_Locale::InvariantCulture;
  }
  
  ON_Locale locale;

  locale.m_windows_lcid = windows_lcid;

  if (false == ON_Locale::ParseName(
    name,
    -1,
    locale.m_language_subtag, sizeof(locale.m_language_subtag) / sizeof(locale.m_language_subtag[0]),
    nullptr, 0,
    locale.m_script_subtag, sizeof(locale.m_script_subtag) / sizeof(locale.m_script_subtag[0]),
    locale.m_region_subtag, sizeof(locale.m_region_subtag) / sizeof(locale.m_region_subtag[0]),
    locale.m_windows_sortorder, sizeof(locale.m_windows_sortorder) / sizeof(locale.m_windows_sortorder[0])
    ))
  {
    ON_ERROR("ParseLocaleName() failed.");
    return ON_Locale::Ordinal;
  }

  if (0 == locale.m_language_subtag[0])
  {
    ON_ERROR("ParseLocaleName() returned empty language name.");
    return ON_Locale::Ordinal;
  }

  if (0 == locale.m_language_subtag[1])
  {
    ON_ERROR("ParseLocaleName() returned invalid language name.");
    return ON_Locale::Ordinal;
  }

  char* buffer_end = LocaleStringBuilderDestEnd(locale.m_bcp47_language_tag,sizeof(locale.m_bcp47_language_tag)/sizeof(locale.m_bcp47_language_tag[0]));
  char* s = locale.m_bcp47_language_tag;
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,locale.m_language_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',locale.m_script_subtag),s,buffer_end);
  s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',locale.m_region_subtag),s,buffer_end);
  if (nullptr == s)
  {
    ON_ERROR("Unable to create m_bcp47_language_tag.");
    return ON_Locale::Ordinal;
  }
  
  locale.m_numeric_locale = ON_CRT_C_locale();

  locale.m_string_coll_map_locale = ON_CRT_create_locale_ALL( locale.m_bcp47_language_tag );
  if (0 == locale.m_string_coll_map_locale)
  {
    ON_ERROR("ON_CRT_create_locale(LC_ALL, locale.m_bcp47_language_tag) failed.");
    return ON_Locale::Ordinal;
  }

  return locale;
}

ON_Locale ON_Locale::FromWindowsName(
    const char* locale_name
    )
{
  if (0 == locale_name || 0 == locale_name[0])
    return ON_Locale::InvariantCulture;

  if ('C' == locale_name[0] && 0 == locale_name[1] )
    return ON_Locale::Ordinal;

#if defined(ON_RUNTIME_WIN)

  const ON_wString buffer1(locale_name);
  const wchar_t* wlocale_name = static_cast< const wchar_t* >(buffer1);
  wchar_t buffer2[LOCALE_NAME_MAX_LENGTH] = { 0 };

  if (false == ::IsValidLocaleName(wlocale_name))
  {
    const int buffer2_capacity = (int)(sizeof(buffer2) / sizeof(buffer2[0]));
    if (0 != ::ResolveLocaleName(wlocale_name,buffer2,buffer2_capacity)
        && ::IsValidLocaleName(buffer2)
      )
    {
      wlocale_name = buffer2;
    }
    else
    {
      return ON_Locale::InvariantCulture;
    }
  }

  LCID windows_lcid = ::LocaleNameToLCID(
    wlocale_name,
    LOCALE_ALLOW_NEUTRAL_NAMES
    );

  if (0 == windows_lcid)
  {
    ON_ERROR("Windows ::LocaleNameToLCID() failed.");
    return ON_Locale::Ordinal;
  }

  char slocale_name[ON_Locale::BUFFER_MAXIMUM_CAPACITY] = { 0 };
  if (false == ASCIIWideToChar(wlocale_name, ON_wString::Length(wlocale_name), slocale_name, sizeof(slocale_name) / sizeof(slocale_name[0])))
  {
    ON_ERROR("locale_name contains invalid values.");
    return ON_Locale::Ordinal;
  }

  return ON_Locale::FromWindowsLCIDAndName(
    (ON__UINT32)windows_lcid,
    slocale_name
    );

#else

  char language_subtag[32] = { 0 };
  char script_subtag[32] = { 0 };
  char region_subtag[32] = { 0 };
  char windows_sortorder[32] = { 0 };
  if (false == ON_Locale::ParseName(
    locale_name,
    -1,
    language_subtag, sizeof(language_subtag) / sizeof(language_subtag[0]),
    nullptr, 0,
    script_subtag, sizeof(script_subtag) / sizeof(script_subtag[0]),
    region_subtag, sizeof(region_subtag) / sizeof(region_subtag[0]),
    windows_sortorder, sizeof(windows_sortorder) / sizeof(windows_sortorder[0])
    ))
  {
    ON_ERROR("ParseLocaleName() failed.");
    return ON_Locale::Ordinal;
  }

  if (0 == language_subtag[0])
  {
    ON_ERROR("ParseLocaleName() returned empty language name.");
    return ON_Locale::Ordinal;
  }

  if (0 == language_subtag[1])
  {
    ON_ERROR("ParseLocaleName() returned invalid language name.");
    return ON_Locale::Ordinal;
  }

  if ( ON_String::EqualOrdinal("zh", -1, language_subtag, -1, true) )
  {
    // Rhino supports two Chinese locales.
    if (ON_String::EqualOrdinal("Hans", -1, script_subtag, -1, true))
    {
      // Apple uses "zh-Hans" to mean BCP 47 "zh-CN"
      if ( 0 == region_subtag[0] || ON_String::EqualOrdinal("CN", -1, region_subtag, -1, true) )
        ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_CN_LCID, "zh-CN" ); 
    }
    else if (ON_String::EqualOrdinal("Hant", -1, script_subtag, -1, true))
    {
      // Apple uses "zh-Hant" to mean BCP 47 "zh-CN"
      if ( 0 == region_subtag[0] || ON_String::EqualOrdinal("TW", -1, region_subtag, -1, true) )
        ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" ); 
    }
    else if ( ON_String::EqualOrdinal("CN", -1, region_subtag, -1, true) )
      ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_CN_LCID, "zh-CN" ); 
    else if ( ON_String::EqualOrdinal("TW", -1, region_subtag, -1, true) )
      ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" ); 
    else
      ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" ); 
  }
  else
  {
    // Rhino supports a single region and default script for these languages.
    ON_String cleaned_loc = language_subtag;
    cleaned_loc += '-';
    cleaned_loc +=region_subtag;
    if ( 0 != windows_sortorder[0])
    {
      cleaned_loc += '_';
      cleaned_loc += windows_sortorder;
    }
    
#define LANG_CASE(lcid,lang) if (ON_String::EqualOrdinal(lang, -1, cleaned_loc, -1, true)) return ON_Locale::FromWindowsLCIDAndName(lcid, lang )
    LANG_CASE( ON_Locale::cs_CZ_LCID, "cs-CZ" );
    LANG_CASE( ON_Locale::de_DE_LCID, "de-DE" );
    LANG_CASE( ON_Locale::en_US_LCID, "en-US" );
    LANG_CASE( ON_Locale::es_ES_tradnl_LCID, "es-ES_tradnl" ); // _tradnl is Windows sort order
    LANG_CASE( ON_Locale::es_ES_LCID, "es-ES" );
    LANG_CASE( ON_Locale::fr_FR_LCID, "fr-FR" );
    LANG_CASE( ON_Locale::it_IT_LCID, "it-IT" );
    LANG_CASE( ON_Locale::ja_JP_LCID, "ja-JP" );
    LANG_CASE( ON_Locale::ko_KR_LCID, "ko-KR" );
    LANG_CASE( ON_Locale::pl_PL_LCID, "pl-PL" );
    LANG_CASE( ON_Locale::pt_PT_LCID, "pt-PT" );
#undef LANG_CASE
    
    // Bail out clause to handle string like fr-US.
#define LANG_CASE_2(two_letter_lang_code,lcid,lang) if (ON_String::EqualOrdinal(two_letter_lang_code, -1, language_subtag, -1, true)) return ON_Locale::FromWindowsLCIDAndName(lcid, lang )
    LANG_CASE_2( "cs", ON_Locale::cs_CZ_LCID, "cs-CZ" );
    LANG_CASE_2( "de", ON_Locale::de_DE_LCID, "de-DE" );
    LANG_CASE_2( "en", ON_Locale::en_US_LCID, "en-US" );
    LANG_CASE_2( "es", ON_Locale::es_ES_LCID, "es-ES" );
    LANG_CASE_2( "fr", ON_Locale::fr_FR_LCID, "fr-FR" );
    LANG_CASE_2( "it", ON_Locale::it_IT_LCID, "it-IT" );
    LANG_CASE_2( "ja", ON_Locale::ja_JP_LCID, "ja-JP" );
    LANG_CASE_2( "ko", ON_Locale::ko_KR_LCID, "ko-KR" );
    LANG_CASE_2( "pl", ON_Locale::pl_PL_LCID, "pl-PL" );
    LANG_CASE_2( "pt", ON_Locale::pt_PT_LCID, "pt-PT" );
#undef LANG_CASE_2
  }

  ON_ERROR("Unsupported language name.");
  return ON_Locale::InvariantCulture;

#endif

}

ON_Locale ON_Locale::FromWindowsName(
  const wchar_t* locale_name
  )
{
  const ON_String s_locale_name(locale_name);
  return ON_Locale::FromWindowsName((const char*)s_locale_name);
}

ON_Locale ON_Locale::FromBCP47LanguageName(
  const char* language_name
  )
{
  return ON_Locale::FromWindowsName(language_name);
}

ON_Locale ON_Locale::FromBCP47LanguageName(
  const wchar_t* language_name
  )
{
  return ON_Locale::FromWindowsName(language_name);
}

ON_Locale ON_Locale::FromAppleName(
  const char* apple_name
  )
{
  // Apple "locale" names have underbars before <REGION>
  // Apple "language" names have hyphens before <REGION> or no <REGION> in the case of Chinese

  ON_String buffer(apple_name);
  buffer.Replace('_', '-'); 
  apple_name = buffer;

  // According to https://en.wikipedia.org/wiki/Chinese_language, Chinese is a family of language
  // varieties, often mutually unintelligible.  Specifying both Script and REGION
  // (zh-Hans-CN or zh-Hant-TW) doesn't narrow things down nearly enough. 
  //
  // Basically we have to hope the string collate and mapping functions supplied by the OS and 
  // the translations supplied by our staff work well for our customers who select from the 
  // two types of "Chinese" Rhino offers.
  //
  if (ON_String::EqualOrdinal("zh-Hans", -1, apple_name, -1, true) || ON_String::EqualOrdinal("zh-CN", -1, apple_name, -1, true))
  {
    // Apple uses "zh-Hanx" for the "language" name and "zh_CN" for the "locale" name 
    // when identifying OS X string services that might be useful to people who live
    // in the Peoples Republic of China.
    return ON_Locale::FromWindowsLCIDAndName(ON_Locale::WindowsLCID::zh_CN_LCID, "zh-CN");
  }
  
  if (ON_String::EqualOrdinal("zh-Hant", -1, apple_name, -1, true) || ON_String::EqualOrdinal("zh-TW", -1, apple_name, -1, true))
  {
    // Apple uses "zh-Hant" for the "language" name and "zh_TW" for the "locale" name 
    // when identifying OS X string services that might be useful to people who live
    // in Tiawan.
    return ON_Locale::FromWindowsLCIDAndName(ON_Locale::WindowsLCID::zh_TW_LCID, "zh-TW");
  }

  return ON_Locale::FromWindowsName( static_cast<const char*>(buffer) );
}

ON_Locale ON_Locale::FromAppleName(
  const wchar_t* apple_name
  )
{
  const ON_String s(apple_name);
  return ON_Locale::FromAppleName(s);
}

bool ON_Locale::SetCurrentCulture(
  const ON_Locale& current_culture_locale
  )
{
  ON_Locale::m_CurrentCulture = current_culture_locale;

  char buffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY] = { 0 };
  size_t buffer_capacity = sizeof(buffer) / sizeof(buffer[0]);
  const char* s = nullptr;

  if (false == current_culture_locale.IsOrdinalOrInvariantCulture())
  {
#if defined(ON_RUNTIME_WIN)
    // Windows sometimes appends _<sortorder>
    s = current_culture_locale.GetWindowsLocaleName(buffer,buffer_capacity);
#elif defined(ON_RUNTIME_APPLE)
    // Apple puts an _ before <REGION>
    s = current_culture_locale.GetAppleLocaleName(buffer,buffer_capacity);
#else
    // The current "standard" ?
    s = current_culture_locale.GetBCP47LanguageTag(buffer, buffer_capacity);
#endif
  }

  // String collate and mapping functions should use the locale for the name.
  if ( nullptr == s || 0 == s[0] )
  {
    setlocale(LC_ALL,"C");
  }
  else
  {
    setlocale(LC_ALL,s);
  }

  // number parsing and formatting use a decimal point in all cases.
  setlocale(LC_NUMERIC,"C");

  return true;
}



ON_Locale ON_Locale::FromSubtags(
  const char* language_code,
  const char* script,
  const char* region_identifier
  )
{
  if (nullptr == language_code || 0 == language_code[0])
    return ON_Locale::InvariantCulture;

  ON_String language_name(language_code);
  language_name.MakeLowerOrdinal();

  if ( nullptr != script && (0 != script[0] || (0 != script[1] && 0 != script[2] && 0 != script[3] && 0 == script[4])) )
  {
    char Script[6];
    Script[0] = '-';
    Script[1] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::UpperOrdinal,script[0]);
    Script[2] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
    Script[3] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
    Script[4] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
    Script[5] = 0;
    language_name += Script;
  }

  if (nullptr != region_identifier && 0 != region_identifier[0])
  {
    ON_String REGION = '-';
    REGION += region_identifier;
    REGION.MakeUpperOrdinal();
    language_name += REGION;
  }

  return ON_Locale::FromBCP47LanguageName(language_name);
}

ON_Locale ON_Locale::FromSubtags(
  const wchar_t* language_code,
  const wchar_t* script,
  const wchar_t* region_identifier
  )
{
  const ON_String s_language_code(language_code);
  const ON_String s_script(script);
  const ON_String s_region_identifier(region_identifier);
  return ON_Locale::FromSubtags(
    static_cast<const char*>(s_language_code),
    static_cast<const char*>(s_script),
    static_cast<const char*>(s_region_identifier)
    );
}

ON_CRT_locale_t ON_Locale::NumericLocalePtr() const
{
  return (ON_CRT_locale_t)m_numeric_locale;
}

ON_CRT_locale_t ON_Locale::StringCollateAndMapLocalePtr() const
{
  return (ON_CRT_locale_t)m_string_coll_map_locale;
}

bool ON_Locale::IsInvariantCulture() const
{
  return 
    0x0027 == m_windows_lcid 
    && 0 != m_numeric_locale 
    && ON_CRT_C_locale() == m_numeric_locale 
    && m_numeric_locale == m_string_coll_map_locale;
}

bool ON_Locale::IsOrdinal() const
{
  return 
    0 == m_windows_lcid 
    && 0 != m_numeric_locale 
    && ON_CRT_C_locale() == m_numeric_locale 
    && m_numeric_locale == m_string_coll_map_locale;
}

bool ON_Locale::IsOrdinalOrInvariantCulture() const
{
  return IsOrdinal() || IsInvariantCulture();
}

class ON_CRT_LOCALE
{
public:
  static bool Validate_sprintf()
  {
    // Test formatted printing
    char buffer[64] = { 0 };
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = sprintf(buffer, m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
  }

  static bool Validate_sprintf_s()
  {
#if defined(ON_COMPILER_CLANG)
    // Test formatted printing
    char buffer[64] = { 0 };
    size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = snprintf(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
    // Test formatted printing
    char buffer[64] = { 0 };
    size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = sprintf_s(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
  }

  static bool Validate_sprintf_l()
  {
#if defined(ON_COMPILER_CLANG)
#if defined(ON_RUNTIME_ANDROID)
    // Test formatted printing
    char buffer[64] = { 0 };
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = sprintf(buffer, m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
    // Test formatted printing
    char buffer[64] = { 0 };
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = sprintf_l(buffer, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
#else
    // Test formatted printing
    char buffer[64] = { 0 };
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = _sprintf_l(buffer, m_validation_print_format, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
  }

  static bool Validate_sprintf_s_l()
  {
#if defined(ON_COMPILER_CLANG)
#if defined(ON_RUNTIME_ANDROID)
    // Test formatted printing
    char buffer[64] = { 0 };
    size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = snprintf(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
    // Test formatted printing
    char buffer[64] = { 0 };
    size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = snprintf_l(buffer, buffer_capacity, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_print_format, m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
#else
    // Test formatted printing
    char buffer[64] = { 0 };
    size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
    // Testing C-runtime - do not using ON_String::FormatIntoBuffer
    int printf_rc = _sprintf_s_l(buffer, buffer_capacity, m_validation_print_format, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_value);
    return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
  }

  static bool Validate_sscanf()
  {
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
  }

  static bool Validate_sscanf_s()
  {
#if defined(ON_COMPILER_CLANG)
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#else
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf_s(m_validation_string, m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#endif
  }

  static bool Validate_sscanf_l()
  {
#if defined(ON_COMPILER_CLANG)
#if defined(ON_RUNTIME_ANDROID)
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#else
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf_l(m_validation_string, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#endif
#else
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = _sscanf_l(m_validation_string, m_validation_scan_format, ON_Locale::InvariantCulture.NumericLocalePtr(), &a);
    return ValidateDouble(a, scanf_rc);
#endif
  }

  static bool Validate_sscanf_s_l()
  {
#if defined(ON_COMPILER_CLANG)
#if defined(ON_RUNTIME_ANDROID)
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#else
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = sscanf_l(m_validation_string, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_scan_format, &a);
    return ValidateDouble(a, scanf_rc);
#endif
#else
    // Test formatted scanning
    double a = ON_UNSET_VALUE;
    // Testing C-runtime - do not using ON_String::Scan
    int scanf_rc = _sscanf_s_l(m_validation_string, m_validation_scan_format, ON_Locale::InvariantCulture.NumericLocalePtr(), &a);
    return ValidateDouble(a, scanf_rc);
#endif
  }

private:

  static bool ValidateString(
    const char* buffer,
    size_t sizeof_buffer,
    int printf_rc
    )
  {
    bool rc = false;

    if (0 == buffer
      || printf_rc <= 0
      || sizeof_buffer <= 0
      || 0 == m_validation_string
      || m_validation_length <= 0
      || m_validation_length >= sizeof_buffer
      || m_validation_length != (size_t)printf_rc
      )
    {
      rc = (0 == buffer && 0 == sizeof_buffer && 0 == m_validation_string && 0 == m_validation_length && 0 == printf_rc);
    }
    else
    {
      for (size_t i = 0; i < sizeof_buffer; i++)
      {
        if (i > m_validation_length)
          break;
        if (buffer[i] != m_validation_string[i])
          break;
        if (0 == m_validation_string[i])
        {
          rc = (i == m_validation_length);
          break;
        }
      }
    }

    return rc;
  }


  static bool ValidateDouble(
    double a,
    int scan_rc
    )
  {
    bool rc;

    if (1 == scan_rc && a == a && a == m_validation_value)
    {
      rc = true;
    }
    else
    {
      rc = false;
    }

    return rc;
  }

  static const double m_validation_value;
  static const char* m_validation_print_format;
  static const char* m_validation_scan_format;
  static const char* m_validation_string;
  static const size_t m_validation_length;
};

const double ON_CRT_LOCALE::m_validation_value = 12345678901234.25;
const char* ON_CRT_LOCALE::m_validation_print_format = "%.17lg";
const char* ON_CRT_LOCALE::m_validation_scan_format = "%lg";
const char* ON_CRT_LOCALE::m_validation_string = "12345678901234.25";
const size_t ON_CRT_LOCALE::m_validation_length = 17;

bool ON_Locale::PeriodIsCRuntimeDecimalPoint()
{
  // Test formatted printing

  // These tests use the locale ON_Locale::InvariantCulture.LocalePtr()
  // and should always pass.
  if (!ON_CRT_LOCALE::Validate_sprintf_l())
    return false;

  if (!ON_CRT_LOCALE::Validate_sprintf_s_l())
    return false;

  // These tests use the C-runtime locale and will fail if
  // the locale does not use a period as the decimal separator.
  if (!ON_CRT_LOCALE::Validate_sprintf())
    return false;

  if (!ON_CRT_LOCALE::Validate_sprintf_s())
    return false;

  // Test formatted scanning

  // These tests use the locale ON_Locale::InvariantCulture.LocalePtr()
  // and should always pass.
  if (!ON_CRT_LOCALE::Validate_sscanf_l())
    return false;

  if (!ON_CRT_LOCALE::Validate_sscanf_s_l())
    return false;

  // These tests use the C-runtime locale and will fail if
  // the locale does not use a period as the decimal separator.
  if (!ON_CRT_LOCALE::Validate_sscanf())
    return false;

  if (!ON_CRT_LOCALE::Validate_sscanf_s())
    return false;

  return true;

}

bool ON_Locale::SetPeriodAsCRuntimeDecimalPoint()
{
  bool rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
  if (false == rc)
  {
#if defined(ON_COMPILER_MSC)
    // Microsoft's C compiler
    const int prev_type = _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);

    const char* s = setlocale(LC_NUMERIC, "C");
    rc = (0 != s && 'C' == s[0] && 0 == s[1]);
    if (rc)
      rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();

    if (prev_type != _DISABLE_PER_THREAD_LOCALE && prev_type >= 0)
      _configthreadlocale(prev_type);

#elif defined(ON_COMPILER_CLANG)
    // Apple's Clang compiler
    const char* s = setlocale(LC_NUMERIC, "C");
    rc = (0 != s && 'C' == s[0] && 0 == s[1]);
    if (rc)
      rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
#else
    // another compiler
    const int prev_type = _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);

    const char* s = setlocale(LC_NUMERIC, "C");
    rc = (0 != s && 'C' == s[0] && 0 == s[1]);
    if (rc)
      rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();

    if (prev_type != _DISABLE_PER_THREAD_LOCALE && prev_type >= 0)
      _configthreadlocale(prev_type);
#endif
  }
  return rc;
}

unsigned int ON_Locale::EnforcePeriodAsCRuntimeDecimalPoint()
{
  if (true == ON_Locale::PeriodIsCRuntimeDecimalPoint())
    return 1; // decimal point = period;

  if (false == ON_Locale::SetPeriodAsCRuntimeDecimalPoint())
    return 0; // attempt to set decimal point = period failed
  
  if (false == ON_Locale::PeriodIsCRuntimeDecimalPoint())
    return  0; // decimal point != period

  return 2; // decimal point = period
}
