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

#include "LinearApproxLogWriter.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/DataEntry.h"
#include "logger/FileLogWriter.h"
#include "logger/EventEntry.h"
#include "logger/SyslogEntry.h"
#include "utils/Mutex.h"
#include "units/Units.h"

LinearApproxLogWriter::LinearApproxLogWriter( LogWriter& outputTo, const Str& varName,
        DataValue* approxError, bool makeDirEntries, Logger& logger )
    : DecimationLogWriter( outputTo, DecimationLogWriter::LINEAR_APPROXIMATION ),
      varName_( varName ),
      approxErrorValue_( approxError ),
      varData_( NULL ),
      cycleStartEntry_( NULL, NULL, EventEntry::START_CYCLE ),
      lastTime_( Timestamp::NOT_SET_TIME )
{
    if( NULL == approxErrorValue_ )
    {
        logger.syslog( "Initializing LinearApproxWriter with NULL parameter value", Syslog::FAULT );
    }
}

LinearApproxLogWriter::~LinearApproxLogWriter()
{
    if( !wroteFooter_ )
    {
        writeFooter();
    }
    if( NULL != varData_ )
    {
        delete varData_;
    }
    if( NULL != approxErrorValue_ )
    {
        delete approxErrorValue_;
    }
}
unsigned int LinearApproxLogWriter::writeHeader()
{
    return outputTo_.writeHeader();
}

unsigned int LinearApproxLogWriter::write( const LogEntry *logEntry, const Unit* unit )
{
    unsigned int bytesWritten( 0 );
    if( NULL == approxErrorValue_ || logEntry->getType() != LogEntry::DATA_LOG_ENTRY )
    {
        return bytesWritten;
    }
    const DataEntry* dataEntry = static_cast<const DataEntry*>( logEntry );
    if( NULL != varData_ )
    {
        if( varData_->checkNextEntry( dataEntry ) )
        {
            varData_->advance();
            DataEntry* dataEntry = varData_->getDataEntry();
            bytesWritten += sendOutput( dataEntry );
            if( varData_->wasNaN() )
            {
                varData_->advance();
                bytesWritten += sendOutput( varData_->getDataEntry() );
            }
        }
    }
    else if( NULL != varData_ )
    {
        varData_->advance();
        bytesWritten += sendOutput( varData_->getDataEntry() );
    }
    else if( NULL == varData_ )
    {
        float approxError;
        approxErrorValue_->copyTo( approxErrorValue_->getBaseUnit(), approxError );
        varData_ = new VarData( dataEntry, approxError );
        bytesWritten += sendOutput( varData_->getDataEntry() );
    }
    return bytesWritten;
}

unsigned int LinearApproxLogWriter::sendOutput( DataEntry* dataEntry )
{
    unsigned int bytesWritten = 0;

    bytesWritten += outputTo_.write( dataEntry );
    return bytesWritten;
}

unsigned int LinearApproxLogWriter::writeFooter()
{
    return flush();
}

void LinearApproxLogWriter::setParameter( DataValue* approxError, Logger& logger )
{
    if( NULL == approxError )
    {
        logger.syslog( "Configuring LinearApproxWriter with NULL parameter value", Syslog::FAULT );
    }
    if( NULL == approxErrorValue_ )
    {
        if( NULL != approxError )
        {
            approxErrorValue_ = approxError;
        }
    }
    else
    {
        if( NULL != approxError )
        {
            approxErrorValue_->setFrom( *approxError );
            delete approxError;
        }
        else
        {
            approxErrorValue_->setFrom( approxErrorValue_->getBaseUnit(), nanf( "" ) );
        }
    }
}

unsigned int LinearApproxLogWriter::flush()
{
    unsigned int bytesWritten( 0 );
    while( NULL != varData_ && !varData_->flushed() )
    {
        varData_->advance();
        DataEntry* dataEntry = varData_->getDataEntry();
        bytesWritten += sendOutput( dataEntry );
    }
    return bytesWritten;
}


LinearApproxLogWriter::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_;
}

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

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

void LinearApproxLogWriter::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 LinearApproxLogWriter::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;
    // Was the previous value NaN?
    bool prevNaN = isnan( startValue_ );
    // Is this a Nan value?
    wasNaN_ = isnan( nextValue_ );
    if( wasNaN_ )
    {
        ready_ = !prevNaN;
    }
    // Is this the 1st point after a Nan value?
    else if( prevNaN )
    {
        startValue_ = endValue_ = nextValue_;
        startTime_ = endTime_ = nextTime_;
        ready_ = true;
    }
    if( !wasNaN_ && !ready_ && deltaT != 0 )
    {
        upperSlope = ( nextValue_ - startValue_ + approxError_ ) / deltaT ;
        lowerSlope = ( nextValue_ - startValue_ - approxError_ ) / deltaT ;
    }
    // Is this the 2nd point?
    if( !wasNaN_ && !ready_ &&  isnan( maxLowerSlope_ ) )
    {
        maxLowerSlope_ = lowerSlope;
        minUpperSlope_ = upperSlope;
        endValue_ = nextValue_;
        endTime_ = nextTime_;
        ready_ = false;
    }
    else if( !wasNaN_ && !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_;
}
