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

#include "EventEntry.h"

#include "component/Component.h"
#include "data/Slate.h"
#include "utils/RingBuffer.h"

RingBufferVoid* EventEntry::MallocRing_( NULL );

EventEntry::StaticDestructor EventEntry::StaticDestructor_;

Mutex EventEntry::Mutex_;

EventEntry::StaticDestructor::~StaticDestructor()
{
    delete MallocRing_;
    MallocRing_ = NULL;
}

/// EventType Constructor
EventEntry::EventEntry( Component* actor,
                        DataElement* dataElement,
                        const EventType eventType )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, Timestamp::Timestamp::NOT_SET_TIME ),
      actorCode_( NULL != actor ? actor->getName().getCode() : CodedStr::NO_CODE ),
      dataElementCode_( NULL != dataElement ? dataElement->getUri().getCode() : ElementURI::NO_CODE ),
      eventType_( eventType ),
      dataStateChange_( NO_DATA_STATE_CHANGE ),
      accuracy_( NULL != dataElement ? dataElement->getBaseAccuracy() : 0.0 )
{}

/// DataStateChange Constructor
EventEntry::EventEntry( Component* actor,
                        DataElement* dataElement,
                        const DataStateChange dataStateChange )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, Timestamp::Timestamp::NOT_SET_TIME ),
      actorCode_( NULL != actor ? actor->getName().getCode() : CodedStr::NO_CODE ),
      dataElementCode_( NULL != dataElement ? dataElement->getUri().getCode() : ElementURI::NO_CODE ),
      eventType_( SET_DATA_STATE ),
      dataStateChange_( dataStateChange ),
      accuracy_( NULL != dataElement ? dataElement->getBaseAccuracy() : 0.0 )
{}

/// ComponentAction = COMPONENT_LOAD Constructor
EventEntry::EventEntry( Component* actor,
                        const bool loaded )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, Timestamp::Timestamp::NOT_SET_TIME ),
      actorCode_( NULL != actor ? actor->getName().getCode() : CodedStr::NO_CODE ),
      loaded_( loaded ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_LOAD ),
      failCount_( 0 )
{}

/// ComponentAction = COMPONENT_RUN Constructor
EventEntry::EventEntry( Component* actor,
                        const int loop )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, Timestamp::Timestamp::NOT_SET_TIME ),
      actorCode_( NULL != actor ? actor->getName().getCode() : CodedStr::NO_CODE ),
      runLoop_( loop ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_MISSION_RUN )
{}

/// ComponentAction = COMPONENT_FAIL Constructor
EventEntry::EventEntry( Component* actor,
                        const FailureMode::FailType failure,
                        const int failCount )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, Timestamp::Timestamp::NOT_SET_TIME ),
      actorCode_( NULL != actor ? actor->getName().getCode() : CodedStr::NO_CODE ),
      failureMode_( failure ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_FAIL ),
      failCount_( failCount )
{}

/// Destructor
EventEntry::~EventEntry()
{}

/// Override new
void* EventEntry::operator new( size_t size )
{
    MutexLocker lock( Mutex_ );
    void* newMem = NULL;
    if( size != sizeof( EventEntry ) )
    {
        printf( "Aborting: Requested %zu bytes for a %zu byte EventEntry\n", size, sizeof( EventEntry ) );
        exit( 1 );
    }
    else
    {
        if( NULL == MallocRing_ )
        {
            MallocRing_ = new RingBufferVoid( true );
        }
        newMem = MallocRing_->pop();
    }
    if( NULL == newMem )
    {
        newMem = malloc( size );
    }
    return newMem;
}

/// Override delete
void EventEntry::operator delete( void* p )
{
    MutexLocker lock( Mutex_ );
    if( NULL != MallocRing_ )
    {
        MallocRing_->push( p );
    }
    else
    {
        free( p );
    }
}

