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

#include "MissionStateLogger.h"

#include "data/Slate.h"
#include "missionScript/MissionItem.h"
#include "missionScript/MissionManager.h"
#include "supervisor/Supervisor.h"
#include <stddef.h>

MappedIOStream* MissionStateLogger::CurrentStream_( NULL );

FlexArray<MissionStateLogger::StreamEntry*> MissionStateLogger::StreamEntries_( true );

int MissionStateLogger::StreamSize_( 0 );

FlexArray<MissionStateLogger::StateInfo*> MissionStateLogger::States_( true );

Str MissionStateLogger::FilterArg_( Str::EMPTY_STR );

MissionStateLogger::StaticDestructor MissionStateLogger::StaticDestructor_;

MissionStateLogger::StaticDestructor::~StaticDestructor()
{
    if( NULL != CurrentStream_ )
    {
        delete CurrentStream_;
        CurrentStream_ = NULL;
    }
}

void MissionStateLogger::SetActive( const CodedStr& aggregateName, MissionItem* active )
{
    unsigned short activeCode = ( NULL == active || NULL == active->getComponent() ) ? CodedStr::NO_CODE : active->getComponent()->getName().getCode();
    SetActive( aggregateName, activeCode );
}

void MissionStateLogger::SetActive( const CodedStr& aggregateName, unsigned short activeCode )
{
    unsigned short code = aggregateName.getCode();
    StateInfo* stateInfo = States_[ code ];
    if( NULL == stateInfo )
    {
        stateInfo = new StateInfo();
        stateInfo->nameCode_ = activeCode;
        States_[ code ] = stateInfo;
    }
    else
    {
        stateInfo->nameCode_ = activeCode;
    }

    if( NULL == CurrentStream_ )
    {
        return;
    }

    StateInfo* activeInfo = NULL;
    if( activeCode != CodedStr::NO_CODE )
    {
        activeInfo = States_[activeCode];
        if( NULL == activeInfo )
        {
            CodedStr* activeName = Slate::GetName( activeCode );
            if( NULL != activeName )
            {
                SetActive( *activeName, CodedStr::NO_CODE );
                activeInfo = States_[activeCode];
            }
            else
            {
                fprintf( stderr, "Can not find name corresponding to code 0x%04x\n", activeCode );
            }
        }
    }

    if( NULL == CurrentStream_ )
    {
        return;
    }

    if( -1 == ( int )stateInfo->pos_ )
    {
        stateInfo->pos_ = StreamSize_;
        CurrentStream_->seek( stateInfo->pos_ );
        CurrentStream_->writeChar( 'S' );
        CurrentStream_->writeInt( NULL == activeInfo ? ( unsigned int ) - 1 : activeInfo->pos_ );
        CurrentStream_->writeInt( stateInfo->repeat_ );
        CurrentStream_->writeShort( aggregateName.length() );
        CurrentStream_->write( aggregateName.cStr(), aggregateName.length() );
        StreamSize_ += 11 + CurrentStream_->bytesWritten();
    }
    else
    {
        CurrentStream_->seek( stateInfo->pos_  + 1 );
        CurrentStream_->writeInt( NULL == activeInfo ? ( unsigned int ) - 1 : activeInfo->pos_ );
    }
}

void MissionStateLogger::SetRepeatIndex( const CodedStr& aggregateName, int repeatIndex )
{
    unsigned short code = aggregateName.getCode();
    StateInfo* stateInfo = States_[ code ];
    if( NULL == stateInfo )
    {
        stateInfo = new StateInfo();
        stateInfo->repeat_ = repeatIndex;
        States_[ code ] = stateInfo;
    }
    else
    {
        stateInfo->repeat_ = repeatIndex;
    }

    if( NULL == CurrentStream_ )
    {
        return;
    }

    if( -1 == ( int )stateInfo->pos_ )
    {
        stateInfo->pos_ = StreamSize_;
        CurrentStream_->seek( stateInfo->pos_ );
        CurrentStream_->writeChar( 'S' );
        CurrentStream_->writeInt( ( unsigned int ) - 1 );
        CurrentStream_->writeInt( repeatIndex );
        CurrentStream_->writeShort( aggregateName.length() );
        CurrentStream_->write( aggregateName.cStr(), aggregateName.length() );
        StreamSize_ += 11 + CurrentStream_->bytesWritten();
    }
    else
    {
        CurrentStream_->seek( stateInfo->pos_  + 5 );
        CurrentStream_->writeInt( repeatIndex );
    }

}

