// RuntimeForStatic base library | C+- (SysRoutine) | Author: Sergei Y.Kitáev
#include "Precompiled.h"
#include "SharedIncludes.h"
#include "LE.internal.h"
namespace RuntimeForStatic {
//==================================================================================================
DWORD $LE::_DataPointerTLS_ = TLS_OUT_OF_INDEXES;
//==================================================================================================
static PLEData DataPtr() $not_null $fatal_error_on_failure;
static PLEData GetDataPtr();
//==================================================================================================

bool $LE::IsInitialized() $thread_static
{
   return $RT::IsInitialized( true ) && _GetDataPtr();
}
void $LE::VerifyInitialized($O) $thread_static $requires_rt_n_thread_init
{
   if ( !IsInitialized() )
      Fail_NotInitialized($ci);
}

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

void $LE::Fail_NotInitialized($O)
{
   $INVALID_OPERATION_WITH_MESSAGE( UninitializedErrorMessage() );
}
PCWSTR $LE::UninitializedErrorMessage() $static_pointer
{
   return $TEXT(
      L"“Last Error” functionality has not been initialized.",
      L"Функциональность “последней ошибки” не была проинициализирована." );
}

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

bool $LE::InitializeThread($OA,$DynText& errorMessage $out) $thread_static $requires_rt_init
{
   $ASSERT( !IsInitialized() || $RT::IsInitializing() );
   PVOID dataPtr = HelpersRT::CleanAlloc($, sizeof(LEData) );
   if ( !dataPtr )
   {
      errorMessage.SetMessage_MemoryAllocationFailure($ci);
      return false;
   }
   $TLS::SetPointer($, &_DataPointerTLS_, dataPtr );
   errorMessage.SetMessage_NoError($ci);
   return DoResetError();
}

void $LE::FinalizeThread($O) $thread_static
{
   auto pData = (PLEData)_DataPtr();
   if ( pData && pData->TextAllocationFlag )
      $Memory::Free( pData->MessagePointer );
}

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

bool $LE::InitializeThreading(PCWSTR& staticMessage $out) $last_system_error
{
   $ASSERT( !IsInitialized() );
   staticMessage = nullptr;
   if (!( HelpersRT::TLSAlloc($, &_DataPointerTLS_ ) ))
   {
      DWORD dwError = ::GetLastError();
      DoCleanup();
      return $Win32Error::SetLastSysError( dwError );
   }
   return true;
}

void $LE::FinalizeThreading()
{
   if ( $TLS::IsInitialized($, &_DataPointerTLS_ ) )
      DoCleanup();
}

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

void $LE::DoCleanup() $fatal_error_on_failure
{
   $TLS::Free($, &_DataPointerTLS_ );
}

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

bool $LE::HasError()
{
   return ErrorSign().HasError;
}
bool $LE::Success()
{
   return ErrorSign().Success;
}
bool $LE::IsWarning()
{
   return ErrorSign().IsWarning;
}
$ErrorType $LE::ErrorType()
{
   return ErrorSign().ErrorType;
}
bool $LE::IsWin32Error()
{
   return ErrorSign().IsWin32Error;
}
bool $LE::IsCOMError()
{
   return ErrorSign().IsCOMError;
}
bool $LE::IsWin32OrCOMError()
{
   return ErrorSign().IsWin32OrCOMError;
}
bool $LE::IsCustomError()
{
   return ErrorSign().IsCustomError;
}
bool $LE::IsUnknownError()
{
   return ErrorSign().IsUnknownError;
}
int $LE::IntCode()
{
   return ErrorSign().IntCode;
}
DWORD $LE::DWordCode()
{
   return ErrorSign().DWordCode;
}
DWORD $LE::Win32Error($O) $fatal_error_on_failure
{
   return ErrorSign().StrictWin32Error($ci);
}
HRESULT $LE::COMError($O) $fatal_error_on_failure
{
   return ErrorSign().StrictCOMError($ci);
}
DWORD $LE::Win32ErrorOrZero()
{
   return ErrorSign().Win32ErrorOrZero;
}
HRESULT $LE::COMErrorOrZero()
{
   return ErrorSign().COMErrorOrZero;
}

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

$ErrorSign $LE::ErrorSign()
{
   return IsInitialized() ? TD.ErrorSign :
      $ErrorSign( $ErrorType::UnknownError );
}

$DynText $LE::Message() $tenacious
{
   bool messageGenerationProblem;
   return GetMessage( messageGenerationProblem );
}
$DynText $LE::GetMessage(bool& messageGenerationProblem $out) $tenacious
{
   messageGenerationProblem = false;
   if ( !IsInitialized() )
      return $DynText($, (PWSTR)UninitializedErrorMessage(), false );
   PWSTR errorText;
   if ( !MessageGenerationProblem() &&
        ( errorText = HelpersRT::DuplicateSZ($, TD.MessagePointer ) ) )
      return $DynText($, errorText, true );
   messageGenerationProblem = true;
   return $DynText::CreateStatic($, $StaticMessages::MemoryAllocationFailure() );
}
$DynText $LE::StrictMessage() $fatal_error_on_failure
{
   return IsInitialized() ? $DynText($, TD.MessagePointer ) :
      $DynText($, (PWSTR)UninitializedErrorMessage(), false );
}

bool $LE::WasMessageProvided()
{
   return IsInitialized() ? TD.WasMessageProvided : false;
}

bool $LE::MessageGenerationProblem()
{
   return IsInitialized() ? TD.MessageGenerationProblem : false;
}

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

$ErrorInfo $LE::ErrorInfo() $tenacious
{
   return $ErrorInfo($, ErrorSign(), MessagePointerOrUninialized() );
}
/*
$ErrorInfo $LE::StrictErrorInfo() $fatal_error_on_failure
{
   auto errorInfo = ErrorInfo();
   if ( errorInfo.MessageGenerationProblem($) )
      $FATAL_ERROR( $TEXT(
         L"Gereration of last error message has failed.",
         L"Генерация сообщения последней ошибки потерпела неудачу." ));
   return errorInfo;
}
*/
//==================================================================================================

bool $LE::SetSystemError(PCWSTR headerMessage $optional,DWORD dwError,bool isCOMError) $ret_false
{
   if ( !IsInitialized() )
      return false;
   PCWSTR staticErrorMessage = nullptr;
   if ( dwError == $COM_UNRECOGNIZED_ERROR && isCOMError )
      staticErrorMessage = $TEXT(
         L"Unrecognized COM-error.",
         L"Неопознанная ошибка COM." );
   PWSTR pSystemMessage = staticErrorMessage ? nullptr :
      HelpersRT::GetSystemMessage($, dwError );
   bool messageGenerationProblem = false;
   if ( !staticErrorMessage && !pSystemMessage )
   {
      pSystemMessage = HelpersRT::GetLastSystemMessage($);
      messageGenerationProblem = true;
   }
   PWSTR pErrorText = $_( (PWSTR)staticErrorMessage, pSystemMessage );
   bool errorTextAllocation = pErrorText && pErrorText == pSystemMessage;
   if ( headerMessage && pErrorText )
   {
      pErrorText = HelpersRT::FormatAlloc($, L"%s\r\n%s", headerMessage, pErrorText );
      $memory::Free( pSystemMessage );
      if ( !pErrorText )
      {
         SetFormatAllocFailure();
         return $RESET_LAST_SYS_ERROR( false );
      }
      errorTextAllocation = true;
   }
   if ( !pErrorText )
   {
      pErrorText = HelpersRT::FormatAlloc($, $TEXT(
         L"System error message generation has failed (code: %d / 0x%.8X).",
         L"Генерация сообщения системной ошибки потерпела неудачу (код: %d / 0x%.8X)." ),
         dwError, dwError );
      errorTextAllocation = $BOOL( pErrorText );
      messageGenerationProblem = true;
   }
   TD = {};
   TD.ErrorSign = isCOMError ?
      $ErrorSign::CreateCOM( dwError ) :
      $ErrorSign::CreateWin32( dwError );
   if ( pErrorText )
      ReplaceMessageText($, pErrorText, errorTextAllocation, messageGenerationProblem );
   else
      ReplaceMessageText($, (PWSTR)$TEXT(
         L"System error message generation has failed.",
         L"Генерация сообщения системной ошибки потерпела неудачу." ),
         false, true );
   TD.WasMessageProvided = true;
   return $RESET_LAST_SYS_ERROR( false );
}

bool $LE::SetErrorLastSystem() $ret_false
{
   return SetWin32Error( ::GetLastError() );
}
bool $LE::SetErrorLastSystem( PCWSTR headerMessage $optional ) $ret_false
{
   return SetSystemError( headerMessage, ::GetLastError() );
}
bool $LE::SetErrorLastSystemFmt($A, PCWSTR headerMessageFmt, ... ) $ret_false
{
   $$VERIFY_POINTER_ARG( headerMessageFmt );
   va_list argList;
   SetErrorLastSystemFmtVA($ci, $VA_COMPLEMENT( headerMessageFmt, argList ) );
   va_end( argList );
   return $RESET_LAST_SYS_ERROR( false );
}
bool $LE::SetErrorLastSystemFmtVA($A, PCWSTR headerMessageFmt, va_list argList ) $ret_false
{
   $$VERIFY_POINTER_ARG( headerMessageFmt );
   PWSTR pHeaderMessage = HelpersRT::FormatAllocVA($, headerMessageFmt, argList );
   $AUTO_FREE( pHeaderMessage );
   return pHeaderMessage ? SetErrorLastSystem( pHeaderMessage ) : SetFormatAllocFailure();
}

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

bool $LE::SetFormatAllocFailure() $ret_false
{
   TD = {};
   TD.ErrorSign = $ErrorSign::CreateWin32( 0 );
   TD.MessagePointer = (PWSTR)$StaticMessages::MemoryAllocationFailure();
   TD.MessageGenerationProblem = true;
   return false;
}

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

bool $LE::SetErrorEx(const $ErrorSign& errorSign,PCWSTR message $optional) $ret_false
{
   if ( !IsInitialized() )
      return false;
   if ( !message )
      switch ( errorSign.ErrorType )
      {
      case $ErrorType::Win32: return SetWin32Error( errorSign.StrictWin32Error($) );
      case $ErrorType::COM  : return SetCOMError  ( errorSign.StrictCOMError  ($) );
      } // switch
   TD.ErrorSign = errorSign;
   if ( message )
   {
      PWSTR text = HelpersRT::DuplicateSZ($, message );
      if ( text )
         ReplaceMessageText($, text, true );
      else
         SetMessageGenerationProblem($, (PWSTR)$StaticMessages::MemoryAllocationFailure() );
      TD.WasMessageProvided = true;
   }
   else
   {
      ReplaceMessageText($, (PWSTR)$TEXT(
         L"Message text was not provided by error setter.",
         L"Текст сообщения не был предоставлен установщиком ошибки." ));
      TD.WasMessageProvided = false;
   }
   return $RESET_LAST_SYS_ERROR( false );
}

bool $LE::SetErrorExFmt($A, const $ErrorSign& errorSign, PCWSTR messageFmt, ... ) $ret_false
{
   $$VERIFY_POINTER_ARG( messageFmt );
   va_list argList;
   SetErrorExFmtVA($ci, errorSign, $VA_COMPLEMENT( messageFmt, argList ) );
   va_end( argList );
   return $RESET_LAST_SYS_ERROR( false );
}
bool $LE::SetErrorExFmtVA($A, const $ErrorSign& errorSign, PCWSTR messageFmt,
                                                           va_list argList ) $ret_false
{
   PWSTR message = HelpersRT::FormatAllocVA($ci,$, messageFmt, argList );
   if ( message )
   {
      SetErrorEx( errorSign, message );
      $Memory::Free( message );
   }
   else
      SetMessageGenerationProblem($, (PWSTR)$StaticMessages::MemoryAllocationFailure() );
   return $RESET_LAST_SYS_ERROR( false );
}

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

bool $LE::ResetError() $ret_true
{
   if ( IsInitialized() )
      DoResetError();
   return true;
}
bool $LE::DoResetError() $ret_true
{
   TD.ErrorSign = $ErrorSign();
   ReplaceMessageText($, (PWSTR)$StaticMessages::NoError() );
   TD.WasMessageProvided = false;
   return $Win32Error::ResetLastSysError();
}

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

void $LE::ReplaceMessageText($A,PWSTR message,bool needsFree,bool messageGenerationProblem)
{
   $$VERIFY_POINTER_ARG( message );
   if ( TD.TextAllocationFlag )
      $Memory::Free( TD.MessagePointer );
   TD.MessagePointer = message;
   TD.TextAllocationFlag = needsFree;
   TD.MessageGenerationProblem = messageGenerationProblem;
}

void $LE::SetMessageGenerationProblem($A,PCWSTR staticText $persistent_ptr)
{
   $$VERIFY_POINTER_ARG( staticText );
   if ( IsInitialized() )
      return;
   ReplaceMessageText($, (PWSTR)staticText, false, true );
}

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

PWSTR $LE::MessagePointerTD() $strict_result
{
   return TD.MessagePointer;
}

PCWSTR $LE::MessagePointerOrUninialized() $not_null
{
   return IsInitialized() ? TD.MessagePointer : UninitializedErrorMessage();
}

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

PVOID $LE::_DataPtr() $not_null $fatal_error_on_failure
{
   auto pointer = _GetDataPtr();
   if ( !pointer )
      $LE::Fail_NotInitialized($);
   return pointer;
}
PVOID $LE::_GetDataPtr()
{
   return $tls::GetPointer<LEData>($, &$LE::_DataPointerTLS_ );
}

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