//----------------------------------------------------------------------------
// Borland WinSys Library
// Copyright (c) 1994, 1996 by Borland International, All Rights Reserved
//
/// \file
/// TLocaleString implementation - localized name support
/// TRegList implementation - runtime component and object registration list
/// TRegItem implementation - runtime component and object registration item
///
/// \note This code must reside in the same module that the strings are defined
///       The cache, NativeLangId, HINSTANCE are managed on a per-module basis
///       TLocaleString::NativeLangId may be user-implemented to symbol langid
///       TLocaleString::Module may be reset from this to another resource DLL
//----------------------------------------------------------------------------
 
#include <owl/pch.h>
#include <owl/defs.h>
#include <owl/lclstrng.h>
//#include <stdio.h>
 
#if defined(BI_MULTI_THREAD_RTL)
#include <owl/thread.h>
#endif
 
#include <owl/appdict.h>
#include <owl/applicat.h>
 
 
namespace owl {
 
//----------------------------------------------------------------------------
// Module global default values - except for TLocaleString::NativeLangId
//
TLangId TLocaleString::SystemDefaultLangId = TLocaleString::GetSystemLangId();
TLangId TLocaleString::UserDefaultLangId   = TLocaleString::GetUserLangId();
HINSTANCE TLocaleString::Module = 0;
static const tchar* const null_string = _T("");
TLocaleString TLocaleString::Null = {null_string};
 
//----------------------------------------------------------------------------
// _TLocaleCache definitions, private for implementation
//
 
#define AUTOLANG_CACHEDNEUT 0x02 // prefix indicates cache entry with neutral
#define AUTOLANG_CACHEDLOAD 0x01 // prefix indicates Neutral is not a string
const TLangId InvalidLangId = 0xFFFF;
 
struct _TLocaleCache;
struct _TLocaleCacheBase;
 
//
// Static object to hold destructable pointer
//
/// \cond
struct _TLocaleCacheList
#if defined(BI_MULTI_THREAD_RTL)
                : public TLocalObject
#endif
{
  _TLocaleCacheList() : Next(0)
    {
    }
 ~_TLocaleCacheList();                        // releases cache entries
 
  _TLocaleCache* Lookup(const tchar* name); // returns cache entry, 0 if failed
  void AddLink(_TLocaleCacheBase* next);     // Add link
 
  _TLocaleCacheBase* Next;                    // linked list of cached translations
 
#if defined(BI_MULTI_THREAD_RTL)
//    TMRSWSection  Lock;
#endif
};
/// \endcond
 
//
// Static instance of the colors
//
static _TLocaleCacheList& GetLocaleCacheList()
{
#if defined(BI_MULTI_THREAD_RTL)
  static TTlsContainer<_TLocaleCacheList> localeCacheList;
  return localeCacheList.Get();
#else
  static _TLocaleCacheList localeCacheList;
  return localeCacheList;
#endif
};
 
namespace
{
  //
  // Ensure singleton initialization at start-up (single-threaded, safe).
  //
  _TLocaleCacheList& InitLocalCacheList = GetLocaleCacheList();
}
 
#if defined(BI_MULTI_THREAD_RTL)
#define LOCKCASHE //TMRSWSection::TLock Lock(GetLocaleCacheList().Lock);
#else
#define LOCKCASHE
#endif
 
 
 
//
// This base struct is used to cache failure to find language resource
//
/// \cond
struct _TLocaleCacheBase {
  long  Hash;            // hashed original string, for duplicate elimination
  const tchar* Neutral;   // original string, +1 if resource found and loaded
  _TLocaleCacheBase* Next;// linked list of cached strings, for search, cleanup
 
  _TLocaleCacheBase(const tchar* name, long hash);
};
 
//
// Buffer follows this header, sized for maximum string + null term.
//
struct _TLocaleCache : public _TLocaleCacheBase {
  void* operator new(size_t size, int buflen);
#if defined(BI_COMP_MSC)
  void operator delete(void* p, int buflen);
#endif
  _TLocaleCache(const tchar* name, long hash, HRSRC rscHdl, HGLOBAL resData);
 ~_TLocaleCache() {}
  const tchar* Translate(TLangId lang);   // (re)translate string
 
