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

#include "LinearApproximationLogWriter.h"

#include <math.h>

#include "data/ElementURI.h"
#include "data/DataAccess.h"
#include "data/DataValue.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "logger/DirectoryEntry.h"
#include "logger/DataEntry.h"
#include "logger/FileLogWriter.h"
#include "logger/EventEntry.h"
#include "logger/SyslogEntry.h"
#include "utils/Mutex.h"
#include "units/Units.h"

LinearApproximationLogWriter::LinearApproximationLogWriter( LogWriter* outputTo, int varCount, char** varList, char** unitList, float* approxErrors, bool sendAtStartCycle, bool makeDirEntries )
    : TableLogWriter( outputTo, varCount, varList, unitList, false ),
      approxErrors_( approxErrors ),
      sendAtStartCycle_( sendAtStartCycle ),
      makeDirEntries_( makeDirEntries ),
      varData_( new LinearApproximationLogWriter::VarData * [varCount_] ),
      nameWrites_( false ),
      forceWrites_( false ),
      dataAccesses_( new DataAccess * [ varCount ] ),
      dataAccessHidden_( new bool[ varCount ] ),
      recentOutput_( false ),
      cycleStartEntry_( NULL, NULL, EventEntry::START_CYCLE )
{
    preInitialize();
}

void LinearApproximationLogWriter::preInitialize()
{
    for( int varIndex = 0; varIndex < varCount_; ++varIndex )
    {
        varData_[ varIndex ] = NULL;
        dataAccesses_[ varIndex ] = NULL;
        dataAccessHidden_[ varIndex ] = false;
    }
}

LinearApproximationLogWriter::~LinearApproximationLogWriter()
{
    if( !wroteFooter_ )
    {
        writeFooter();
    }
    for( int i = 0; i < varCount_; ++i )
    {
        if( NULL != varData_[ i ] )
        {
            delete varData_[ i ];
        }
        if( NULL != dataAccesses_[ i ] )
        {
            delete dataAccesses_[ i ];
        }
    }
    delete[] varData_;
    delete[] dataAccesses_;
    delete[] dataAccessHidden_;
    if( deleteLists_ )
    {
        delete[] approxErrors_;
    }
}

unsigned int LinearApproximationLogWriter::writeHeader()
{
    return outputTo_->writeHeader();
}

unsigned int LinearApproximationLogWriter::writeToColumn( const DataEntry *dataEntry, const int column )
{
    unsigned int bytesWritten( 0 );
    VarData*& varDatum = varData_[column];
    if( NULL != varDatum && NULL != dataEntry )
    {
        if( varDatum->checkNextEntry( dataEntry ) )
        {
            varDatum->advance();
            DataEntry* dataEntry = varDatum->getDataEntry();
            bytesWritten += sendOutput( dataEntry, column );
            if( varDatum->wasNaN() )
            {
                varDatum->advance();
                bytesWritten += sendOutput( varDatum->getDataEntry(), column );
            }
            recentOutput_ = true;
        }
    }
    else if( NULL == dataEntry && NULL != varDatum )
    {
        varDatum->advance();
        bytesWritten += sendOutput( varDatum->getDataEntry(), column );
        recentOutput_ = true;
    }
    else if( NULL == varDatum && NULL != dataEntry )
    {
        varDatum = new VarData( dataEntry, approxErrors_[column] );
        bytesWritten += sendOutput( varDatum->getDataEntry(), column );
        recentOutput_ = true;
    }
    return bytesWritten;
}

unsigned int LinearApproximationLogWriter::writeRow()
{
    unsigned int bytesWritten = 0;
    if( recentOutput_ && sendAtStartCycle_ )
    {
        for( int i = 0; i < varCount_; ++ i )
        {
            bytesWritten += writeToColumn( NULL, i );
        }
        cycleStartEntry_.setTimestamp( lastTime_ );
        bytesWritten += outputTo_->write( &cycleStartEntry_ );
        recentOutput_ = false;
    }
    return bytesWritten;
}

unsigned int LinearApproximationLogWriter::writeSyslog( const SyslogEntry* syslogEntry )
{
    unsigned int bytesWritten = 0;

    if( makeDirEntries_ )
    {
        const CodedStr& codedName = syslogEntry->getLoggerFacility();
        if( NULL == nameWrites_[codedName.getCode()] )
        {
            nameWrites_[codedName.getCode()] = ( int* )true;
            DirectoryEntry* nameEntry = new DirectoryEntry( ( CodedStr* )&codedName );
            bytesWritten += outputTo_->write( nameEntry );
            delete nameEntry;
        }
    }
    bytesWritten += outputTo_->write( syslogEntry );

    return bytesWritten;
}

