// Copyright (c) 1998-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

//============================================================================
//==== Title: Standard_ErrorHandler.cxx
//==== Role : class "Standard_ErrorHandler" implementation.
//============================================================================
#include <Standard_ErrorHandler.hxx>
#include <Standard_Failure.hxx>
#include <Standard_Mutex.hxx>
#include <Standard.hxx>

#ifndef _WIN32
  #include <pthread.h>
#else
  #include <windows.h>
#endif

// ===========================================================================
// The class "Standard_ErrorHandler" variables
// ===========================================================================

// During [sig]setjmp()/[sig]longjmp() K_SETJMP is non zero (try)
// So if there is an abort request and if K_SETJMP is non zero, the abort
// request will be ignored. If the abort request do a raise during a setjmp
// or a longjmp, there will be a "terminating SEGV" impossible to handle.

//==== The top of the Errors Stack ===========================================
static Standard_ErrorHandler* Top = 0;

//! A mutex to protect from concurrent access to Top.
//! Mutex is defined as function to avoid issues caused by
//! an undefined static variables initialization order across compilation units (@sa #0031681 bug).
//! Note that we should NOT use Sentry while in this class, as Sentry
//! would register mutex as callback in the current exception handler.
static Standard_Mutex& GetMutex()
{
  static Standard_Mutex theMutex;
  return theMutex;
}

static inline Standard_ThreadId GetThreadID()
{
#ifndef _WIN32
  return (Standard_ThreadId)pthread_self();
#else
  return GetCurrentThreadId();
#endif
}

//============================================================================
//====  Constructor : Create a ErrorHandler structure. And add it at the
//====                'Top' of "ErrorHandler's stack".
//============================================================================

Standard_ErrorHandler::Standard_ErrorHandler()
    : myStatus(Standard_HandlerVoid),
      myCallbackPtr(0)
{
  myThread = GetThreadID();
  memset(&myLabel, 0, sizeof(myLabel));

  GetMutex().Lock();
  myPrevious = Top;
  Top        = this;
  GetMutex().Unlock();
}

//============================================================================
//==== Destructor : Delete the ErrorHandler and Abort if there is a 'Error'.
//============================================================================

void Standard_ErrorHandler::Destroy()
{
  Unlink();
  if (myStatus == Standard_HandlerJumped)
  {
    // jumped, but not caught
    Abort(myCaughtError);
  }
}

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

void Standard_ErrorHandler::Unlink()
{
  // put a lock on the stack
  GetMutex().Lock();

  Standard_ErrorHandler* aPrevious = 0;
  Standard_ErrorHandler* aCurrent  = Top;

  // locate this handler in the stack
  while (aCurrent != 0 && this != aCurrent)
  {
    aPrevious = aCurrent;
    aCurrent  = aCurrent->myPrevious;
  }

  if (aCurrent == 0)
  {
    GetMutex().Unlock();
    return;
  }

  if (aPrevious == 0)
  {
    // a top exception taken
    Top = aCurrent->myPrevious;
  }
  else
  {
    aPrevious->myPrevious = aCurrent->myPrevious;
  }
  myPrevious = 0;
  GetMutex().Unlock();

  // unlink and destroy all registered callbacks
  Standard_Address aPtr = aCurrent->myCallbackPtr;
  myCallbackPtr         = 0;
  while (aPtr)
  {
    Standard_ErrorHandler::Callback* aCallback = (Standard_ErrorHandler::Callback*)aPtr;
    aPtr                                       = aCallback->myNext;
    // Call destructor explicitly, as we know that it will not be called automatically
    aCallback->DestroyCallback();
  }
}

//=======================================================================
// function : IsInTryBlock
// purpose  :  test if the code is currently running in
//=======================================================================

Standard_Boolean Standard_ErrorHandler::IsInTryBlock()
{
  Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, Standard_False);
  return anActive != NULL;
}

//============================================================================
//==== Abort: make a longjmp to the saved Context.
//====    Abort if there is a non null 'Error'
//============================================================================

void Standard_ErrorHandler::Abort(const Handle(Standard_Failure)& theError)
{
  Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, Standard_True);

  //==== Check if can do the "longjmp" =======================================
  if (anActive == NULL)
  {
    std::cerr << "*** Abort *** an exception was raised, but no catch was found." << std::endl;
    if (!theError.IsNull())
      std::cerr << "\t... The exception is:" << theError->GetMessageString() << std::endl;
    exit(1);
  }

  anActive->myStatus = Standard_HandlerJumped;
  longjmp(anActive->myLabel, Standard_True);
}

