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

#include "LcmUniversalReporter.h"

#include "data/ElementURI.h"
#include "data/Slate.h"
#include "units/Units.h"

const StrValue LcmUniversalReporter::LcmReportItem::NO_VALUE( "no_value" );

LcmUniversalReporter* LcmUniversalReporter::Instance_( NULL );

// Constructor
LcmUniversalReporter::LcmUniversalReporter()
    : SyncReporterComponent( "LcmUniversalReporter", NULL ),
      reportArray_( true )  // delete array entries on cleanup
{
    setState( BLOCK_NORMAL );
    Instance_ = this;
}

LcmUniversalReporter::~LcmUniversalReporter()
{
    Instance_ = NULL;
}

/// Activates messaging of ALL universal data.
void LcmUniversalReporter::activateMessaging()
{
    logger_.syslog( "Activating messaging.", Syslog::INFO );

    // loop through the Slate to search for universal data elements
    for( unsigned short int code = 0; code < Slate::GetElementURICount(); ++code )
    {
        DataElement* element = Slate::GetElement( code );

        if( NULL != element && element->isUniversal() && !element->isOrphaned() )
        {
            // add reporter for universal
            addReport( code );
        }
    }
}

/// Deactivates messaging.
void LcmUniversalReporter::deactivateMessaging( void )
{
    logger_.syslog( "Deactivating messaging.", Syslog::INFO );

    // remove all the report items so nothing will be reported
    removeAllReports();

    // remove all LCM messages
    lcmMsgMap_.clear( true );
}

// Run the component
void LcmUniversalReporter::run( void )
{
    if( !reportArray_.isEmpty() )
    {
        MutexLocker mutexLocker( mutex_ );
        LcmReportItem *ri( NULL );

        // go through our list of items to report and report them
        for( unsigned int i = 0; i < reportArray_.size(); ++i )
        {
            ri = reportArray_.get( i );
            if( ri != NULL && ri->needsReporting() )
            {
                ri->report();
            }
        }
    }
}

// add a uriCode for the LcmUniversalReporter to report
bool LcmUniversalReporter::addReport( unsigned short int uriCode, ReportType type, const Timespan *timespan )
{
    MutexLocker mutexLocker( mutex_ );
    LcmReportItem *ri;
    unsigned int i;

    if( ElementURI::NO_CODE == uriCode )
    {
        return false;
    }

    // see if this uriCode already exist, if so
    // then we will change the LcmReportItem accordingly
    for( i = 0; i < reportArray_.size(); ++i )
    {
        ri = reportArray_.get( i );
        if( uriCode == ri->getUriCode() )
        {
            ri->setReportType( type );
            ri->setTimespan( timespan );
            return false;
        }
    }
    reportArray_.push( new LcmReportItem( this, uriCode, type, timespan ) );
    return true;
}

// updates a how a uriCode is reported
void LcmUniversalReporter::changeReport( unsigned short int uriCode, ReportType type, const Timespan *timespan )
{
    if( ElementURI::NO_CODE == uriCode )
    {
        return;
    }
    addReport( uriCode, type, timespan );
}

// removes the uriCode from being reported
void LcmUniversalReporter::removeReport( unsigned short int uriCode )
{
    MutexLocker mutexLocker( mutex_ );
    LcmReportItem *ri;
    unsigned int i;

    if( ElementURI::NO_CODE == uriCode )
    {
        return;
    }

    // go through our list of report items and
    // find the item to remove and then pop it
    // off our list
    for( i = 0; i < reportArray_.size(); ++i )
    {
        ri = reportArray_.get( i );
        if( uriCode == ri->getUriCode() )
        {
            reportArray_.pop( i );
            delete ri;
            return;
        }
    }
}

// removes all the report items so nothing will be reported
void LcmUniversalReporter::removeAllReports()
{
    MutexLocker mutexLocker( mutex_ );
    // go through our list of report items delete them
    reportArray_.clear();
}

// lists the uriCodes being reported
void LcmUniversalReporter::listReports()
{
    MutexLocker mutexLocker( mutex_ );
    LcmReportItem *ri;
    unsigned int i;

    // go through our list of report items and
    // tell each item to report its status
    for( i = 0; i < reportArray_.size(); ++i )
    {
        ri = reportArray_.get( i );
        ri->reportStatus( &logger_ );
    }
}

// external interface for adding report
bool LcmUniversalReporter::AddReport( unsigned short int uriCode )
{
    if( NULL == Instance_ )
    {
        return false;
    }

    DataElement* element = Slate::GetElement( uriCode );

    if( NULL != element && element->isUniversal() && !element->isOrphaned() )
    {
        return Instance_->addReport( uriCode );
    }

    return false;
}

// external interface for removing report
void LcmUniversalReporter::RemoveReport( unsigned short int uriCode )
{
    if( NULL != Instance_ )
    {
        Instance_->removeReport( uriCode );
    }
}

