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

#include "TableLogWriter.h"
#include "data/DataValue.h"
#include "data/ElementURI.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalURI.h"
#include "logger/DataEntry.h"
#include "logger/DirectoryEntry.h"
#include "logger/EventEntry.h"
#include "logger/SyslogEntry.h"
#include "units/UnitRegistry.h"
#include "units/Units.h"
#include "utils/AuvMath.h"

TableLogWriter::True TableLogWriter::True_;

/// Constructor
TableLogWriter::TableLogWriter( int varCount, char** varList, char** unitList, bool interpolate )
    : LogWriter(),
      outputTo_( NULL ),
      varCount_( varCount ),
      varList_( varList ),
      unitList_( unitList ),
      units_( new const Unit * [varCount] ),
      uriCodes_( new unsigned short[ varCount ] ),
      isUniversal_( new bool[ varCount ] ),
      codeLookup_( new int[ElementURI::NUM_CODES] ),
      interpolate_( interpolate ),
      interpBuffer_( false, 2048, 2147483647 ),
      interpSyslogs_( true, 2048, 2147483647 ),
      interpTimes_( true, 2048, 2147483647 ),
      readerColumn_( false ),
      lastTime_( Timestamp::NOT_SET_TIME ),
      deleteLists_( false )
{
    resetInputs();
}

TableLogWriter::TableLogWriter( LogWriter* outputTo, int varCount, char** varList, char** unitList, bool interpolate )
    : LogWriter(),
      outputTo_( outputTo ),
      varCount_( varCount ),
      varList_( varList ),
      unitList_( unitList ),
      units_( new const Unit * [varCount] ),
      uriCodes_( new unsigned short[ varCount ] ),
      isUniversal_( new bool[ varCount ] ),
      codeLookup_( new int[ElementURI::NUM_CODES] ),
      interpolate_( interpolate ),
      interpBuffer_( false ),
      interpSyslogs_( true ),
      interpTimes_( true ),
      readerColumn_( false ),
      lastTime_( Timestamp::NOT_SET_TIME ),
      deleteLists_( false )
{
    if( NULL != outputTo_ )
    {
        outputTo_->addParent();
    }
    resetInputs();
}

/// Reset cached input settings
void TableLogWriter::resetInputs()
{
    writeAllInterpolated();

    if( NULL != outputTo_ )
    {
        outputTo_->resetInputs();
    }

    initializedTableLogWriter_ = false;
    slateElementURICount_ = 0;
    for( int i = 0; i < varCount_; ++i )
    {
        uriCodes_[i] = ElementURI::NO_CODE;
        isUniversal_[i] = false;
        units_[i] = NULL;
    }
    for( int i = 0; i < ElementURI::NUM_CODES; ++i )
    {
        codeLookup_[i] = -1;
    }

}

void TableLogWriter::initializeTableLogWriter()
{
    initializedTableLogWriter_ = true;
    for( int i = 0; i < varCount_; ++i )
    {
        if( uriCodes_[i] == ElementURI::NO_CODE )
        {
            unsigned short uriCode = Slate::GetElementURICode( varList_[ i ] );
            if( uriCode != ElementURI::NO_CODE )
            {
                uriCodes_[ i ] = uriCode;
                isUniversal_[i] = Slate::GetElementURI( uriCodes_[i] )->isUniversal();
                codeLookup_[uriCode] = i;
            }
            else if( Slate::IsDirectoryRead() )
            {
                isUniversal_[i] = false;
            }
            else
            {
                initializedTableLogWriter_ = false;
            }
        }
        if( NULL == units_[i] && NULL != unitList_[i] )
        {
            units_[i] = UnitRegistry::FindUnit( unitList_[i] );
        }
    }
    slateElementURICount_ = Slate::GetElementURICount();
}

