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

#include "SyslogEntry.h"

#include "data/Slate.h"
#include "logger/Logger.h"
#include "utils/AuvMath.h"
#include "utils/CodedStr.h"
#include "utils/RingBuffer.h"
#include "utils/Timestamp.h"

RingBufferVoid* SyslogEntry::MallocRing_( NULL );

SyslogEntry::StaticDestructor SyslogEntry::StaticDestructor_;

Mutex SyslogEntry::Mutex_;

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

SyslogEntry::SyslogEntry( const Str &inMsg, Syslog::Severity sev, const CodedStr& loggerFacility,
                          const Timestamp& timestamp, const Service::ServiceType serviceType )
    : LogEntry( SYSLOG_LOG_ENTRY, timestamp, serviceType ),
      message_( inMsg ),
      severity_( sev ),
      loggerFacility_( loggerFacility )
{}

/// Copy Constructor
SyslogEntry::SyslogEntry( const SyslogEntry& syslogEntry )
    : LogEntry( SYSLOG_LOG_ENTRY, syslogEntry.timestamp_ ),
      message_( syslogEntry.message_ ),
      severity_( syslogEntry.severity_ ),
      loggerFacility_( syslogEntry.loggerFacility_ )
{}

SyslogEntry::~SyslogEntry()
{}

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

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

/// Read new SyslogEntry from a stream.
SyslogEntry* SyslogEntry::Read( Timestamp& timeBase, Timespan& timeAdjust, InStream &inStream, unsigned short firstWord, Logger& logger, bool& error )
{
    // Format
    //SYSLOG_LOG_ENTRY = 0xC000,  // (2b)<--sev(2b)&logger(12b), timespan, size, chars

    unsigned short severity = firstWord & Syslog::SEVERITY_MASK;
    unsigned short loggerFacilityCode = firstWord >> 5;

    CodedStr* loggerFacility = Slate::GetName( loggerFacilityCode );
    if( NULL == loggerFacility )
    {
        logger.syslog( "No LoggerFacility found for code ", loggerFacilityCode, Syslog::ERROR );
        error = true;
        return NULL;
    }

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

    unsigned short length;
    if( inStream.readUShortCompact( length ).eof() )
    {
        logger.syslog( "Encountered eof while reading Syslog entry length", Syslog::ERROR );
        return NULL;
    }

    if( length > 0 )
    {
        char message[length + 1];
        message[length] = 0;
        if( inStream.read( message, length ).bytesRead() != length )
        {
            logger.syslog( "Encountered eof while reading Syslog message", Syslog::ERROR );
            return NULL;
        }

        SyslogEntry* newEntry = new SyslogEntry( message, ( Syslog::Severity ) severity, *loggerFacility );

        if( timeAdjust != Timespan::INVALID_TIMESPAN && 0 == strncmp( message, "GPS fix at: ", 12 ) )
        {
            double fixSec = atof( message + 12 );
            if( fixSec > 0 )
            {
                Timestamp fix( fixSec );
                Timespan timeErr = fix - timestamp;
                timeBase += timeErr;
                timeAdjust += timeErr;
                timestamp = fix;
            }
        }
        newEntry->setTimestamp( timestamp );

        return newEntry;
    }

    return NULL;
}

unsigned int SyslogEntry::write( Timestamp& timeBase, OutStream& logStream ) const
{
    if( severity_ == Syslog::NONE )
    {
        return 0;
    }

    unsigned int bytesWritten( 0 );

    // Log the Type | Severity | Logger code as 2 bytes
    unsigned short loggerFacilityCode( loggerFacility_.getCode() );
    bytesWritten += logStream.writeUShortCompact(
                        SYSLOG_LOG_ENTRY
                        | severity_
                        | ( loggerFacilityCode << 5 )
                    ).bytesWritten();

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

    // Log the message
    const Str& message = getMessage();
    unsigned short messageLength = MIN( message.length(), UINT16_MAX );

    // Message length, then message
    bytesWritten += logStream.writeUShortCompact( messageLength ).bytesWritten();
    bytesWritten += logStream.write( message.data(), messageLength ).bytesWritten();

    return bytesWritten;
}

/// Return Str
Str SyslogEntry::toKmlString( const Unit* unit ) const
{
    return timestamp_.toSmallString() + " [" + loggerFacility_ + "]:" + message_;
}

Str SyslogEntry::toString( const Unit* unit ) const
{
    if( severity_ == Syslog::NONE )
        return "";

    /// A little ugliness here
    char buf[ 80 ];

    timestamp_.toString( buf, 79, 3 );

    Str retVal( buf );
    retVal += ",";
    retVal += Str( timestamp_.asDouble(), 3 );
    retVal += " [";
    retVal += loggerFacility_;
    retVal += "](";
    retVal += Syslog::SeverityToString( severity_ );
    retVal += "): ";
    retVal += message_;
    return retVal;
}