  TLangId ActLangId;   // actual language ID of cached string
  TLangId ReqLangId;   // requested language ID of cached string
  HRSRC   ResHdl;      // handle returned from ::FindResource()
  tchar  Buf[1];      // first character is string type
};
 
//----------------------------------------------------------------------------
// _TLocaleCache implementation
//
 
_TLocaleCacheBase::_TLocaleCacheBase(const tchar* name, long hash)
:
  Hash(hash),
  Neutral(name),
  Next(0)
{
  GetLocaleCacheList().AddLink(this);
}
 
void* _TLocaleCache::operator new(size_t size, int buflen)
{
  return ::operator new(size+buflen);
}
#if defined(BI_COMP_MSC)
void _TLocaleCache::operator delete(void* p, int)
{
  ::operator delete(p);
}
#endif
 
_TLocaleCache::_TLocaleCache(const tchar* name, long hash,
                           HRSRC resHdl, HGLOBAL resData)
:
  _TLocaleCacheBase(name, hash),
  ResHdl(resHdl)
{
  ReqLangId = ActLangId = InvalidLangId;     // indicate initializing state
  *(HGLOBAL*)(Buf+1) = resData;   // store resource pointer temp in buffer
}
//
//
//
_TLocaleCache* _TLocaleCacheList::Lookup(const tchar* name)
{
  LOCKCASHE
  const tchar* neut = name + 1;      // skip over prefix flag tchar
  long hash = 0;
  const tchar* pc = name;
  while (*pc)
    hash = hash*2 ^ *pc++;
  for (_TLocaleCacheBase* entry = Next; entry; entry = entry->Next) {
    if (hash == entry->Hash) {
      const tchar* pc = entry->Neutral;
      if (*pc != *neut)       // Neutral points to prefix if lookup failed
        pc++;
      if (TLocaleString::CompareLang(pc,neut,TLocaleString::NativeLangId) != 0)
        return  pc == entry->Neutral ? (_TLocaleCache*)entry : 0;
    }
  }
  pc = name;
  if (*name != AUTOLANG_RCID)
    pc++;                    // '#' part of Id
  HRSRC resHdl = ::FindResource(TLocaleString::Module ? TLocaleString::Module :
                     (HINSTANCE)*OWLGetAppDictionary().GetApplication(0),
                      pc, RT_LOCALIZATION);
  if (!resHdl) {
    new _TLocaleCacheBase(name, hash);    // add cache entry for failed lookup
    return 0;
  }
  HGLOBAL resData = ::LoadResource(TLocaleString::Module ? TLocaleString::Module :
                      (HINSTANCE)*OWLGetAppDictionary().GetApplication(0),
                        resHdl);
  if (!resData) {
    return 0;     // should throw exception on failure?!!
  }
  _TUCHAR * pr = (_TUCHAR *)::LockResource(resData);
  int maxLen = sizeof(HGLOBAL);  // scan for longest string, including null
  _TUCHAR c = *pr;         // check first byte of langid or neutral text
  if (c == 0) {                  // check for empty resource string
    ::FreeResource(resData);
    new _TLocaleCacheBase(name, hash); // add failed cache entry if null or err
    return 0;
  }
  if (c >= _T(' '))                  // check for unprefixed neutral string first
    pr--;                        // cancel ++ in for loop initialization
  else
    pr++;                        // start to skip over 2-byte language id
  do {                           // loop to check for maximum string length
    _TUCHAR * p = ++pr; // skip over id to start of translation
    while ((c=*pr++) >= _T(' ')) ;   // skip over translation string
    if ((int)(pr-p) > maxLen)    // update maximum, including terminator
      maxLen = (int)(pr-p);
  } while(c);
  _TLocaleCache* cache = new(maxLen)_TLocaleCache(neut, hash, resHdl, resData);
  cache->Buf[0] = tchar(*name == AUTOLANG_XLAT ? AUTOLANG_CACHEDNEUT
                                              : AUTOLANG_CACHEDLOAD);
  return cache;
}
//
const tchar*
_TLocaleCache::Translate(TLangId reqLang)
{
  HGLOBAL resData;
  if (ReqLangId == InvalidLangId) { // if first time called after construction
    resData = *(HGLOBAL*)(Buf+1);
    ReqLangId = reqLang;
  }
  else {
    if (Buf[0]==AUTOLANG_CACHEDNEUT && TLocaleString::IsNativeLangId(reqLang))
      return Neutral;
    if (reqLang == ActLangId)
      return Buf+1;
    if (reqLang == ReqLangId) {
      if (ActLangId != InvalidLangId)
        return Buf+1;
      else if (Buf[0] == AUTOLANG_CACHEDNEUT)
        return Neutral;
      else
        return 0;
    }
    if ((resData = ::LoadResource(TLocaleString::Module ? TLocaleString::Module :
                         (HINSTANCE)*OWLGetAppDictionary().GetApplication(0),
                           ResHdl)) == 0)
      return Neutral;   // should throw exception on failure?!!
  }
 
  _TUCHAR  * resBuf = (_TUCHAR *)::LockResource(resData);
  _TUCHAR * translation = 0;
  _TUCHAR * pr = resBuf;
  TLangId actLang = InvalidLangId;
  TLangId resLang;
  _TUCHAR c;
 
  while ((c = *pr) != 0) {
    if (c > _T(' ')) { // check for initial neutral string, used with CACHEDLOAD
      actLang = resLang = TLocaleString::NativeLangId;
      translation = pr;    // lowest preference match
    }
    else {
      resLang = TLangId(((c - 1)<<10) | *++pr);
      pr++;
    }
    if (resLang == reqLang) {     // exact match
      translation = pr;
      actLang = resLang;
      break;
    }
    if ((tchar)resLang == (tchar)reqLang) {   // base language match
      if ((tchar)actLang != (tchar)reqLang || resLang == (reqLang & 0x00FF)) {
        translation = pr;
        actLang = resLang;
      }
    }
    for ( ; *pr >= _T(' '); ++pr)   // skip over translation string till next Id
      ;
  }
  const tchar* retVal;
  if (translation) {
    while (*translation < _T(' '))    // skip over multiple language IDs
      translation += 2;
    if (actLang != ActLangId) {   // if same as in buffer, leave alone
      tchar* pc;
      for (pr = translation, pc = Buf + 1; *pr >= _T(' '); )
        *pc++ = *pr++;
      *pc = 0;
      ActLangId = actLang;
      if (reqLang != ActLangId)
        ReqLangId = reqLang;
    }
    retVal = Buf+1;
  }
  else if (Buf[0] == AUTOLANG_CACHEDNEUT) {
    retVal = Neutral;
  }
  else {
    retVal = 0;
  }
  ::FreeResource(resData);
  return retVal;
}
//
_TLocaleCacheList::~_TLocaleCacheList()
{
  LOCKCASHE
  while (Next) {
    _TLocaleCacheBase* p = Next;
    Next = Next->Next;
    delete p;
  }
}
//
void _TLocaleCacheList::AddLink(_TLocaleCacheBase* link)
{
  LOCKCASHE
  link->Next = Next;
  Next = link;
}
 
/// \endcond
 
//----------------------------------------------------------------------------
// TLocaleString implementation, except for static int CompareLang(s1,s2,lang)
//
 
/// Translates the string to the given language. Translate follows this order of
/// preference in order to choose a language for translation:
/// - 1.  Base language and dialect.
/// - 2.  Base language and no dialect.
/// - 3.  Base language and another dialect.
/// - 4.  The native language of the resource itself.
/// - 5.  Returns 0 if unable to translate the string. (This can happen only if an @
/// or # prefix is used; otherwise, the ! prefix indicates that the string following
/// is the native language itself.)
const tchar* TLocaleString::Translate(TLangId reqLang)
{
  if (!Private)                   // check for null string pointer
    return Private;
 
  if (reqLang == LangNeutral)
    reqLang = NativeLangId;
  else if (reqLang == LangSysDefault)
    reqLang = SystemDefaultLangId;
  else if (reqLang == LangUserDefault)
    reqLang = UserDefaultLangId;
 
  _TLocaleCache* cache;
  switch (Private[0])
  {
    default:                      // untranslatable string, no prefix
      return Private;
 
    case AUTOLANG_XLAT:           // not yet translated
      if (IsNativeLangId(reqLang))
        return Private+1;         // resource name IS neutral or default name
      if ((cache =   GetLocaleCacheList().Lookup(Private)) == 0)
        return ++Private;         // permanently bump pointer to make constant
      Private = cache->Buf;       // point to buffer in cache
      return cache->Translate(reqLang);
 
    case AUTOLANG_LOAD:           // named resource not accessed yet
    case AUTOLANG_RCID:           // numeric resource not accessed yet
      if ((cache = GetLocaleCacheList().Lookup(Private)) == 0)
        return (Private = 0);     // permanently set pointer to null
      Private = cache->Buf;       // point to buffer in cache
      return cache->Translate(reqLang);
 
    case AUTOLANG_CACHEDNEUT:     // string in cache with neutral pointer
    case AUTOLANG_CACHEDLOAD:     // string in cache with no neutral pointer
      cache = (_TLocaleCache*)(Private+1) - 1;   // backup to point to header
      return cache->Translate(reqLang);
  }
}
 
TLocaleString::operator const tchar*() const
{
  if (Private == 0)
    return 0;
 
  switch (Private[0]) {
  case AUTOLANG_XLAT:       // not yet translated
  case AUTOLANG_CACHEDNEUT: // translated string in cache
  case AUTOLANG_CACHEDLOAD: // translated or neutral name in cache
    return Private+1;
 
  case AUTOLANG_RCID:       // resource not accessed yet
  case AUTOLANG_LOAD:       // resource not accessed yet
    return 0;
 
  default:                  // untranslatable string, no prefix
    return Private;
  }
}
 
/// Using the specified language (lang), Compare compares TLocaleString with another
/// string. It uses the standard string compare and the language-specific collation
/// scheme. It returns one of the following values:
/// - \c \b    0  There is no match between the two strings.
/// - \c \b    1  This string is greater than the other string.
/// - \c \b    -1  This string is less than the other string.
int TLocaleString::Compare(const tchar * str, TLangId lang)
{
  return CompareLang(this->Translate(lang), str, lang);
}
 
/// Returns true if lang equals the native system language.
int TLocaleString::IsNativeLangId(TLangId lang)
{
  return lang == NativeLangId || lang == (NativeLangId & 0x00FF);
}
 
 
} // OWL namespace
 

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: Buf.

V1065 Expression can be simplified, check '1' and similar operands.

V832 It's better to use '= default;' syntax instead of empty destructor body.