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

#include "DirectoryEntry.h"

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

RingBufferVoid* DirectoryEntry::MallocRing_( NULL );

DirectoryEntry::StaticDestructor DirectoryEntry::StaticDestructor_;

FlexArray<ElementURI*> DirectoryEntry::ElementStore_( true );

unsigned short DirectoryEntry::MinimumLogVersion_( 0x0003 );
unsigned short DirectoryEntry::CurrentLogVersion_( 0x0008 );

Mutex DirectoryEntry::Mutex_;

DirectoryEntry::StaticDestructor::~StaticDestructor()
{
    delete MallocRing_;
    MallocRing_ = NULL;
    ElementStore_.clear();
}

/// Ver Construnctor
DirectoryEntry::DirectoryEntry( const short version )
    : LogEntry( DIRECTORY_LOG_ENTRY ),
      directoryType_( VERSION ),
      version_( version == 0 ? CurrentLogVersion_ : version )
{}

/// Accessor Constructor
DirectoryEntry::DirectoryEntry( DataAccess* dataAccess )
    : LogEntry( DIRECTORY_LOG_ENTRY ),
      directoryType_( ACCESSOR ),
      dataAccess_( dataAccess )
{}

/// ElementURI Constructor
DirectoryEntry::DirectoryEntry( ElementURI* elementURI )
    : LogEntry( DIRECTORY_LOG_ENTRY ),
      directoryType_( ELEMENT ),
      elementURI_( elementURI )
{}

/// Entity Name Constructor
DirectoryEntry::DirectoryEntry( const CodedStr* name )
    : LogEntry( DIRECTORY_LOG_ENTRY ),
      directoryType_( NAME ),
      name_( name )
{}

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

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

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

/// Read new DirectoryEntry from a stream.
DirectoryEntry* DirectoryEntry::Read( Timestamp& timeBase, InStream &inStream, unsigned short firstWord, Logger& logger, bool& error )
{
    if( firstWord != ( DIRECTORY_LOG_ENTRY | ( '*' >> 1 ) ) )
    {
        logger.syslog( "Invalid directory entry starting with code 0x" + Str( firstWord, 16 ) + " at byte 0x" + Str( inStream.getTotalBytesRead(), 16 ), Syslog::ERROR );
        error = true;
    }
    signed char directoryType;
    if( inStream.readChar( directoryType ).bytesRead() != 1 )
    {
        logger.syslog( "Failed to read directory type.", Syslog::ERROR );
        return NULL;
    }
    switch( directoryType )
    {
    case VERSION:
    {
        signed char space, newline;
        short version;
        if( inStream.readChar( space ).bytesRead() != 1 || space != ' ' )
        {
            logger.syslog( "Missing space reading directory version", Syslog::ERROR );
            return NULL;
        }
        if( inStream.readShortHex( version ).bytesRead() == 4 )
        {
            if( inStream.readChar( newline ).bytesRead() != 1 || newline != '\n' )
            {
                logger.syslog( "Missing newline reading directory version", Syslog::ERROR );
                return NULL;
            }
            logger.syslog( "Got version " + Str( version ) + Str( " from " ) + Str( inStream.getName() ) );
            if( version < MinimumLogVersion_ || version > CurrentLogVersion_ )
            {
                logger.syslog( "Can only unserialize verisions " + Str( MinimumLogVersion_, 10 ) +
                               " through " + Str( CurrentLogVersion_, 10 ), Syslog::CRITICAL );
            }
            else
            {
                CurrentLogVersion_ = version;
            }
            return new DirectoryEntry( version );
        }
        logger.syslog( "Encountered eof while reading directory version", Syslog::ERROR );
        return NULL;
    }
    case ACCESSOR:
    {
        DataAccess* dataAccess = DataAccess::Read( inStream );
        if( NULL != dataAccess )
        {
            DataAccess* registeredDataAccess = Slate::RegisterDataAccess( dataAccess );
            if( NULL != registeredDataAccess )
            {
                //printf("registered access %s\n", registeredDataAccess->asString(true).cStr() );
                return new DirectoryEntry( registeredDataAccess );
            }
        }
        else
        {
            logger.syslog( "Encountered error while reading dataAccess", Syslog::ERROR );
        }
        return NULL;
    }
    case NAME:
    {
        CodedStr* name = CodedStr::Read( inStream );
        if( NULL != name )
        {
            CodedStr* codedName = Slate::RegisterCodedName( name );
            if( NULL != codedName )
            {
                //printf("registered name %s\n", codedName->cStr() );
                return new DirectoryEntry( codedName );
            }
        }
        else
        {
            logger.syslog( "Encountered error while reading name", Syslog::ERROR );
        }
        return NULL;
    }
    case ELEMENT:
    {
        ElementURI* element = ElementURI::Read( inStream );
        if( NULL != element )
        {
            ElementURI* registeredElement = Slate::RegisterElementURI( element );
            if( NULL != registeredElement )
            {
                //printf("registered element %s\n", registeredElement->cStr() );
                if( registeredElement == element )
                {
                    ElementStore_.push( element );
                }
                return new DirectoryEntry( registeredElement );
            }
        }
        else
        {
            logger.syslog( "Encountered error while reading element", Syslog::ERROR );
        }
        return NULL;
    }
    }
    return NULL;
}

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

    // Start a short that compacts to the char "<"
    bytesWritten += logStream.writeUShortCompact(
                        DIRECTORY_LOG_ENTRY | ( '*' >> 1 )
                    ).bytesWritten();

    // Log the directoryType_
    bytesWritten += logStream.writeChar( directoryType_ ).bytesWritten();

    switch( directoryType_ )
    {
    case ACCESSOR:
        bytesWritten += dataAccess_->write( logStream );
        break;
    case ELEMENT:
        bytesWritten += elementURI_->write( logStream );
        break;
    case NAME:
        bytesWritten += name_->write( logStream );
        break;
    case VERSION:
        bytesWritten += logStream.writeChar( ' ' ).bytesWritten();
        bytesWritten += logStream.writeShortHex( version_ ).bytesWritten();
        bytesWritten += logStream.writeChar( '\n' ).bytesWritten();
        break;
    }
    return bytesWritten;
}

Str DirectoryEntry::toString( const Unit* unit ) const
{
    switch( directoryType_ )
    {
    case ACCESSOR:
        return DirectoryTypeToString( directoryType_ ) + Str( dataAccess_->asString( true ) );
    case ELEMENT:
        return DirectoryTypeToString( directoryType_ ) + Str( elementURI_->getCode(), 16 ) + " " + *elementURI_;
    case NAME:
        return DirectoryTypeToString( directoryType_ ) + Str( name_->getCode(), 16 ) + " " + *name_;
    case VERSION:
        return DirectoryTypeToString( directoryType_ ) + Str( version_ );
    }
    return "(undefined)";
}

/// Convert an Action to a char*
const char *DirectoryEntry::DirectoryTypeToString( DirectoryType directoryType )
{
    switch( directoryType )
    {
    case ACCESSOR:
        return "ACCESSOR ";
    case ELEMENT:
        return "ELEMENT ";
    case NAME:
        return "NAME ";
    case VERSION:
        return "VERSION ";
    }
    return "(undefined)";
}

