// RuntimeForStatic base library | C+- (SysRoutine) | Author: Sergei Y.Kitáev
#include "Precompiled.h"
#include "SharedIncludes.h"
namespace RuntimeForStatic {
//==================================================================================================
struct ThreadStartData
{
   $ThreadSettings   ThreadSettings;
   $ThreadStart      ThreadStart;
   PVOID             ArgumentPointer;
}; // ThreadStartData
typedef ThreadStartData*       PThreadStartData;
typedef const ThreadStartData* PCThreadStartData;
//==================================================================================================
DWORD
   $Thread::_ThreadSettingsPointerTLS_  = TLS_OUT_OF_INDEXES,
   $Thread::_UseThreadSettingsCloneTLS_ = TLS_OUT_OF_INDEXES;
//==================================================================================================

bool $Thread::IsInitialized() $thread_static $not_requires_rt_init
{
   return $RT::IsInitialized( true ) && GetSettingsPointerTS();
}

void $Thread::VerifyInitialized($O) $thread_static $requires_rt_n_thread_init
{
   $RT::VerifyInitialized($ci);
   if ( !GetSettingsPointerTS() )
      Fail_NotInitialized($ci);
}
void $Thread::Fail_NotInitialized($O)
{
   $INVALID_OPERATION_WITH_MESSAGE( $TEXT(
      L"Program thread has not been initialized for “RuntimeForStatic” library usage.",
      L"Программный поток не был проинициализирован для использования библиотеки "
      L"“RuntimeForStatic”." ));
}

//--------------------------------------------------------------------------------------------------

bool $Thread::AcceptSettings($A,$PThreadSettings pThreadSettings,$DynText& errorMessage $out)
{
   $$VERIFY_POINTER_ARG( pThreadSettings );
   if ( !pThreadSettings->DontReplaceEmptyLocaleValues )
      pThreadSettings->ReplaceEmptyLocaleValues();
   return errorMessage.SetMessage_NoError($ci);
}

//--------------------------------------------------------------------------------------------------

bool $Thread::InitializeThread($OA, $DynText& errorMessage $out ) $thread_static
{
   return InitializeThread($ci, nullptr, errorMessage );
}
bool $Thread::InitializeThread($OA, $PCThreadSettings threadSettingsPtr $optional,
                                    $DynText& errorMessage $out ) $thread_static
{
   if ( !threadSettingsPtr )
      threadSettingsPtr = &$RT::ThreadSettingsPattern($ci);
   return InitializeThread($ci, ($PThreadSettings)threadSettingsPtr, true, false, errorMessage );
}

bool $Thread::InitializeThread($OA, $PThreadSettings pThreadSettings $persistent_ptr,
                                    bool createThreadSettingsClone,
                                    $DynText& errorMessage $out ) $thread_static
{
   return InitializeThread($ci, pThreadSettings, createThreadSettingsClone, false, errorMessage );
}

bool $Thread::InitializeThread($OA, $PThreadSettings pThreadSettings $persistent_ptr,
                                    bool createThreadSettingsClone,
                                    bool leaveLEInitializedOnFailure,
                                    $DynText& errorMessage $out ) $thread_static
{
   $$VERIFY_POINTER_ARG( pThreadSettings );
   if ( IsInitialized() && !$RT::IsInitializing() )
      $INVALID_OPERATION_WITH_MESSAGE( $TEXT(
         L"Attepmt of repeatedly initialization of program thread (“RuntimeForStatic” library).",
         L"Попытка повторной инициализации программного потока (библиотека “RuntimeForStatic”)." ));
   if ( !AcceptSettings($ci, pThreadSettings, errorMessage ) ||
        !$LE::InitializeThread($ci, errorMessage ) )
      goto FAILURE;
   PCWSTR crtLocaleFailureMessageFmt, failedCRTLocaleCategoryStr;
   if ( !ApplyCRTLocaleStrings( pThreadSettings->CRTLocaleStrings,
                                &crtLocaleFailureMessageFmt,
                                &failedCRTLocaleCategoryStr ))
      if ( errorMessage.FormatNS($, crtLocaleFailureMessageFmt,
                                    failedCRTLocaleCategoryStr ).HasPointer )
         goto FAILURE;
      else
         goto ALLOCATION_FAILURE;
   if ( $TLS::SetAsBool($, &_UseThreadSettingsCloneTLS_, createThreadSettingsClone ) )
   {
      auto pNewSettings = ($PThreadSettings)HelpersRT::Allocate($, sizeof($ThreadSettings) );
      if ( !pNewSettings ) goto ALLOCATION_FAILURE;
      *pNewSettings = *pThreadSettings;
      pThreadSettings = pNewSettings;
   }
   $TLS::SetPointer($, &_ThreadSettingsPointerTLS_, pThreadSettings );
   errorMessage.SetMessage_NoError($ci);
   return $LE::ResetError();
ALLOCATION_FAILURE:
   errorMessage.SetMessage_MemoryAllocationFailure($ci);
FAILURE:
   if ( !leaveLEInitializedOnFailure )
      $LE::FinalizeThread($);
   return false;
}

//--------------------------------------------------------------------------------------------------

void $Thread::FinalizeThread() $thread_static
{
   FinalizeThread($$);
}
void $Thread::FinalizeThread($O) $thread_static
{
   if ( !$RT::WasInitialized() )
      return;
   $LE::FinalizeThread($ci);
   auto pThreadSettings = ($PThreadSettings)$Thread::GetSettingsPointerTS();
   if ( pThreadSettings )
   {
      if ( $TLS::GetAsBool($, &_UseThreadSettingsCloneTLS_ ) )
         $Memory::Free( pThreadSettings );
      $TLS::ResetPointer($, &_ThreadSettingsPointerTLS_ );
   }
}

//==================================================================================================

bool $Thread::InitializeThreading( $ThreadSettings& threadSettings,
                                   PCWSTR& staticMessage $out $persistent_ptr ) $last_system_error
{
   staticMessage = nullptr;
   $ASSERT( !$RT::IsInitialized() );
   if (!(
      HelpersRT::TLSAlloc($, &_ThreadSettingsPointerTLS_ ) &&
      HelpersRT::TLSAlloc($, &_UseThreadSettingsCloneTLS_ ) ))
      goto FAILURE_LAST_SYSTEM_ERROR;
   if ( threadSettings.LocaleId )
   {
      if ( !::SetThreadLocale( threadSettings.LocaleId ) )
         goto FAILURE_LAST_SYSTEM_ERROR;
   }
   else
      threadSettings.LocaleId = ::GetThreadLocale();
   if ( threadSettings.LanguageId )
   {
      if ( ::IsWindowsVistaOrGreater() &&
           !::SetThreadUILanguage( threadSettings.LanguageId ) )
         goto FAILURE_LAST_SYSTEM_ERROR;
   }
   else
      threadSettings.LanguageId = LANGIDFROMLCID( threadSettings.LocaleId );
   $TLS::SetPointer($, &_ThreadSettingsPointerTLS_, &threadSettings );
   return true;
FAILURE_LAST_SYSTEM_ERROR:
   DWORD dwError = ::GetLastError();
   $LE::FinalizeThread($);
   DoCleanup();
   return $Win32Error::SetLastSysError( dwError );
}

void $Thread::FinalizeThreading()
{
   if ( $TLS::IsInitialized($, &_ThreadSettingsPointerTLS_ ) )
      DoCleanup();
}

//--------------------------------------------------------------------------------------------------

void $Thread::DoCleanup() $fatal_error_on_failure
{
   $TLS::Free($, &_ThreadSettingsPointerTLS_ );
   $TLS::Free($, &_UseThreadSettingsCloneTLS_ );
}

//==================================================================================================

bool $Thread::IsInMainThread() $not_requires_rt_init
{
   DWORD mainThreadId = $RT::GetMainThreadId();
   return mainThreadId && mainThreadId == ::GetCurrentThreadId();
}

//--------------------------------------------------------------------------------------------------

LANGID $Thread::LanguageId() $thread_static
{
   return SettingsPointerTS()->LanguageId;
}
LCID $Thread::LocaleId() $thread_static
{
   return SettingsPointerTS()->LocaleId;
}

//==================================================================================================

HANDLE $Thread::Create($OA,
   $ThreadStart threadStart, PVOID argumentPointer $optional, PUINT pThreadId $out $optional )
   $null_on_failure $last_error
{
   return Create($ci, nullptr, 0, threadStart, argumentPointer, 0, pThreadId );
}
HANDLE $Thread::Create($OA,
   PVOID security $optional, UINT stackSize $optional, $ThreadStart threadStart,
   PVOID argumentPointer $optional, UINT threadFlag $optional, PUINT pThreadId $out $optional )
   $null_on_failure $last_error
{
   return CreateEx($ci,
      security, stackSize, threadStart, argumentPointer, threadFlag, pThreadId, nullptr );
}

HANDLE $Thread::CreateSuspended($OA,
   $ThreadStart threadStart, PVOID argumentPointer $optional, PUINT pThreadId $out $optional )
   $null_on_failure $last_error
{
   return CreateSuspended($ci, nullptr, 0, threadStart, argumentPointer, pThreadId );
}
HANDLE $Thread::CreateSuspended($OA,
   PVOID security $optional, UINT stackSize $optional, $ThreadStart threadStart,
   PVOID argumentPointer $optional, PUINT pThreadId $out $optional )
   $null_on_failure $last_error
{
   return Create($ci,
      security, stackSize, threadStart, argumentPointer, CREATE_SUSPENDED, pThreadId );
}

HANDLE $Thread::CreateEx($OA,
   PVOID security $optional, UINT stackSize $optional, $ThreadStart threadStart,
   PVOID argumentPointer $optional, UINT threadFlag $optional, PUINT pThreadId $out $optional,
   $PCThreadSettings pSettings $optional )
   $null_on_failure $last_error
{
   VerifyInitialized($);
   $$VERIFY_POINTER_ARG( threadStart );
   if ( $RT::GetNativeThreadStartForbiddenVCRT() )
      return $SET_ERROR_LE( $TEXT(
         L"Native VCRT-thread start is not possible.",
         L"Запуск нативного VCRT-потока невозможен." ));
   $memory::ClearIfNotNull( pThreadId );
   auto psd = $memory::AllocateNS<ThreadStartData>($);
   $DynText errorMessage;
   if ( !psd ) goto ALLOCATION_FAILURE;
   psd->ThreadSettings = pSettings ? *pSettings : $RT::ThreadSettingsPattern($ci);
   if ( !AcceptSettings($, &psd->ThreadSettings, errorMessage ) )
      return $SET_ERROR_LE( errorMessage.T($) );
   psd->ThreadStart = threadStart;
   psd->ArgumentPointer = argumentPointer;
   ::MemoryBarrier();
   auto hThread = (HANDLE)::_beginthreadex( security, stackSize, ThreadOuterRoutine,
                                            psd, threadFlag, pThreadId );
   if ( hThread )
      return $RESET_LE( hThread );
ALLOCATION_FAILURE:
   $Memory::Free( psd );
   return $SET_ERROR_LE( $TEXT(
      L"Can not create new thread.",
      L"Не удаётся создать программный поток." ));
}

//--------------------------------------------------------------------------------------------------

bool $Thread::Join($OA,HANDLE hThread) $last_error
{
   DWORD exitCode; return Join($ci, hThread, exitCode );
}
bool $Thread::Join($OA,HANDLE hThread,DWORD& exitCode $out) $last_error
{
   $$VERIFY_HANDLE_ARG( hThread );
   exitCode = 0;
   if ( ::WaitForSingleObject( hThread, INFINITE ) == WAIT_FAILED )
      return $SET_FROM_SYSTEM_LE();
   if ( !::GetExitCodeThread( hThread, &exitCode ) )
      return $SET_FROM_SYSTEM_LE();
   return $SUCCEED_LE();
}

//==================================================================================================

void $Thread::Hang() $not_returns $not_requires_rt_init
{
   ::Sleep( INFINITE ); $DEAD_CODE_BRANCH();
}

//==================================================================================================

$PCThreadSettings $Thread::SettingsPointerTS() $strict_result
{
   auto rslt = GetSettingsPointerTS();
   if ( !rslt ) Fail_NotInitialized($);
   return rslt;
}

$PCThreadSettings $Thread::GetSettingsPointerTS()
{
   return $tls::GetPointerIfInitializedC<$ThreadSettings>($, &_ThreadSettingsPointerTLS_ );
}

//==================================================================================================

UINT __stdcall $Thread::ThreadOuterRoutine(PVOID pStartData)
{
   $$VERIFY_POINTER_ARG_( pStartData );
   ::MemoryBarrier();
   auto psd = (PThreadStartData)pStartData;
   auto pThreadSettings = &psd->ThreadSettings;
   const $ThreadSettings tset = psd->ThreadSettings;
   bool customizationFailureAsFatalError = !tset.StartupCustomizationFailureAsFatalError;
   DWORD dwError = 0;
   if ( (tset.LocaleId || tset.AllowZeroLocaleIdApplying) && !::SetThreadLocale( tset.LocaleId ) )
      $subst::ReplaceNull( dwError, ::GetLastError() );
   if ( dwError && customizationFailureAsFatalError )
      $FATAL_ERROR_WIN32( dwError );
   $DynText errorMessage;
   if ( !InitializeThread($$, pThreadSettings, false, true, errorMessage ) )
      if ( !$LE::IsInitialized() || customizationFailureAsFatalError )
         $FATAL_ERROR( errorMessage.T($) );
      else
         $LE::SetError( errorMessage.T($) );
   UINT exitCode = psd->ThreadStart( psd->ArgumentPointer );
   if ( !$RT::IsInInitializerThread() )
      FinalizeThread($$);
   $Memory::Free( psd );
   ::MemoryBarrier();
   return exitCode;
}

//--------------------------------------------------------------------------------------------------

bool $Thread::ApplyCRTLocaleStrings( const $CRTLocaleStrings& crtLocaleStrings,
                                     PCWSTR* pCRTLocaleFailureMessageFmt $required,
                                     PCWSTR* pFailedCRTLocaleCategoryStr $required )
{
   $$VERIFY_POINTER_ARG_( pFailedCRTLocaleCategoryStr );
   $$VERIFY_POINTER_ARG_( pCRTLocaleFailureMessageFmt );
   bool
      crtLCAllFailure = crtLocaleStrings.ALL &&
         !::setlocale( LC_ALL, crtLocaleStrings.ALL ),
      ctrLCCollateFailure =  crtLocaleStrings.COLLATE &&
         !::setlocale( LC_COLLATE, crtLocaleStrings.COLLATE ),
      ctrLCTypeFailure = crtLocaleStrings.CTYPE &&
         !::setlocale( LC_CTYPE, crtLocaleStrings.CTYPE ),
      crtLCMonetaryFailure = crtLocaleStrings.MONETARY &&
         !::setlocale( LC_MONETARY, crtLocaleStrings.MONETARY ),
      crtLCNumericFailure = crtLocaleStrings.NUMERIC &&
         !::setlocale( LC_NUMERIC, crtLocaleStrings.NUMERIC ),
      crtLCTimeFailure = crtLocaleStrings.TIME &&
         !::setlocale( LC_TIME, crtLocaleStrings.TIME );
   PCWSTR failedCRTLocaleCategoryStr =
      crtLCTimeFailure     ? L"LC_ALL"       :
      ctrLCCollateFailure  ? L"LC_COLLATE"   :
      ctrLCTypeFailure     ? L"LC_CTYPE"     :
      crtLCMonetaryFailure ? L"LC_MONETARY"  :
      crtLCNumericFailure  ? L"LC_NUMERIC"   :
      crtLCTimeFailure     ? L"LC_TIME"      :
      nullptr;
   *pFailedCRTLocaleCategoryStr = failedCRTLocaleCategoryStr;
   *pCRTLocaleFailureMessageFmt = failedCRTLocaleCategoryStr ? $TEXT(
      L"CRT locale setting has failed for “%s” category.",
      L"Установка локали CRT для категории “%s” потерпела неудачу." ) :
      nullptr;
   return !failedCRTLocaleCategoryStr;
}

//==================================================================================================
} // RuntimeForStatic