unsigned int LinearApproximationLogWriter::forceWrite( DataEntry* dataEntry )
{
    unsigned int bytesWritten = 0;

    if( makeDirEntries_ )
    {
        unsigned short eCode = dataEntry->getDataAccess()->getElementCode();
        ElementURI* element = Slate::GetElementURI( eCode );
        unsigned short oCode = dataEntry->getDataAccess()->getOwnerCode();
        CodedStr* codedName = Slate::GetName( oCode );
        if( NULL != codedName )
        {
            if( NULL == nameWrites_[oCode] )
            {
                nameWrites_[oCode] = ( int* )true;
                DirectoryEntry* nameEntry = new DirectoryEntry( codedName );
                bytesWritten += outputTo_->write( nameEntry );
                delete nameEntry;
            }
        }
        if( NULL == forceWrites_[eCode] )
        {
            forceWrites_[eCode] = ( int* )true;
            DirectoryEntry* dirEntry = new DirectoryEntry( element );
            bytesWritten += outputTo_->write( dirEntry );
            delete dirEntry;
            dirEntry = new DirectoryEntry( dataEntry->getDataAccess() );
            bytesWritten += outputTo_->write( dirEntry );
            delete dirEntry;
        }
    }

    bytesWritten += outputTo_->write( dataEntry );
    return bytesWritten;
}

unsigned int LinearApproximationLogWriter::sendOutput( DataEntry* dataEntry, int varIndex )
{
    unsigned int bytesWritten = 0;

    if( makeDirEntries_ )
    {
        if( ( NULL == dataAccesses_[ varIndex ] && uriCodes_[ varIndex ] != ElementURI::NO_CODE ) || dataAccessHidden_[ varIndex ] )
        {
            DirectoryEntry* dirEntry;;
            ElementURI* element = Slate::GetElementURI( uriCodes_[ varIndex ] );
            unsigned short ownerCode( CodedStr::NO_CODE );
            size_t nameLen = strcspn( varList_[varIndex], "." );
            if( nameLen > 0 && nameLen < strlen( varList_[varIndex] ) )
            {
                Str name( varList_[varIndex], nameLen );
                CodedStr* codedName = Slate::FindCodedName( name );
                if( NULL != codedName )
                {
                    ownerCode = codedName->getCode();
                    if( NULL == nameWrites_[codedName->getCode()] )
                    {
                        nameWrites_[codedName->getCode()] = ( int* )true;
                        DirectoryEntry* nameEntry = new DirectoryEntry( codedName );
                        bytesWritten += outputTo_->write( nameEntry );
                        delete nameEntry;
                    }
                }
            }
            dirEntry = new DirectoryEntry( element );
            bytesWritten += outputTo_->write( dirEntry );
            delete dirEntry;
            if( NULL == dataAccesses_[ varIndex ] )
            {
                dataAccesses_[ varIndex ] = new DataAccess(
                    ownerCode,
                    uriCodes_[ varIndex ],
                    dataEntry->getDataAccess()->getUniversalCode() == uriCodes_[ varIndex ] ? uriCodes_[ varIndex ] : ElementURI::NO_CODE,
                    dataEntry->getDataAccess()->getUnit()->getName(),
                    dataEntry->getDataAccess()->getBinaryType(),
                    dataEntry->getDataAccess()->getTypeSize(),
                    dataEntry->getDataAccess()->getAccessorType(),
                    varIndex,
                    false,
                    Logger::TIME_PRECISION_MILLIS
                );
                if( isnan( approxErrors_[ varIndex ] ) || approxErrors_[ varIndex ] == 0.0f )
                {
                    dataAccesses_[ varIndex ]->setNoInterpFlag();
                }
            }
            else
            {
                dataAccessHidden_[ varIndex ] = false;
            }
            dirEntry = new DirectoryEntry( dataAccesses_[ varIndex ] );
            bytesWritten += outputTo_->write( dirEntry );
            delete dirEntry;
        }
        if( NULL != dataAccesses_[ varIndex ] )
        {
            dataEntry->setDataAccess( dataAccesses_[ varIndex ] );
        }
    }
    bytesWritten += outputTo_->write( dataEntry );
    return bytesWritten;
}

unsigned int LinearApproximationLogWriter::writeFooter()
{
    return forceWriteRow();
}

unsigned int LinearApproximationLogWriter::forceWriteRow()
{
    unsigned int bytesSent( 0 );
    for( int i = 0; i < varCount_; ++i )
    {
        if( NULL != varData_[i] )
        {
            varData_[i]->advance();
            bytesSent += sendOutput( varData_[i]->getDataEntry(), i );
            recentOutput_ = true;
        }
    }

    writeRow();

    return bytesSent;
}