/// Read new EventEntry from a stream.
EventEntry* EventEntry::Read( Timestamp& timeBase, Timespan& timeAdjust, InStream &inStream, unsigned short firstWord, Logger& logger, bool& error )
{
    // Format
    //EVENT_LOG_ENTRY = 0x0000,  // (2b)<--dataAction(2b)&actor(12b), element, timespan, accuracy(64b)|state(8b)
    // or 0x00000000, timestamp(56b micros): start of cycle.

    unsigned short eventType = firstWord & EVENT_TYPE_MASK;
    if( eventType == START_CYCLE )
    {
        long long micros( 0 );
        if( inStream.read( ( ( char* )&micros ), 7 ).bytesRead() != 7 )
        {
            logger.syslog( "Encountered eof while reading start of cycle timestamp", Syslog::ERROR );
            return NULL;
        }

        Timestamp newTimeBase( Timestamp::NOT_SET_TIME );
        if( timeAdjust == Timespan::INVALID_TIMESPAN )
        {
            newTimeBase = Timestamp::FromMicros( micros );
        }
        else
        {
            newTimeBase = Timestamp::FromMicros( micros ) + timeAdjust;
        }

        // Temporary workaround to prevent erroneous parsing of timestamps until we take the
        // time to reformat the LRAUV binary packet structure. Basically, ignore timestamps
        // set before beginning of epoch and timestamps that are further than a month in
        // the future from time of read (2628000 seconds).
        if( newTimeBase < Timestamp::EPOCH_START_TIME || ( newTimeBase.asFloat() > ( Timestamp::Now().asFloat() + 2628000 ) ) )
        {
            logger.syslog( "EventEntry timebase value of " + Str( newTimeBase.asFloat(), 2 ) + " is out of bounds.", Syslog::ERROR );
            logger.syslog( "inStream at 0x", inStream.getTotalBytesRead(), Syslog::ERROR );
            error = true;
            return NULL;
        }
        else
        {
            timeBase = newTimeBase;
        }

        EventEntry *startCycle = new EventEntry( NULL, NULL, START_CYCLE );
        startCycle->setTimestamp( timeBase );
        return startCycle;
    }

    unsigned short actorCode = firstWord >> 4;

    unsigned short dataElementCode( ElementURI::NO_CODE );
    if( eventType != COMPONENT_ACTION )
    {
        if( inStream.readUShortCompact( dataElementCode ).eof() )
        {
            logger.syslog( "Encountered eof while reading Data Element code", Syslog::ERROR );
            return NULL;
        }
    }

    bool changed;
    Timestamp timestamp( timeBase );
    if( !ReadTimestamp( timestamp, changed, inStream, logger, Logger::TIME_PRECISION_MILLIS ) )
    {
        return NULL;
    }

    if( eventType == SET_DATA_ACCURACY )
    {
        float accuracy( 0.0 );
        if( inStream.read( ( ( char* )&accuracy ) + 2, 2 ).bytesRead() != 2 )
        {
            logger.syslog( "Encountered eof while reading Data Element accuracy", Syslog::ERROR );
            return NULL;
        }
        return new EventEntry( timestamp, actorCode, dataElementCode, SET_DATA_ACCURACY, accuracy );
    }
    if( eventType == SET_DATA_STATE )
    {
        signed char dataStateChange;
        if( inStream.readChar( dataStateChange ).bytesRead() != 1 )
        {
            logger.syslog( "Encountered eof while reading Data Element state change", Syslog::ERROR );
            return NULL;
        }
        return new EventEntry( timestamp, actorCode, dataElementCode, ( DataStateChange )( ( int )dataStateChange & 0xFF ) );
    }
    if( eventType == COMPONENT_ACTION )
    {
        signed char componentAction;
        if( inStream.readChar( componentAction ).bytesRead() != 1 )
        {
            logger.syslog( "Encountered eof while reading Component Action", Syslog::ERROR );
            return NULL;
        }
        switch( componentAction )
        {
        case COMPONENT_LOAD:
        {
            signed char loaded;
            if( inStream.readChar( loaded ).bytesRead() != 1 )
            {
                logger.syslog( "Encountered eof while reading Loaded state", Syslog::ERROR );
                return NULL;
            }
            return new EventEntry( timestamp, actorCode, ( bool )loaded );
        }
        case COMPONENT_MISSION_RUN:
        {
            int runLoop;
            if( inStream.readInt24( runLoop ).bytesRead() != 3 )
            {
                logger.syslog( "Encountered eof while reading Run Loop", Syslog::ERROR );
                return NULL;
            }
            return new EventEntry( timestamp, actorCode, runLoop );
        }
        case COMPONENT_FAIL:
        {
            signed char failureMode;
            if( inStream.readChar( failureMode ).bytesRead() != 1 )
            {
                logger.syslog( "Encountered eof while reading Failure Mode", Syslog::ERROR );
                return NULL;
            }
            int failCount;
            if( inStream.readInt24( failCount ).bytesRead() != 3 )
            {
                logger.syslog( "Encountered eof while reading Fail Count", Syslog::ERROR );
                return NULL;
            }
            return new EventEntry( timestamp, actorCode, ( FailureMode::FailType )failureMode, failCount );
        }
        }
    }
    return NULL;
}

