/** \file
 *
 *  Contains the PCaller class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "PCaller.h"

#include <cstdlib>
#include <execinfo.h>
#include <fenv.h>
#include <linux/sched.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef SCHED_IDLE
#define SCHED_IDLE      5
#endif

/// Runs static intialization code
PCaller::Initializer PCaller::Initializer_;

/// The actual static intialization code
/// Sets up signal handling
PCaller::Initializer::Initializer()
{
    PCaller::RegisterDefaultSignals();
}

FlexArray<PCaller*> PCaller::PCallers_( false );

const char PCaller::SIGNAL_LOG_FILE[] = "Data/backtrace";

/// Constructor
PCaller::PCaller( bool idleThread, Logger& logger )
    : logger_( logger ),
      pCallMutex_(),
      threadConditionMutex_(),
      threadCondition_( threadConditionMutex_ ),
      containedThread_( 0 ),
      ownerThread_( pthread_self() ),
      runningThread_( ownerThread_ ),
      currentComponent_( NULL ),
      threadAttributes_(),
      threadRunning_( false ),
      threadDone_( false ),
      idle_( idleThread )
{
    currentOperation_.component_ = NULL;
    currentOperation_.operation_ = PCALLER_START;
    currentOperation_.returnValue_ = NULL;

    PCallers_.push( this );

    //printf("Creating PCaller for %s\n", logger_.getFacility().c_str() );
    pthread_attr_init( &threadAttributes_ );
    //size_t stackSize;
    //pthread_attr_getstacksize( &threadAttributes_, &stackSize );
    //printf( "Thread stack size was %d bytes\n", stackSize);
    pthread_attr_setstacksize( &threadAttributes_, 3 * 16384 * sizeof( size_t ) );
    //pthread_attr_getstacksize( &threadAttributes_, &stackSize );
    //printf( "Thread stack size is now %d bytes, which is > %d\n", stackSize, PTHREAD_STACK_MIN);

    startThread();
}

bool PCaller::startThread()
{
    // If thread is already running, stop it.
    stopThread();

    //Start the thread...
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("starting thread\n"); fflush( stdout );

    int errCode = pthread_create( &containedThread_, &threadAttributes_, PThreadRun, this );
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("started thread %08X with errorCode %d\n", (int)containedThread_, errCode ); fflush( stdout );

    threadRunning_ = ( 0 == errCode );
    if( !threadRunning_ )
    {
        containedThread_ = 0;
        logger_.syslog( "Could not create thread for making protected calls, got ", strerror( errCode ), Syslog::CRITICAL );
        //printf("Could not create thread for making protected calls, got %s\n", strerror( errCode ) ); fflush( stdout );
        return false;
    }

    if( idle_ )
    {
        int policy = SCHED_IDLE;
        const struct sched_param param = { 0 };
        pthread_setschedparam( containedThread_, policy, &param );
    }

    logger_.syslog( "Created PCaller Thread at ", ( size_t )containedThread_, Syslog::DEBUG );

    //And wait for it to get going...
    currentOperation_.operation_ = BLOCK_NO_OPERATION;
    yieldToContained();
    ownerWait();
    //printf("Done creating PCaller for %s\n", logger_.getFacility().c_str() );
    return true;
}

void PCaller::stopThread()
{
    if( containedThread_ )
    {
        //join the old thread...
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("joining the old thread %08X\n", (int)containedThread_); fflush( stdout );
        pthread_join( containedThread_, NULL );
        containedThread_ = 0;
        threadRunning_ = false;
    }
}

/// Destructor
PCaller::~PCaller()
{
    if( threadRunning_ && containedThread_ )
    {
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("terminating thread %08X\n", (int)containedThread_);
        currentComponent_ = NULL;

        currentOperation_.operation_ = PCALLER_TERMINATE;
        yieldToContained();
    }
    stopThread();

    //if(logger_.getFacility() == "Pi ThreadHandler")printf("done with thread %08X\n", (int)containedThread_);

    pthread_attr_destroy( &threadAttributes_ );

    PCallers_.pop( this );
}

/// Executes the specified method of this component with signal catching
/// Returns true on normal run termination, false if a signal has
/// occurred.
bool PCaller::pCall( Component& component, PCallOperationType operation, bool* returnValue )
{
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("start pCall( %s, %s )\n", component.getName().c_str(), PCallOperationToChars(operation)); fflush( stdout );
    if( component.isFailed() && ( operation == BLOCK_EXECUTE || operation == BLOCK_EXECUTE_IF_UNSATISFIED ) )
    {
        if( component.isFailureUninitialized() )
        {
            return false;
        }
        if( component.isUsingRunState() )
        {
            component.setFailureUninitialized( Component::STOPPED == component.getRunState() );
            component.requestStop();
        }
        else
        {
            component.setFailureUninitialized( true );
            operation = BLOCK_UNINITIALIZE;
        }

    }

    if( component.isFailureUninitialized() && !component.isFailed() )
    {
        if( component.isUsingRunState() )
        {
            component.requestStart();
        }
        else
        {
            operation = BLOCK_INITIALIZE;
        }
        component.setFailureUninitialized( false );
    }

    if( !threadRunning_ )
    {
        if( !startThread() )
        {
            return false;
        }
    }

    //if(logger_.getFacility() == "Pi ThreadHandler")printf("pCallMutex_.lock()\n"); fflush( stdout );
    pCallMutex_.lock();
    //TODO: Not sure I like this -- maybe we should probably be more restrictive!
    ownerThread_ = pthread_self();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("pCall( %s, %s )\n", component.getName().c_str(), PCallOperationToChars(operation)); fflush( stdout );
    currentComponent_ = &component;
    currentOperation_.component_ = &component;
    currentOperation_.operation_ = operation;
    currentOperation_.returnValue_ = returnValue;

    //if(logger_.getFacility() == "Pi ThreadHandler")printf("yieldToContained()\n"); fflush( stdout );
    yieldToContained();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("ownerWait()\n"); fflush( stdout );
    ownerWait();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("pCallMutex_.unlock()\n"); fflush( stdout );
    pCallMutex_.unlock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("done pCall( %s, %s )\n", component.getName().c_str(), PCallOperationToChars(operation)); fflush( stdout );
    return true;
}

void PCaller::LogTrace( Component* component, int signal, int errnum, int code, size_t addr, size_t backtraceCount, char **backtraceStrings )
{
    const char * msg = SignalMessage( signal, code );
    Logger& logger = component->getLogger();
    Str tracePrefix;
    Str traceStr;
    int ret = -1;

    traceStr = "Backtrace:\n";
    for( size_t i = 0 ; i < backtraceCount; ++i )
    {
        traceStr += backtraceStrings[i];
        traceStr += "\n";
    }

    // Log error msg + trace to the signal backtrace file
    FILE* fid = fopen( SIGNAL_LOG_FILE, "a" );
    if( fid != NULL )
    {
        tracePrefix = Str( Timestamp::Now().asDouble() );
        tracePrefix += " [" + component->getName() + "]: ";

        if( NULL != msg )
        {
            fwrite( tracePrefix.cStr(), tracePrefix.size(), 1, fid );
            fwrite( msg, strlen( msg ), 1, fid );
            fwrite( "\n", 1, 1, fid );
        }
        fwrite( tracePrefix.cStr(), tracePrefix.size(), 1, fid );
        fwrite( traceStr.cStr(), traceStr.size(), 1, fid );
        fwrite( "\n", 1, 1, fid );

        // Flush the stream I/O buffers...
        fflush( fid );
        // ...and commit to stable storage
        ret = fsync( fileno( fid ) );
        fclose( fid );
    }

    // Log error msg to syslog
    if( NULL != msg )
    {
        logger.syslog( msg, Syslog::CRITICAL );
        if( ret < 0 )
        {
            // Sync to stable storage failed... Try dumping the trace in the syslog
            logger.syslog( traceStr, Syslog::CRITICAL );
        }
    }
    else
    {
        logger.syslog( "Run-time exception #", signal, Syslog::CRITICAL );
    }
}

/// Wait for another thread to activate this thread.
void PCaller::ownerWait()
{
    // wait for this thread to become active again
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner locking in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.lock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner locked in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    while( runningThread_ != pthread_self() )
    {
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner waiting in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
        threadCondition_.wait();
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner done waiting in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    }
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner unlocking in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.unlock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s owner unlocked in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
}

/// The entry point for the protected thread.  A static function.
/// Wait for another thread to activate this thread.
void PCaller::containedWait()
{
    // wait for this thread to become active again
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s ) locking in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.lock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s) locked in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    while( runningThread_ != pthread_self() )
    {
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s) waiting in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
        threadCondition_.wait();
        //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s) done waiting in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    }
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s) unlocking in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.unlock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s contained (%s) unlocked in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
}

/// Just yield execution to the waiting contained thread
void PCaller::yieldToContained()
{
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s locking to Contained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.lock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s locked to Contained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    runningThread_ = containedThread_;
    // wake up just the waiting thread
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s signalingToContained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(),(int)pthread_self()); fflush( stdout );
    threadCondition_.signal();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s signaledToContained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s unlocking to Contained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.unlock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s unlocked to Contained (%s) in thread %08X\n", logger_.getFacility().c_str(), NULL==currentComponent_?"NULL":currentComponent_->getName().c_str(), (int)pthread_self()); fflush( stdout );
}

/// Just yield execution to the waiting owner thread
void PCaller::yieldToOwner()
{
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s locking to Owner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.lock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s locked to Owner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    runningThread_ = ownerThread_;
    // wake up just the waiting thread
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s signalToOwner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    threadCondition_.signal();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s signaledToOwner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s unlocking to Owner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
    threadConditionMutex_.unlock();
    //if(logger_.getFacility() == "Pi ThreadHandler")printf("%s unlocked to Owner in thread %08X\n", logger_.getFacility().c_str(), (int)pthread_self()); fflush( stdout );
}

const char* PCaller::PCallOperationToChars( const PCallOperationType operation )
{
    switch( operation )
    {
    case BLOCK_NO_OPERATION:
        return "BLOCK_NO_OPERATION";
    case BLOCK_INITIALIZE:
        return "BLOCK_INITIALIZE";
    case BLOCK_EXECUTE:
        return "BLOCK_EXECUTE";
    case BLOCK_EXECUTE_IF_UNSATISFIED:
        return "BLOCK_EXECUTE_IF_UNSATISFIED";
    case BLOCK_UNINITIALIZE:
        return "BLOCK_UNINITIALIZE";
    case PCALLER_START:
        return "PCALLER_START";
    case PCALLER_TERMINATE:
        return "PCALLER_TERMINATE";
    default:
        return "?";
    }
}

void* PCaller::PThreadRun( void* pPCaller )
{
    // Some settings...
    pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL );
    pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, NULL );

    // That's enough being static.
    PCaller* pCaller = ( PCaller* ) pPCaller;
    pCaller->containedThread_ = pthread_self();
    PCallOperation& currentOperation = pCaller->currentOperation_;
    bool loggedId = false;

    // Loop until the thread is done
    while( !pCaller->threadDone_ )
    {
        //if(pCaller->logger_.getFacility() == "Pi ThreadHandler")printf("PThreadRun starting contained wait\n"); fflush( stdout );
        pCaller->containedWait();
        //if(pCaller->logger_.getFacility() == "Pi ThreadHandler")printf("PThreadRun done contained wait\n"); fflush( stdout );
        if( !loggedId )
        {
            pCaller->logger_.syslog( "Protected caller Thread ID is ", ( int )syscall( SYS_gettid ), Syslog::INFO );
            loggedId = true;
        }
        // Do the call
        Component* component( currentOperation.component_ );
        Behavior* missionComponent( NULL );
        switch( currentOperation.operation_ )
        {
        case BLOCK_NO_OPERATION:
            break;
        case BLOCK_INITIALIZE:
            component->initialize();
            break;
        case BLOCK_EXECUTE:
            component->execute();
            break;
        case BLOCK_EXECUTE_IF_UNSATISFIED:
            missionComponent = dynamic_cast<Behavior*>( component );
            if( missionComponent != NULL && currentOperation.returnValue_ != NULL )
            {
                *( currentOperation.returnValue_ ) = missionComponent->executeIfUnsatisfied();
            }
            break;
        case BLOCK_UNINITIALIZE:
            component->uninitialize();
            break;
        case PCALLER_START:
            break;
        case PCALLER_TERMINATE:
            pCaller->threadDone_ = true;
            break;
        }
        if( !pCaller->threadDone_ )
        {
            /// Give up execution, and wait (if not done)
            pCaller->yieldToOwner();
        }
    }
    return NULL;
}

void PCaller::RegisterDefaultSignals( void )
{
    // Make sure we are throwing all floating point exceptions
#ifndef __arm__
#ifndef __APPLE_CC__
    feenableexcept( ( FE_ALL_EXCEPT ^ FE_INEXACT ) ^ FE_UNDERFLOW );
#endif
#endif // #ifndef __arm__

    // Shamelessly lifted from runAllTests.cpp from the Cxxtest plug-in for Eclipse
    struct sigaction act;
    act.sa_sigaction = PCaller::DefaultSignalHandler;
    act.sa_flags = SA_SIGINFO
#ifndef __APPLE_CC__
                   | SA_NOMASK
#endif
                   ;
    // Not sure if this is the correct thing to do, but valgrind reports
    // sigaction is using an uninitialized value (act->sa_mask)
#ifdef __APPLE_CC__
    memset( & ( act.sa_mask ), 0, sizeof( act.sa_mask ) );
#elif defined (__arm__)
    __sigemptyset( & ( act.sa_mask ) );
#else
    sigemptyset( & ( act.sa_mask ) );
#endif
    sigaction( SIGSEGV, &act, 0 );
    sigaction( SIGFPE, &act, 0 );
    sigaction( SIGILL, &act, 0 );
    sigaction( SIGBUS, &act, 0 );
    sigaction( SIGABRT, &act, 0 );
    sigaction( SIGTRAP, &act, 0 );
    sigaction( SIGSYS, &act, 0 );
    sigaction( SIGALRM, &act, 0 );
}

void PCaller::DefaultSignalHandler( int signal, siginfo_t *sigInfo, void *context )
{
    void* foo;
    void* traces[ 40 ];
    int backtraceCount;
    char **backtraceStrings;
    backtraceCount = backtrace( traces, 40 );
    backtraceStrings = backtrace_symbols( traces, backtraceCount );
    printf( "Caught signal #%d in thread 0x%08zX with stack at 0x%08zX (err#%d at addr 0x%08zX): %s\n", signal, ( size_t )pthread_self(), ( size_t )&foo, sigInfo->si_errno, ( size_t )sigInfo->si_addr, SignalMessage( signal, sigInfo->si_code ) );
    for( int i = 0; i < backtraceCount; ++i )
    {
        printf( "%s\n", backtraceStrings[i] );
    }
    for( int i = PCallers_.getMinIndex(); i <= PCallers_.getMaxIndex(); ++i )
    {
        PCaller* pCaller = PCallers_[i];
        if( NULL != pCaller && pCaller->containedThread_ == pthread_self() )
        {
            Component* component = pCaller->currentComponent_;
            if( NULL != component )
            {
                LogTrace( component, signal, sigInfo->si_errno,
                          sigInfo->si_code, ( size_t )sigInfo->si_addr,
                          backtraceCount, backtraceStrings );
                component->setFailure( FailureMode::SOFTWARE );
                pCaller->threadRunning_ = false;
                pCaller->yieldToOwner();
                break;
            }
        }
    }

    free( backtraceStrings );
    fflush( stdout );
    pthread_exit( NULL );
}

const char *PCaller::SignalMessage( int signal, int code )
{
    const char * msg = NULL;
    switch( signal )
    {
    case SIGILL:
        switch( code )
        {
        case ILL_ILLOPC:
            msg = "SIGILL: illegal opcode";
            break;
        case ILL_ILLOPN:
            msg = "SIGILL: illegal operand";
            break;
        case ILL_ILLADR:
            msg = "SIGILL: illegal addressing mode";
            break;
        case ILL_ILLTRP:
            msg = "SIGILL: illegal trap";
            break;
        case ILL_PRVOPC:
            msg = "SIGILL: privileged opcode";
            break;
        case ILL_PRVREG:
            msg = "SIGILL: privileged register";
            break;
        case ILL_COPROC:
            msg = "SIGILL: coprocessor error";
            break;
        case ILL_BADSTK:
            msg = "SIGILL: internal stack error";
            break;
        }
        break;

    case SIGFPE:
        switch( code )
        {
        case FPE_INTDIV:
            msg = "SIGFPE: integer divide by zero";
            break;
        case FPE_INTOVF:
            msg = "SIGFPE: integer overflow";
            break;
        case FPE_FLTDIV:
            msg = "SIGFPE: floating point divide by zero";
            break;
        case FPE_FLTOVF:
            msg = "SIGFPE: floating point overflow";
            break;
        case FPE_FLTUND:
            msg = "SIGFPE: floating point underflow";
            break;
        case FPE_FLTRES:
            msg = "SIGFPE: floating point inexact result";
            break;
        case FPE_FLTINV:
            msg = "SIGFPE: floating point invalid operation";
            break;
        case FPE_FLTSUB:
            msg = "SIGFPE: subscript out of range";
            break;
        }
        break;

    case SIGSEGV:
        switch( code )
        {
        case SEGV_MAPERR:
            msg = "SIGSEGV: address not mapped to object";
            break;
        case SEGV_ACCERR:
            msg = "SIGSEGV: invalid permissions for mapped object";
            break;
        }
        break;

    case SIGBUS:
        switch( code )
        {
        case BUS_ADRALN:
            msg = "SIGBUS: invalid address alignment";
            break;
        case BUS_ADRERR:
            msg = "SIGBUS: non-existant physical address";
            break;
        case BUS_OBJERR:
            msg = "SIGBUS: object specific hardware error";
            break;
        }
        break;

    case SIGTRAP:
        switch( code )
        {
        case TRAP_BRKPT:
            msg = "SIGTRAP: process breakpoint";
            break;
        case TRAP_TRACE:
            msg = "SIGTRAP: process trace trap";
            break;
        }
        break;

    case SIGSYS:
        msg = "SIGSYS: bad argument to system call";
        break;

    case SIGABRT:
        msg = "SIGABRT: execution aborted "
              "(failed assertion, corrupted heap, or other problem?)";
        break;

    case SIGALRM:
        msg = "SIGALRM: allotted time expired";
        break;
    }
    return msg;
}
