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

#include "DataEntry.h"

#include "data/BlobValue.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "logger/DirectoryEntry.h"
#include "utils/RingBuffer.h"

RingBufferVoid* DataEntry::MallocRing_( NULL );

DataEntry::StaticDestructor DataEntry::StaticDestructor_;

Mutex DataEntry::Mutex_;

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

/// Constructor
DataEntry::DataEntry( DataAccessor* accessor, const bool best, const bool changed )
    : LogEntry( LogEntry::DATA_LOG_ENTRY, accessor->getElement().getTimestamp() ),
      dataAccess_( accessor->getDataAccess() ),
      best_( best ),
      changed_( changed )
{
    try
    {
        if( NULL != accessor->getElement().getDataValue() )
        {
            dataValue_ = accessor->getElement().getDataValue() ->copy();
        }
        else
        {
            dataValue_ = NULL;
        }
    }
    catch( DataElement::UninitializedException& deui )
    {
        dataValue_ = NULL;
    }
}

/// Copy Constructor
DataEntry::DataEntry( const DataEntry* dataEntry )
    : LogEntry( LogEntry::DATA_LOG_ENTRY, dataEntry->getTimestamp() ),
      dataAccess_( dataEntry->getDataAccess() ),
      dataValue_( dataEntry->getDataValue() ->copy() ),
      best_( dataEntry->isBest() ),
      changed_( true /*dataEntry->changed_*/ )
{}

/// DataAccess Constructor
DataEntry::DataEntry( DataAccess* access, DataValue* dataValue, const Timestamp& timestamp, const bool best, const bool changed )
    : LogEntry( LogEntry::DATA_LOG_ENTRY, timestamp ),
      dataAccess_( access ),
      dataValue_( dataValue ),
      best_( best ),
      changed_( changed )
{}

/// Destructor
DataEntry::~DataEntry()
{
    if( NULL != dataValue_ )
    {
        delete dataValue_;
    }
}

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

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