unsigned int EventEntry::write( Timestamp& timeBase, OutStream& logStream ) const
{
    unsigned int bytesWritten( 0 );

    if( eventType_ == START_CYCLE )
    {
        // Cycle start, 8 bytes
        timeBase = timestamp_;
        long long micros = timeBase.asCompactMicros();
        bytesWritten += logStream.writeUShortCompact(
                            EVENT_LOG_ENTRY | START_CYCLE
                        ).bytesWritten();
        bytesWritten += logStream.write( ( const char* ) & ( micros ), 7 ).bytesWritten();
    }
    else
    {
        // Log the Type | Action | Actor code as 2 bytes
        bytesWritten += logStream.writeUShortCompact(
                            EVENT_LOG_ENTRY | eventType_ | ( actorCode_ << 4 )
                        ).bytesWritten();

        if( eventType_ != COMPONENT_ACTION )
        {
            // Log the element
            unsigned short dataElementCode( getDataElementCode() );
            bytesWritten += logStream.writeUShortCompact( dataElementCode ).bytesWritten();
        }

        // Log the timestamp
        bytesWritten += writeTimestamp( timeBase, true, logStream, Logger::TIME_PRECISION_MILLIS );

        if( eventType_ == SET_DATA_ACCURACY )
        {
            //bytesWritten += logStream.writeFloat2( dataElement->getBaseAccuracy() ).gcount();
            bytesWritten += logStream.writeFloat2( getAccuracy() ).bytesWritten();
        }
        else if( eventType_ == SET_DATA_STATE )
        {
            bytesWritten += logStream.writeChar( getDataStateChange() ).bytesWritten();
        }
        else if( eventType_ == COMPONENT_ACTION )
        {
            bytesWritten += logStream.writeChar( getComponentAction() ).bytesWritten();
            switch( getComponentAction() )
            {
            case COMPONENT_LOAD:
            {
                unsigned char loaded = getLoaded();
                bytesWritten += logStream.writeChar( loaded ).bytesWritten();
                break;
            }
            case COMPONENT_MISSION_RUN:
                bytesWritten += logStream.writeInt24( getRunLoop() ).bytesWritten();
                break;
            case COMPONENT_FAIL:
            {
                bytesWritten += logStream.writeChar( getFailureMode() ).bytesWritten();
                bytesWritten += logStream.writeInt24( getFailCount() ).bytesWritten();
                break;
            }
            }
        }
    }
    return bytesWritten;
}

Str EventEntry::toString( const Unit* unit ) const
{
    int precision = eventType_ == START_CYCLE ? 5 : 3;

    /// A little ugliness here
    char buf[ 80 ];
    timestamp_.toString( buf, 79, precision );

    /// Pad with a space
    unsigned int buflen = strlen( buf );
    if( buflen < 79 )
    {
        buf[ buflen + 1 ] = 0;
        buf[ buflen ] = ',';
    }

    if( actorCode_ == CodedStr::NO_CODE )
    {
        return Str( buf ) + Str( timestamp_.asDouble(), precision ) + " " + EventTypeToString( eventType_ );
    }

    CodedStr* actorName = Slate::GetName( actorCode_ );

    if( eventType_ == COMPONENT_ACTION )
    {
        return Str( buf )
               + Str( timestamp_.asDouble(), precision ) + " "
               + "["
               + ( actorName == NULL ? UNKNOWN : *actorName )
               + "] "
               + makeComponentActionString();
    }

    // If it's not a component action, then we have a dataElementCode.
    ElementURI* elementName = Slate::GetElementURI( dataElementCode_ );
    return Str( buf )
           + Str( timestamp_.asDouble(), precision ) + " "
           + "["
           + ( actorName == NULL ? UNKNOWN : *actorName )
           + "]("
           + EventTypeToString( eventType_ )
           + " of "
           + ( elementName == NULL ? UNKNOWN : *elementName )
           + "): "
           + makeDataValueString( );

}