/// Destructor
TableLogWriter::~TableLogWriter()
{
    if( NULL != outputTo_ )
    {
        outputTo_->writeFooter();
        outputTo_->removeParent();
        if( outputTo_->getParentCount() <= 0 )
        {
            delete outputTo_;
        }
    }
    if( deleteLists_ )
    {
        for( int i = 0; i < varCount_; ++i )
        {
            if( NULL != varList_[i] )
            {
                delete[] varList_[i];
            }
            if( NULL != unitList_[i] )
            {
                delete[] unitList_[i];
            }
        }
        delete[] varList_;
        delete[] unitList_;
    }
    delete[] units_;
    delete[] uriCodes_;
    delete[] isUniversal_;
    delete[] codeLookup_;
    for( unsigned int i = 0; i < interpBuffer_.size(); ++i )
    {
        const DataEntry** dataEntryRow = interpBuffer_[i];
        deleteDataEntryRow( dataEntryRow );
    }
}

const DataEntry** TableLogWriter::newDataEntryRow()
{
    const DataEntry** dataEntryRow = new const DataEntry*[varCount_];
    for( int j = 0; j < varCount_; ++j )
    {
        dataEntryRow[j] = NULL;
    }
    return dataEntryRow;
}

void TableLogWriter::deleteDataEntryRow( const DataEntry** dataEntryRow )
{
    if( NULL != dataEntryRow )
    {
        for( int j = 0; j < varCount_; ++j )
        {
            const DataEntry* dataEntry = dataEntryRow[j];
            if( NULL != dataEntry )
            {
                delete dataEntry;
            }
        }
        delete[] dataEntryRow;
    }
}

/// Write the given Entry to the OutStream.
///
/// Pipes the toString() method of the entry to the OutStream with
/// some formatting.
unsigned int TableLogWriter::write( const LogEntry *entry, const Unit* unit )
{
    MutexLocker lock( writeMutex_ );

    if( !initializedTableLogWriter_ && slateElementURICount_ != Slate::GetElementURICount() )
    {
        initializeTableLogWriter();
    }
    unsigned int bytesWritten = 0;
    if( entry->getType() == LogEntry::DATA_LOG_ENTRY )
    {
        const DataEntry * dataEntry = static_cast<const DataEntry*>( entry );
        //if ( dataEntry->isDataWrite() )
        //{
        //for ( int i = 0; i < varCount_; ++i )
        //{
        //if ( uriCodes_[ i ] ==
        //        ( isUniversal_[ i ] && dataEntry->isBest() ?
        //          dataEntry->getDataAccess()->getUniversalCode() :
        //          dataEntry->getDataAccess()->getElementCode() ) )
        unsigned short code = dataEntry->getDataAccess()->getElementCode();
        if( code != ElementURI::NO_CODE )
        {
            int column = codeLookup_[code];
            if( column >= 0 )
            {
                if( !interpolate_ )
                {
                    bytesWritten += writeToColumn( dataEntry, column );
                }
                else
                {
                    bytesWritten += writeInterpolated( dataEntry, column );
                }
            }
        }
        if( dataEntry->isBest() )
        {
            code = dataEntry->getDataAccess()->getUniversalCode();
            if( code != ElementURI::NO_CODE && code != ElementURI::SELF_CODE )
            {
                int column = codeLookup_[code];
                if( column >= 0 )
                {
                    if( !interpolate_ )
                    {
                        bytesWritten += writeToColumn( dataEntry, column );
                    }
                    else
                    {
                        bytesWritten += writeInterpolated( dataEntry, column );
                    }
                }
            }
        }
        //}
        //}
    }
    else if( !interpolate_ && entry->getType() == LogEntry::EVENT_LOG_ENTRY )
    {
        const EventEntry* eventEntry = static_cast<const EventEntry*>( entry );
        if( eventEntry->getEventType() == EventEntry::START_CYCLE )
        {
            if( lastTime_ != Timestamp::NOT_SET_TIME )
            {
                bytesWritten += writeRow();
                clearRow();
            }
            lastTime_ = eventEntry->getTimestamp();
        }
    }
    else if( entry->getType() == LogEntry::SYSLOG_LOG_ENTRY )
    {
        if( !interpolate_ )
        {
            bytesWritten += writeSyslog( ( SyslogEntry* )entry );
        }
        else
        {
            bytesWritten += writeInterpolated( entry, -1 );
        }
    }
    /*
    else if( entry->getType() == LogEntry::DIRECTORY_LOG_ENTRY )
    {
        if( static_cast<const DirectoryEntry*>( entry )->getDirectoryType() == DirectoryEntry::VERSION )
        {
            bytesWritten += writeAllInterpolated();
        }
    }
    */
    /// Or else what?
    return bytesWritten;

}