void LinearApproximationLogWriter::resetDirectoryEntries()
{
    for( int i = nameWrites_.getMinIndex(); i <= nameWrites_.getMaxIndex(); ++i )
    {
        nameWrites_[i] = NULL;
    }
    for( int i = forceWrites_.getMinIndex(); i <= forceWrites_.getMaxIndex(); ++i )
    {
        forceWrites_[i] = NULL;
    }
    for( int varIndex = 0; varIndex < varCount_; ++varIndex )
    {
        if( NULL != dataAccesses_[ varIndex ] )
        {
            dataAccessHidden_[ varIndex ] = true;
        }
    }

}

LinearApproximationLogWriter::VarData::VarData( const DataEntry* dataEntry, float approxError )
    :         dataEntry_( new DataEntry( dataEntry ) ),
              dataValue_( dataEntry_->getDataValue() ),
              unit_( dataValue_->getBaseUnit() ),
              approxError_( approxError ),
              minUpperSlope_( nanf( "" ) ),
              maxLowerSlope_( nanf( "" ) ),
              startValue_( nan( "" ) ),
              endValue_( nan( "" ) ),
              nextValue_( nan( "" ) ),
              startTime_( dataEntry->getTimestamp() ),
              endTime_( startTime_ ),
              nextTime_( startTime_ ),
              ready_( true ),
              wasNaN_( false )
{
    dataValue_->copyTo( unit_, startValue_ );
    endValue_ = nextValue_ = startValue_;
}

LinearApproximationLogWriter::VarData::~VarData()
{
    if( NULL != dataEntry_ )
    {
        delete dataEntry_;
    }
}

float LinearApproximationLogWriter::VarData::getDeltaT( const Timestamp timestamp )
{
    return ( timestamp - startTime_ ).asDouble();
}

void LinearApproximationLogWriter::VarData::advance()
{
    dataValue_->setFrom( unit_, endValue_ );
    dataEntry_->setTimestamp( endTime_ );
    startValue_ = endValue_;
    startTime_ = endTime_;
    endValue_  = nextValue_;
    endTime_ = nextTime_;
    minUpperSlope_ = nanf( "" );
    maxLowerSlope_ = nanf( "" );
}

// Returns bounds for next output, 0 = no output required
bool LinearApproximationLogWriter::VarData::checkNextEntry( const DataEntry* nextEntry )
{
    nextEntry->getDataValue()->copyTo( nextEntry->getDataValue()->getBaseUnit(), nextValue_ );
    nextTime_ = nextEntry->getTimestamp();
    float deltaT( getDeltaT( nextTime_ ) );
    float upperSlope( nanf( "" ) );
    float lowerSlope( nanf( "" ) );
    ready_ = false;
    // Is this a Nan value?
    wasNaN_ = isnan( nextValue_ );
    if( wasNaN_ )
    {
        ready_ = true;
    }
    // Is this the 1st point after a Nan value?
    else if( isnan( startValue_ ) )
    {
        startValue_ = endValue_ = nextValue_;
        startTime_ = endTime_ = nextTime_;
        ready_ = true;
    }
    if( !ready_ && deltaT != 0 )
    {
        upperSlope = ( nextValue_ - startValue_ + approxError_ ) / deltaT ;
        lowerSlope = ( nextValue_ - startValue_ - approxError_ ) / deltaT ;
    }
    // Is this the 2nd point?
    if( !ready_ &&  isnan( maxLowerSlope_ ) )
    {
        maxLowerSlope_ = lowerSlope;
        minUpperSlope_ = upperSlope;
        endValue_ = nextValue_;
        endTime_ = nextTime_;
        ready_ = false;
    }
    else if( !ready_ )
    {
        float midSlope = 0.5 * ( upperSlope + lowerSlope );
        // point low, return BOUND_LOWER // call again to update endTime
        if( midSlope < maxLowerSlope_ || midSlope > minUpperSlope_ ||
                0.0f == approxError_ )
        {
            ready_ = true;
        }
        else
        {
            // update the minUpperSlope if necessary
            if( upperSlope < minUpperSlope_ )
            {
                minUpperSlope_ = upperSlope;
            }
            // update the maxLowerSlope if necessary
            if( lowerSlope > maxLowerSlope_ )
            {
                maxLowerSlope_ = lowerSlope;
            }
            endValue_ = nextValue_;
            endTime_ = nextTime_;
            ready_ = false;
        }
    }
    dataEntry_->setDataAccess( nextEntry->getDataAccess() );
    return ready_;
}