/// Read new DataEntry from a stream.
DataEntry* DataEntry::Read( Timestamp& timeBase, InStream &inStream, unsigned short firstWord, Logger& logger, bool& error )
{
    // Format:
    // DATA_LOG_ENTRY = 0x4000,  // (2b)(1b best)<--accessor(13b), [timespan,] data

    bool best = ( firstWord & BEST_MASK ) == IS_BEST;

    unsigned short accessCode = firstWord >> 3;
    DataAccess* dataAccess = Slate::GetDataAccess( accessCode );
    if( NULL == dataAccess )
    {
        logger.syslog( "No DataAccessor found for code 0x" + Str( accessCode, 16 ), Syslog::ERROR );
        logger.syslog( "inStream at 0x", inStream.getTotalBytesRead(), Syslog::ERROR );
        error = true;
        return NULL;
    }

    // Deal with DataWriters declared before InputReaders
    if( dataAccess->getUniversalCode() == ElementURI::NO_CODE
            && ( int )dataAccess->getBinaryType() == NO_TYPE
            && dataAccess->getTypeSize() == 0 )
    {
        for( int i = accessCode + 1; i < Slate::GetDataAccessCount(); ++i )
        {
            DataAccess* aDataAccess = Slate::GetDataAccess( i );
            if( NULL != aDataAccess
                    && aDataAccess->getElementCode() == dataAccess->getElementCode()
                    && aDataAccess->getBinaryType() >= 0
                    && aDataAccess->getTypeSize() > 0 )
            {
                dataAccess->copyUnitsTypeAndSizeFrom( aDataAccess );
                break;
            }
        }
    }

    if( dataAccess->getUniversalCode() == ElementURI::SELF_CODE
            && ( int )dataAccess->getBinaryType() == NO_TYPE
            && dataAccess->getTypeSize() == 0 )
    {
        for( int i = 0; i < Slate::GetDataAccessCount(); ++i )
        {
            DataAccess* aDataAccess = Slate::GetDataAccess( i );
            if( NULL != aDataAccess
                    && aDataAccess->getUniversalCode() == dataAccess->getElementCode()
                    && aDataAccess->getBinaryType() >= 0
                    && aDataAccess->getTypeSize() > 0 )
            {
                dataAccess->copyUnitsTypeAndSizeFrom( aDataAccess );
                break;
            }
        }
    }

    bool changed;
    Timestamp timestamp( timeBase );
    if( !ReadTimestamp( timestamp, changed, inStream, logger, dataAccess->getTimePrecision() ) )
    {
        return NULL;
    }

    DataElement* dataElement = Slate::GetElement( dataAccess->getElementCode() );
    if( NULL == dataElement )
    {
        ElementURI* uri = Slate::GetElementURI( dataAccess->getElementCode() );
        if( NULL == uri )
        {
            logger.syslog( "Could not find Element with code 0x" + Str( dataAccess->getElementCode(), 16 ), Syslog::ERROR );
        }
        else
        {
            dataElement = Slate::NewDataElement( *uri, NULL, *dataAccess->getUnit(), dataAccess->getBinaryType() );
        }
    }

    DataValue* dataValue( NULL );
    if( changed )
    {
        BinaryDataType binaryType = dataAccess->getBinaryType();
        const Unit& unit = *( dataAccess->getUnit() );
        dataValue = unit( binaryType, NULL == dataElement ? NOT_BLOB : dataElement->getUri().getBlobType() );
        unsigned short dataSize;
        if( binaryType == MULTIVALUE )
        {
            inStream.readShort( dataSize );
        }
        else
        {
            dataSize = dataAccess->getTypeSize();
        }

        char dataIn[dataSize];

        size_t dataRead = inStream.read( dataIn, dataSize ).bytesRead();

        if( dataRead < dataSize )
        {
            logger.syslog( "End of Binary File Reached while reading DataEntry DataValue", Syslog::ERROR );
            return NULL;
        }

        if( binaryType == MULTIVALUE )
        {
            ( ( StrValue* )dataValue )->fromBinary( dataIn, dataSize );
        }
        else
        {
            dataValue->fromBinary( dataIn );
        }

        if( NULL != dataElement )
        {
            dataElement->getDataValue()->setFrom( *dataValue );
        }
    }
    else
    {
        if( NULL != dataElement )
        {
            dataValue = dataElement->getDataValue()->copy();
        }
    }

    return new DataEntry( dataAccess, dataValue, timestamp, best, changed );

}

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

    if( NULL != dataValue_ && NULL != dataAccess_ )
    {
        // Log the Type | Best | Data Accessor code as 2 bytes
        unsigned short accessorCode( dataAccess_->getCode() );

        bytesWritten += logStream.writeUShortCompact(
                            DATA_LOG_ENTRY
                            | ( best_ ? IS_BEST : NOT_BEST )
                            | ( accessorCode << 3 )
                        ).bytesWritten();

        bytesWritten += writeTimestamp( timeBase, changed_, logStream, dataAccess_->getTimePrecision() );

        if( changed_ )
        {
            bytesWritten += dataValue_->toStream( logStream );
        }
    }
    return bytesWritten;
}

Str DataEntry::toString( const Unit* unit ) const
{
    int precision( 5 );
    switch( dataAccess_->getTimePrecision() )
    {
    case Logger::TIME_PRECISION_MICROS:
        precision = 5;
        break;
    case Logger::TIME_PRECISION_MILLIS:
        precision = 3;
        break;
    }

    /// 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 ] = ',';
    }

    CodedStr* actorName = Slate::GetName( dataAccess_->getOwnerCode() );
    ElementURI* dataElementUri = Slate::GetElementURI( dataAccess_->getElementCode() );

    return Str( buf )
           + Str( timestamp_.asDouble(), precision ) + " "
           + ( NULL == actorName ? UNKNOWN : *actorName )
           + ( dataAccess_->getAccessorType() == DataAccessor::DATA_WRITER ? ( isBest() ? "==>" : "-->" ) : "<--" )
           + ( NULL == dataElementUri ? UNKNOWN : *dataElementUri )
           + "="
           + getDataValueString( unit );
}


const Str DataEntry::getDataValueString( const Unit* unit, bool hideUnits ) const
{
    if( NULL == dataValue_ )
    {
        return "NULL";
    }
    const Unit& theUnit( NULL == unit ? dataValue_->getUnit() : *unit );
    if( hideUnits )
    {
        return dataValue_->toString( theUnit );
    }
    return dataValue_->toString( theUnit ) + " " + theUnit.getAbbreviation();
}

bool DataEntry::isDataWrite() const
{
    return dataAccess_->getAccessorType() == DataAccessor::DATA_WRITER;
}