//============================================================================
//==== Catches: If there is a 'Error', and it is in good type
//====          returns True and clean 'Error', else returns False.
//============================================================================

Standard_Boolean Standard_ErrorHandler::Catches(const Handle(Standard_Type)& AType)
{
  Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerJumped, Standard_False);
  if (anActive == 0)
    return Standard_False;

  if (anActive->myCaughtError.IsNull())
    return Standard_False;

  if (anActive->myCaughtError->IsKind(AType))
  {
    myStatus = Standard_HandlerProcessed;
    return Standard_True;
  }
  else
  {
    return Standard_False;
  }
}

Handle(Standard_Failure) Standard_ErrorHandler::LastCaughtError()
{
  Handle(Standard_Failure) aHandle;
  Standard_ErrorHandler*   anActive = FindHandler(Standard_HandlerProcessed, Standard_False);
  if (anActive != 0)
    aHandle = anActive->myCaughtError;

  return aHandle;
}

Handle(Standard_Failure) Standard_ErrorHandler::Error() const
{
  return myCaughtError;
}

void Standard_ErrorHandler::Error(const Handle(Standard_Failure)& theError)
{
  Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, Standard_False);
  if (anActive == NULL)
    Abort(theError);

  anActive->myCaughtError = theError;
}

Standard_ErrorHandler* Standard_ErrorHandler::FindHandler(const Standard_HandlerStatus theStatus,
                                                          const Standard_Boolean       theUnlink)
{
  // lock the stack
  GetMutex().Lock();

  // Find the current ErrorHandler according to thread
  Standard_ErrorHandler* aPrevious = 0;
  Standard_ErrorHandler* aCurrent  = Top;
  Standard_ErrorHandler* anActive  = 0;
  Standard_Boolean       aStop     = Standard_False;
  Standard_ThreadId      aTreadId  = GetThreadID();

  // searching an exception with correct ID number
  // which is not processed for the moment
  while (!aStop)
  {
    while (aCurrent != NULL && aTreadId != aCurrent->myThread)
    {
      aPrevious = aCurrent;
      aCurrent  = aCurrent->myPrevious;
    }

    if (aCurrent != NULL)
    {
      if (theStatus != aCurrent->myStatus)
      {

        if (theUnlink)
        {
          // unlink current
          if (aPrevious == 0)
          {
            // a top exception taken
            Top = aCurrent->myPrevious;
          }
          else
          {
            aPrevious->myPrevious = aCurrent->myPrevious;
          }
        }

        // shift
        aCurrent = aCurrent->myPrevious;
      }
      else
      {
        // found one
        anActive = aCurrent;
        aStop    = Standard_True;
      }
    }
    else
    {
      // Current is NULL, means that no handlesr
      aStop = Standard_True;
    }
  }
  GetMutex().Unlock();

  return anActive;
}

#if defined(OCC_CONVERT_SIGNALS)

Standard_ErrorHandler::Callback::Callback()
    : myHandler(0),
      myPrev(0),
      myNext(0)
{
}

Standard_ErrorHandler::Callback::~Callback()
{
  UnregisterCallback();
}

void Standard_ErrorHandler::Callback::RegisterCallback()
{
  if (myHandler)
    return; // already registered

  // find current active exception handler
  Standard_ErrorHandler* aHandler =
    Standard_ErrorHandler::FindHandler(Standard_HandlerVoid, Standard_False);

  // if found, add this callback object first to the list
  if (aHandler)
  {
    myHandler = aHandler;
    myNext    = aHandler->myCallbackPtr;
    if (myNext)
      ((Standard_ErrorHandler::Callback*)myNext)->myPrev = this;
    aHandler->myCallbackPtr = this;
  }
}

void Standard_ErrorHandler::Callback::UnregisterCallback()
{
  if (!myHandler)
    return;
  if (myNext)
    ((Standard_ErrorHandler::Callback*)myNext)->myPrev = myPrev;
  if (myPrev)
    ((Standard_ErrorHandler::Callback*)myPrev)->myNext = myNext;
  else if (((Standard_ErrorHandler*)myHandler)->myCallbackPtr == this)
    ((Standard_ErrorHandler*)myHandler)->myCallbackPtr = (Standard_ErrorHandler::Callback*)myNext;
  myHandler = myNext = myPrev = 0;
}
#endif