Str* MissionStateLogger::GetActive( const CodedStr& aggregateName, int& repeatIndex )
{
    StateInfo* stateInfo = States_[ aggregateName.getCode() ];
    repeatIndex = NULL == stateInfo ? 0 : stateInfo->repeat_;
    return NULL == stateInfo ? NULL : Slate::GetName( stateInfo->nameCode_ );
}

void MissionStateLogger::ShowMissionState( Logger& logger )
{
    for( int i = States_.getMinIndex(); i <= States_.getMaxIndex(); ++i )
    {
        StateInfo* stateInfo = States_[ i ];
        if( NULL != stateInfo )
        {
            Str* name = stateInfo == NULL ? NULL : Slate::GetName( stateInfo->nameCode_ );
            int repeatIndex = NULL == stateInfo ? 0 : stateInfo->repeat_;
            logger.syslog( *Slate::GetName( i ) + "->" + ( NULL == name ? "null" : *name ) + "/" + repeatIndex, Syslog::INFO );
        }
    }
}

void MissionStateLogger::LogArgWrite( const ElementURI& uri, DataValue* dataValue )
{
    float value( nanf( "" ) );
    if( !dataValue->copyTo( dataValue->getBaseUnit(), value ) )
    {
        return;
    }
    StreamEntry* entry = StreamEntries_[uri.getCode()];
    if( NULL == entry )
    {
        entry = new StreamEntry();
        StreamEntries_[uri.getCode()] = entry;
        if( NULL != CurrentStream_ )
        {
            entry->pos_ = StreamSize_;
            CurrentStream_->seek( entry->pos_ );
            CurrentStream_->writeChar( 'A' );
            CurrentStream_->writeFloat( value );
            CurrentStream_->writeShort( uri.length() );
            CurrentStream_->write( uri.cStr(), uri.length() );
            StreamSize_ += 7 + CurrentStream_->bytesWritten();
        }
    }
    else if( NULL != CurrentStream_ )
    {
        CurrentStream_->seek( entry->pos_  + 1 );
        CurrentStream_->writeFloat( value );
    }
    entry->floatValue_ = value;
    if( NULL == CurrentStream_ )
    {
        return;
    }
}

void MissionStateLogger::ClearStates()
{
    if( NULL == CurrentStream_ )
    {
        return;
    }
    delete CurrentStream_;
    CurrentStream_ = NULL;
    StreamSize_ = 0;
    StreamEntries_.clear();
    States_.clear();
}

void MissionStateLogger::ConfigureFilterArg( Str loadedMissionFilename )
{
    loadedMissionFilename.replaceChar( '/', '_' );
    loadedMissionFilename.replaceChar( '.', '_' );
    if( loadedMissionFilename.startsWith( "__Missions_" ) )
    {
        loadedMissionFilename = loadedMissionFilename.substr( 11 );
    }
    FilterArg_ = loadedMissionFilename + ".state";
}