// external interface for removing all reports
void LcmUniversalReporter::RemoveAllReports( void )
{
    if( NULL != Instance_ )
    {
        Instance_->deactivateMessaging();
    }
}

// Constructor
LcmUniversalReporter::LcmReportItem::LcmReportItem( Component *owner, unsigned short int uriCode, ReportType type, const Timespan *timespan )
    : uriCode_( uriCode ),
      type_( type )
{
    owner_ = owner;
    dataReader_ = NULL;
    lcmMsg_ = new LcmMsgWriter( "Universal" );
    initDataReader();	// try to init the dataReader
    setTimespan( timespan );
    lastReported_ = Timestamp::Now() - timespan_;
    oldValue_ = "";
}

// returns the uriCode of a report item
unsigned short int LcmUniversalReporter::LcmReportItem::getUriCode()
{
    return uriCode_;
}

// returns how the return type is being reported
LcmUniversalReporter::ReportType LcmUniversalReporter::LcmReportItem::getReportType()
{
    return type_;
}

// returns the interval in seconds of when the item is going to be reported
const Timespan* LcmUniversalReporter::LcmReportItem::getTimespan()
{
    return &timespan_;
}

// sets an items report type
void LcmUniversalReporter::LcmReportItem::setReportType( ReportType type )
{
    type_ = type;
}

// set the interval of how often the item is supposed to be reported.
void LcmUniversalReporter::LcmReportItem::setTimespan( const Timespan *timespan )
{
    if( NULL == timespan )
    {
        // if the user did not give us a timespan make it 0
        timespan_ = Timespan( 0 );
        return;
    }
    timespan_ = *timespan;
}

// does the value need to be reported?
bool LcmUniversalReporter::LcmReportItem::needsReporting()
{
    StrValue value;

    // make sure dataReader_ is initialized
    if( !initDataReader() )
    {
        return false;
    }

    // what type of reporting are we doing for this item
    switch( type_ )
    {
    case REPORT_CHANGE:
        // read the current value.
        if( !dataReader_->read( value ) )
        {
            return false;
        }
        // is it different than the old value?
        if( value.asString() != oldValue_ )
        {
            // yes, it is different
            oldValue_ = value.asString();
            return true;
        }
        // no, it is not different
        return false;
    case REPORT_PERIODICALLY:
        // is the current time greater than our last reported + timespan
        return ( Timestamp::Now() > ( lastReported_ + timespan_ ) );
    case REPORT_TOUCHED:
        // has the it been touched recently?
        return dataReader_->getTimestamp() - lastReported_ > Timespan::ZERO_TIMESPAN;
    default:
        return false;
    }
    return false;
}

// reports the items string value as a string
bool LcmUniversalReporter::LcmReportItem::report()
{
    // make sure we have an initialized dataReader
    if( !initDataReader() )
    {
        return false;
    }

    // Update the LCM message with reader's DataElement
    lcmMsg_->setValue( dataReader_->getElement() );
    lcmMsg_->publish();

    // update the last reported timestamp
    lastReported_ = Timestamp::Now();
    return true;
}

void LcmUniversalReporter::LcmReportItem::reportStatus( Logger* logger )
{
    Str output;

    if( NULL == logger )
    {
        return;
    }

    // get the name
    const ElementURI elementUri = dataReader_->getUri();
    output = Str( elementUri );

    output += Str( " reporting " );
    switch( type_ )
    {
    case REPORT_CHANGE:
        output += Str( "changes" );
        break;
    case REPORT_PERIODICALLY:
        output += Str( "periodically every " );
        output += timespan_.asDouble();
        output += Str( " seconds" );
        break;
    case REPORT_TOUCHED:
        output += Str( "when touched" );
        break;
    default:
        output += " invalid";
        break;
    }
    logger->syslog( output, Syslog::INFO );
}

// initializes the data reader for the report item.
// if the item has never been logged in the slate then
// its data element does not exist.  So, we will
// keep trying to get a data reader until we get it.
bool LcmUniversalReporter::LcmReportItem::initDataReader()
{
    DataElement *dataElement;

    // is the dataReader already initialized?
    if( NULL != dataReader_ )
    {
        return true;
    }

    // does there exist a dataElement for this uricode?
    dataElement = Slate::GetElement( uriCode_ );
    if( NULL == dataElement )
    {
        return false;
    }
    // get the data reader if there is a dataElement
    dataReader_ = new DataReader( owner_, *dataElement, &NO_VALUE, false );
    // add entry for the universal in the LCM message
    lcmMsg_->add( *dataElement );
    return true;
}

// Destructor
LcmUniversalReporter::LcmReportItem::~LcmReportItem()
{
    // cleanup
    if( NULL != dataReader_ )
    {
        delete dataReader_;
    }

    delete lcmMsg_;
}