const Str EventEntry::makeComponentActionString() const
{
    switch( componentAction_ )
    {
    case COMPONENT_LOAD:             /// Followed by boolean true or false to indicate load/unload
        return loaded_ ? "Loaded" : "Unloaded";
    case COMPONENT_MISSION_RUN:      /// Followed by 0=done, positive=loop#, negative=preempted in loop#
        return runLoop_ == -1 ? "Preempted" : ( runLoop_ == 0 ? "Stopped" : ( "Running Loop=" + Str( runLoop_ ) ) );
    case COMPONENT_FAIL:             /// Followed by failtype and failcount
        return FailureMode::ToString( failureMode_ ) + ", FailCount= " + failCount_;
    default:
        return "Unknown Component Action";
    }
}

const Str EventEntry::makeDataValueString() const
{
    if( eventType_ == SET_DATA_ACCURACY )
    {
        return Str( accuracy_ );
    }
    if( eventType_ == SET_DATA_STATE )
    {
        return StateChangeToString( dataStateChange_ );
    }
    return "";
}

/// Convert an Action to a char*
const char *EventEntry::EventTypeToString( EventType eventType )
{
    switch( eventType )
    {
    case START_CYCLE:
        return "started cycle";
    case SET_DATA_ACCURACY:
        return "set accuracy";
    case SET_DATA_STATE:
        return "set state";
    case COMPONENT_ACTION:
        return "component";
    }
    return "(undefined)";
}

/// Convert an Action to a char*
const char *EventEntry::StateChangeToString( DataStateChange dataStateChange )
{
    switch( dataStateChange )
    {
    case NO_DATA_STATE_CHANGE:
        return "";
    case DATA_STATE_CHANGE_UNAVAILABLE:
        return " unavilable";
    case DATA_STATE_CHANGE_AVAILABLE:
        return " available";
    case DATA_STATE_CHANGE_ORPHANED:
        return " orphaned";
    case DATA_STATE_CHANGE_UNORPHANED:
        return " unorphaned";
    case DATA_STATE_CHANGE_SET_BEST:
        return " best";
    case DATA_STATE_CHANGE_INVALID:
        return " invalid";
    case DATA_STATE_CHANGE_VALID:
        return " valid";
    }
    return "(undefined)";
}

/// Protected EventType Constructor
EventEntry::EventEntry( const Timestamp& timestamp,
                        const unsigned short actorCode,
                        const unsigned short dataElementCode,
                        const EventType dataAction,
                        const double& accuracy )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, timestamp ),
      actorCode_( actorCode ),
      dataElementCode_( dataElementCode ),
      eventType_( dataAction ),
      dataStateChange_( NO_DATA_STATE_CHANGE ),
      accuracy_( accuracy )
{}

/// Protected DataStateChange Constructor
EventEntry::EventEntry( const Timestamp& timestamp,
                        const unsigned short actorCode,
                        const unsigned short dataElementCode,
                        const DataStateChange dataStateChange,
                        const double& accuracy )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, timestamp ),
      actorCode_( actorCode ),
      dataElementCode_( dataElementCode ),
      eventType_( SET_DATA_STATE ),
      dataStateChange_( dataStateChange ),
      accuracy_( accuracy )
{}

/// Protected ComponentAction = COMPONENT_LOAD Constructor
EventEntry::EventEntry( const Timestamp& timestamp,
                        const unsigned short actorCode,
                        const bool loaded )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, timestamp ),
      actorCode_( actorCode ),
      loaded_( loaded ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_LOAD ),
      failCount_( 0 )
{}

/// Protected ComponentAction = COMPONENT_RUN Constructor
EventEntry::EventEntry( const Timestamp& timestamp,
                        const unsigned short actorCode,
                        const int loop )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, timestamp ),
      actorCode_( actorCode ),
      runLoop_( loop ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_MISSION_RUN )
{}

/// Protected ComponentAction = COMPONENT_FAIL Constructor
EventEntry::EventEntry( const Timestamp& timestamp,
                        const unsigned short actorCode,
                        const FailureMode::FailType failure,
                        const int failCount )
    : LogEntry( LogEntry::EVENT_LOG_ENTRY, timestamp ),
      actorCode_( actorCode ),
      failureMode_( failure ),
      eventType_( COMPONENT_ACTION ),
      componentAction_( COMPONENT_FAIL ),
      failCount_( failCount )
{}