unsigned int TableLogWriter::writeHeader()
{
    unsigned int bytesWritten( 0 );
    if( NULL != outputTo_ )
    {
        bytesWritten += outputTo_->writeHeader();
    }
    return bytesWritten;
}

unsigned int TableLogWriter::writeAllInterpolated()
{
    unsigned int bytesWritten( 0 );
    if( interpolate_ )
    {
        int rows = interpBuffer_.size();
        for( int i = 0; i < rows; ++i )
        {
            bytesWritten += writeDataEntryRow( 0 );
        }
    }
    return bytesWritten;
}

unsigned int TableLogWriter::writeFooter()
{
    unsigned int bytesWritten( 0 );
    bytesWritten += writeAllInterpolated();
    if( NULL != outputTo_ )
    {
        bytesWritten += outputTo_->writeFooter();
    }
    return bytesWritten;

}

void TableLogWriter::clearRow()
{
}

unsigned int TableLogWriter::writeRow()
{
    unsigned int bytesWritten( 0 );
    if( NULL != outputTo_ )
    {
        EventEntry cycleStartEntry( NULL, NULL, EventEntry::START_CYCLE );
        cycleStartEntry.setTimestamp( lastTime_ );
        bytesWritten += outputTo_->write( &cycleStartEntry );
    }
    return bytesWritten;
}

unsigned int TableLogWriter::writeSyslog( const SyslogEntry* syslogEntry )
{
    unsigned int bytesWritten( 0 );
    if( NULL != outputTo_ )
    {
        bytesWritten += outputTo_->write( syslogEntry );
    }
    return bytesWritten;
}

unsigned int TableLogWriter::writeToColumn( const DataEntry *dataEntry, const int column )
{
    unsigned int bytesWritten( 0 );
    if( NULL != outputTo_ && NULL != dataEntry )
    {
        bytesWritten += outputTo_->write( dataEntry );
    }
    return bytesWritten;
}

unsigned int TableLogWriter::writeInterpolated( const LogEntry *logEntry, const int column )
{
    unsigned int bytesWritten( 0 );
    Timestamp timestamp = logEntry->getTimestamp();
    if( timestamp < lastTime_ )
    {
        timestamp = lastTime_;
    }
    unsigned int row;
    const DataEntry** dataEntryRow( NULL );
    for( row = 0; row < interpTimes_.size(); ++row )
    {
        if( timestamp == *interpTimes_[row] )
        {
            break;
        }
        else if( timestamp < *interpTimes_[row] )
        {
            interpTimes_.insert( row, new Timestamp( timestamp ) );
            dataEntryRow = newDataEntryRow();
            interpBuffer_.insert( row, dataEntryRow );
            interpSyslogs_.insert( row, NULL );
            break;
        }
    }
    if( row >= interpTimes_.size() )
    {
        interpTimes_.push( new Timestamp( timestamp ) );
        dataEntryRow = newDataEntryRow();
        interpBuffer_.push( dataEntryRow );
        interpSyslogs_.push( NULL );
    }
    else if( NULL == dataEntryRow )
    {
        dataEntryRow = interpBuffer_[row];
    }
    if( logEntry->getType() == LogEntry::DATA_LOG_ENTRY && column >= 0 )
    {
        dataEntryRow[column] = new DataEntry( ( DataEntry* )logEntry );
        for( int i = row - 1; i >= 0; --i )
        {
            dataEntryRow = interpBuffer_[i];
            const DataEntry* startEntry = dataEntryRow[column];
            if( NULL != startEntry )
            {
                for( unsigned int j = i + 1; j < row; ++j )
                {
                    dataEntryRow = interpBuffer_[j];
                    if( ( ( DataEntry* )logEntry )->isDataWrite() )
                    {
                        dataEntryRow[column] = interpolateDataEntry( *interpTimes_[j], startEntry, ( DataEntry* )logEntry );
                    }
                    else
                    {
                        readerColumn_[column] = &True_;
                        dataEntryRow[column] = nanDataEntry( *interpTimes_[j], ( DataEntry* )logEntry );
                    }
                }
                break;
            }
        }
    }
    else if( logEntry->getType() == LogEntry::SYSLOG_LOG_ENTRY )
    {
        interpSyslogs_[row] = new SyslogEntry( *( SyslogEntry* )logEntry );
    }


    int doneRowMax = 0;
    for( unsigned int i = 1; i <= row; ++i )
    {
        dataEntryRow = interpBuffer_[i];
        if( NULL != dataEntryRow )
        {
            for( int j = 0; j < varCount_; ++j )
            {
                if( NULL == dataEntryRow[j] )
                {
                    break;
                }
                else if( j + 1 == varCount_ )
                {
                    doneRowMax = i;
                }
            }
        }
    }

    for( int i = 0; i < doneRowMax; ++i )
    {
        bytesWritten += writeDataEntryRow( 0 );
    }

    return bytesWritten;
}

