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

#include "Reporter.h"

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

const StrValue Reporter::ReportItem::NO_VALUE( "no_value" );

// Constructor
Reporter::Reporter()
    : SyncReporterComponent( "Reporter", NULL ),
      reportArray_( true )
{
    setState( BLOCK_NORMAL );
}

// Run the component
void Reporter::run( void )
{
    MutexLocker mutexLocker( mutex_ );
    ReportItem *ri;
    unsigned int i;


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

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

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

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

// updates a how a uriCode is reported
void Reporter::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 Reporter::removeReport( unsigned short int uriCode )
{
    MutexLocker mutexLocker( mutex_ );
    ReportItem *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 Reporter::removeAllReports()
{
    MutexLocker mutexLocker( mutex_ );
    // go through our list of report items delete them
    reportArray_.clear();
}

// lists the uriCodes being reported
void Reporter::listReports()
{
    MutexLocker mutexLocker( mutex_ );
    ReportItem *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_ );
    }
}

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

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

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

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

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

// set the interval of how often the item is supposed to be reported.
void Reporter::ReportItem::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 Reporter::ReportItem::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 Reporter::ReportItem::report( Logger *logger )
{
    //StrValue value;

    // make sure we have a logger and an initialized dataReader
    if( NULL == logger || !initDataReader() )
    {
        return false;
    }
    // read the value
    //if( !dataReader_->read( value ) )
    //{
    //    return false;
    //}
    DataElement& de = dataReader_->getElement();
    DataValue* value = de.getDataValue();
    if( NULL == value )
    {
        return false;
    }

    // report the item to our logger
    logger->syslog( Str( dataReader_->getUri().cStr() ) + " " + value->toString(), Syslog::INFO );
    // update the last reported timestamp
    lastReported_ = Timestamp::Now();
    return true;
}

void Reporter::ReportItem::reportStatus( Logger * logger )
{
    ElementURI *elementUri;
    Str output;

    if( NULL == logger )
    {
        return;
    }

    // get the name
    elementUri = Slate::GetElementURI( uriCode_ );
    if( NULL == elementUri )
    {
        return;
    }
    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 Reporter::ReportItem::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 );
    return true;
}

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