void MissionStateLogger::NewResume( Str loadedMissionFilename, Logger& logger )
{
    ClearStates();
    Str oldFilterArg( FilterArg_ );
    ConfigureFilterArg( loadedMissionFilename );
    Str latestLogFilename = Supervisor::GetLatestLogFilename( Filter );
    if( latestLogFilename == Str::NO_POS )
    {
        logger.syslog( "No resume info, can not resume.", Syslog::FAULT );
        return;
    }
    logger.syslog( "Resuming from info in " + latestLogFilename, Syslog::IMPORTANT );
    MappedIOStream inStream( latestLogFilename, false, 2048 );
    signed char firstChar( 0 );
    while( inStream.readChar( firstChar ).bytesRead() == 1 && firstChar != 0 )
    {
        switch( firstChar )
        {
        case 'A':
        {
            float floatValue;
            unsigned short length;
            char buffer[2048];
            Str name;
            inStream.readFloat( floatValue );
            inStream.readShort( length );
            inStream.read( buffer, length );
            name = Str( buffer, length );
            ElementURI* uri = Slate::FindElementURI( name );
            if( NULL != uri )
            {
                DataElement* el = Slate::GetElement( uri->getCode() );
                if( NULL != el )
                {
                    DataValue* dv = el->getDataValue();
                    if( NULL != dv )
                    {
                        dv->setFrom( dv->getBaseUnit(), floatValue );
                        LogArgWrite( *uri, dv );
                        continue;
                    }
                }
            }
        }
        break;
        case 'S':
        {
            int activePos;
            int repeat;
            unsigned short length;
            char buffer[2048];
            Str nameStr;
            CodedStr* name;
            inStream.readInt( activePos );
            inStream.readInt( repeat );
            inStream.readShort( length );
            inStream.read( buffer, length );
            nameStr = Str( buffer, length );
            name = Slate::FindCodedName( nameStr );
            if( NULL != name )
            {
                if( ( unsigned int ) activePos == ( unsigned int ) - 1 )
                {
                    SetActive( *name, CodedStr::NO_CODE );
                }
                else
                {
                    signed char aFirstChar;
                    unsigned int tell = inStream.tell();
                    inStream.seek( activePos );
                    inStream.readChar( aFirstChar );
                    inStream.readInt( activePos );
                    inStream.readInt( activePos );
                    inStream.readShort( length );
                    inStream.read( buffer, length );
                    inStream.seek( tell );
                    nameStr = Str( buffer, length );
                    CodedStr* activeName = Slate::FindCodedName( nameStr );
                    if( NULL != activeName )
                    {
                        SetActive( *name, activeName->getCode() );
                    }
                }
                SetRepeatIndex( *name, repeat );
            }
        }
        break;
        default:
            printf( "Error scanning %s, got firstChar code %d", latestLogFilename.cStr(), firstChar );
            return;
        }
    }
    NewRun( loadedMissionFilename );
}

void MissionStateLogger::NewRun( Str loadedMissionFilename )
{
    ClearStates();
    ConfigureFilterArg( loadedMissionFilename );
    CurrentStream_ = new MappedIOStream( "Logs/latest/" + FilterArg_, true, 2048 );
    for( int i = StreamEntries_.getMinIndex(); i <= StreamEntries_.getMaxIndex(); ++i )
    {
        StreamEntry* entry = StreamEntries_[i];
        if( NULL == entry )
        {
            continue;
        }
        if( entry->pos_ == ( unsigned int ) - 1 )
        {
            ElementURI* uri = Slate::GetElementURI( i );
            if( NULL != uri )
            {
                entry->pos_ = StreamSize_;
                CurrentStream_->seek( entry->pos_ );
                CurrentStream_->writeChar( 'A' );
                CurrentStream_->writeFloat( entry->floatValue_ );
                CurrentStream_->writeShort( uri->length() );
                CurrentStream_->write( uri->cStr(), uri->length() );
                StreamSize_ += 7 + CurrentStream_->bytesWritten();
            }
        }
    }
    for( int i = States_.getMinIndex(); i <= States_.getMaxIndex(); ++i )
    {
        StateInfo* stateInfo = States_[i];
        if( NULL != stateInfo )
        {
            CodedStr* name = Slate::GetName( i );
            if( NULL != name )
            {
                SetActive( *name, stateInfo->nameCode_ );
                SetRepeatIndex( *name, stateInfo->repeat_ );
            }
        }
    }
}

/// Helper function for Supervisor::GetLatestLogFilename()
int MissionStateLogger::Filter( DIRENT_CONST struct dirent* dirEntry )
{
    int retVal = dirEntry != NULL
                 && dirEntry->d_type == DT_REG
                 && strncmp( dirEntry->d_name, FilterArg_.cStr(), FilterArg_.length() ) == 0
                 && strlen( dirEntry->d_name ) == FilterArg_.length();
    return retVal;
}