unsigned int TableLogWriter::writeDataEntryRow( unsigned int row )
{
    const DataEntry** dataEntryRow = interpBuffer_[row];
    const SyslogEntry* syslogEntry = interpSyslogs_[row];
    const Timestamp& timestamp = *interpTimes_[row];
    unsigned int bytesWritten( 0 );
    lastTime_ = timestamp;
    for( int column = 0; column < varCount_; ++column )
    {
        if( NULL != dataEntryRow[column] )
        {
            bytesWritten += writeToColumn( dataEntryRow[column], column );
            delete dataEntryRow[column];
            dataEntryRow[column] = NULL;
        }
        else if( readerColumn_[column] == &True_ )
        {
            bytesWritten += writeToColumn( NULL, column );
        }
    }
    if( NULL != syslogEntry )
    {
        bytesWritten += writeSyslog( syslogEntry );
        delete syslogEntry;
    }
    bytesWritten += writeRow();
    deleteDataEntryRow( interpBuffer_[row] );
    interpBuffer_.pop( row );
    interpSyslogs_.pop( row );
    delete interpTimes_[row];
    interpTimes_.pop( row );
    return bytesWritten;
}

DataEntry* TableLogWriter::interpolateDataEntry( const Timestamp& timestamp, const DataEntry* startEntry, const DataEntry* endEntry )
{
    double startValue( nan( "" ) );
    const Unit& unit = endEntry->getDataValue()->getUnit();
    startEntry->getDataValue()->copyTo( unit, startValue );
    double endValue( nan( "" ) );
    endEntry->getDataValue()->copyTo( unit, endValue );
    double value;
    if( startEntry->getDataAccess()->getNoInterpFlag() )
    {
        if( timestamp == endEntry->getTimestamp() )
        {
            value = endValue;
        }
        else
        {
            value = startValue;
        }
    }
    else
    {
        value = AuvMath::Interpolate1D( timestamp.asDouble(), startValue, endValue, startEntry->getTimestamp().asDouble(), endEntry->getTimestamp().asDouble() );
    }
    DataEntry* dataEntry = new DataEntry( endEntry );
    dataEntry->getDataValue()->setFrom( unit, value );
    dataEntry->setTimestamp( timestamp );
    return dataEntry;
}

DataEntry* TableLogWriter::nanDataEntry( const Timestamp& timestamp, const DataEntry* origEntry )
{
    const Unit& unit = origEntry->getDataValue()->getUnit();
    double value( nan( "" ) );
    DataEntry* dataEntry = new DataEntry( origEntry );
    dataEntry->getDataValue()->setFrom( unit, value );
    dataEntry->setTimestamp( timestamp );
    return dataEntry;
}
