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

#include "VemcoVR2C.h"
#include "VemcoVR2CIF.h"

#include "data/BlobWriter.h"
#include "data/SimSlate.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataWriter.h"
#include "data/UniversalDataReader.h"
#include "units/Units.h"
#include <stdlib.h>

/****************************************************************************************************
Vemco output:

450088,002,2013-09-09 14:32:52,A69-1105,90,152,#03\r\n �� Sensor Tag; ID = 90, A2D = 152
450088,006,2013-09-09 14:33:12,A69-1105,91,29,#D8\r\n �� Sensor Tag; ID = 91, A2D = 29
450088,010,2013-09-09 14:33:57,A69-1601,179,#7D\r\n �� Standard Tag; ID = 179
450088,011,2013-09-09 14:34:03,A69-1601,191,#70\r\n �� Standard Tag; ID = 191

Actual Data from 22296 tag
450149,002,2015-04-15 22:27:38,A69-1601,22296,#DB
450149,003,2015-04-15 22:29:01,A69-1601,22296,#D4


Note: Sensor tag values are output in A2D counts only. Refer to the sensor tag specification sheet
for the appropriate slope and intercept value in order to convert the A2D counts to SI units.
****************************************************************************************************/

#define STD_TAG_LEN (6)
#define SENSOR_TAG_LEN (7)


VemcoVR2C::VemcoVR2C( const Module* module )
    : SyncSensorComponent( VemcoVR2CIF::NAME, module ),
      uart_( VemcoVR2CIF::UART, VemcoVR2CIF::BAUD, 0.110, logger_, 4095 ),
      loadControl_( VemcoVR2CIF::LOAD_CONTROL, !simulateHardware(), logger_, this ),
      debug_( false ),
      tagID_( 0 ),
      adCount_( 0 )
{

    samplingActiveWriter_ = newDataWriter( VemcoVR2CIF::SAMPLING_ACTIVE_STATE );
    responseStringWriter_ = newBlobWriter( VemcoVR2CIF::RESPONSE_STRING );
    tagIDWriter_ = newDataWriter( VemcoVR2CIF::TAG_ID );
    adCountWriter_ = newDataWriter( VemcoVR2CIF::AD_COUNT );

    // This configures the advanced run modes.
    setRunState( START );

    this->setFailureMissionCritical( false );
    this->setAllowableFailures( 3 );
    this->setRetryTimeout( 250 );
}

VemcoVR2C::~VemcoVR2C()
{
}

void VemcoVR2C::run()
{
    //logger_.syslog("Run", Syslog::INFO);
}

/// Do what needs to be done to run
/// Similar to initialize, in old init/run/uninit sequence
Component::RunState VemcoVR2C::start()
{
    if( debug_ ) logger_.syslog( "Start", Syslog::INFO );
    if( simulateHardware() )
    {
        return STARTING;
    }

    // Close if the uart if it is open
    if( uart_.isReadable() )
    {
        uart_.close();
        return START;
    }

    // Open the uart
    uart_.enableUART();
    uart_.open();
    if( uart_.hasError() )
    {
        logger_.syslog( "Error opening port: ", uart_.errorString() );
        return START;
    }

    logger_.syslog( "Powering up", Syslog::INFO );
    if( !simulateHardware() )
    {
        // The discrete may already be asserted from the stopped state, but will need to be activated here if we're coming out of a failed state
        if( !loadControl_.powerUp() )
        {
            logger_.syslog( "Failed to power up", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
            return STOPPED;
        }
    }
    return STARTING;
}


/// Might follow a STOP...START sequence
Component::RunState VemcoVR2C::starting()
{
    if( debug_ ) logger_.syslog( "Starting", Syslog::INFO );

    if( simulateHardware() )
    {
        return RUNNABLE;
    }

    // We're operating on real hardware from here down

    if( !logVoltageAndCurrent() )
    {
        return STOP;
    }
    return RUNNABLE;
}


// Pause for a short period (indicated by pauseTime)
Component::RunState VemcoVR2C::pause()
{
    // This state is not currently used
    if( debug_ ) logger_.syslog( "Pause", Syslog::INFO );
    return STOP;
}


// Should eventually follow a PAUSE request: should set continueTime
Component::RunState VemcoVR2C::paused()
{
    if( debug_ ) logger_.syslog( "Paused", Syslog::INFO );
    // This state is not currently used
    return STOP;
}


Component::RunState VemcoVR2C::resume()
{
    if( debug_ ) logger_.syslog( "Resume", Syslog::INFO );

    return START;
}


Component::RunState VemcoVR2C::resuming()
{
    if( debug_ ) logger_.syslog( "Resuming", Syslog::INFO );
    // This state is not currently used
    return START;
}


Component::RunState VemcoVR2C::runnable()
{
    if( debug_ ) logger_.syslog( "Runnable", Syslog::INFO );

    // Reset these each time through. Will be overwritten with a hit
    tagID_ = 0;
    adCount_ = 0;

    // If data is available, parse it
    if( !simulateHardware() )
    {
        if( !logVoltageAndCurrent() )
        {
            return STOP;
        }

        // Grab a line if it's there
        if( uart_.canReadUntil( '#' ) )
        {

            uart_.flushCRLF().readLine( deviceResponse_, sizeof( deviceResponse_ ) );
            if( uart_.hasError() )
            {
                logger_.syslog( "Uart error: ", uart_.errorString(), Syslog::FAULT );
                this->setFailure( FailureMode::COMMUNICATIONS );
            }
            else // Grabbed good data
            {
                logger_.syslog( "Tag found:", deviceResponse_, Syslog::IMPORTANT );
                responseStringWriter_->writeBlob( deviceResponse_, sizeof( deviceResponse_ ), Timestamp::Now() );

                readAndParseResponses();
                this->resetFailCount();
            }
        }
    }
    // write the real data if it's been populated above or count on the initialized values at the top of runnable.
    tagIDWriter_->write( Units::COUNT, tagID_ );
    adCountWriter_->write( Units::COUNT, adCount_ );

    return RUNNABLE;
}


Component::RunState VemcoVR2C::stop()
{
    if( debug_ ) logger_.syslog( "Stop", Syslog::INFO );

    logger_.syslog( "Powering down", Syslog::INFO );

    if( !simulateHardware() )
    {
        if( !loadControl_.powerDown() )  // First power down then query for faults next cycle
        {
            logger_.syslog( "Failed to power down", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
            return STOPPING;
        }
        uart_.close();
    }
    return STOPPING;
}


Component::RunState VemcoVR2C::stopping()
{
    if( debug_ ) logger_.syslog( "Stopping", Syslog::INFO );

    if( !simulateHardware() )
    {
        loadControl_.readFaults(); // See if anything went wrong that may have caused this request for uninitialize
        if( loadControl_.hasError() )
        {
            logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }
    }
    return STOPPED;
}


Component::RunState VemcoVR2C::stopped()
{
    if( debug_ ) logger_.syslog( "Stopped", Syslog::INFO );
    if( isDataRequested() )
    {
        return START;
    }
    return STOPPED;
}


bool VemcoVR2C::logVoltageAndCurrent()
{
// Log voltage and current and check for any faults
    loadControl_.requestVoltageAndCurrent();
    if( loadControl_.hasError() )
    {
        logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
        return false;
    }
    return true;
}


bool VemcoVR2C::isDataRequested()
{
    return true; // For now, this interface will always run when it's enabled at startup
}


// This duplicated stop() for now so that quit will call it
void VemcoVR2C::uninitialize()
{
    if( !simulateHardware() )
    {
        if( !loadControl_.powerDown() )
        {
            logger_.syslog( "Failed to power down", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }
        uart_.close();
    }
}

/// Should return [myNamespace]::SIMULATE_HARDWARE, or [myNamespace]::POWER, etc
ConfigURI VemcoVR2C::getConfigURI( ConfigOption configOption ) const
{
    switch( configOption )
    {
    case CONFIG_POWER:
        return VemcoVR2CIF::POWER;
    case CONFIG_SIMULATE_HARDWARE:
        return VemcoVR2CIF::SIMULATE_HARDWARE;
    default:
        return ConfigURI::NO_CONFIG_URI;
    }

}

void VemcoVR2C::readAndParseResponses( void )
{
    /*
    450088,002,2013-09-09 14:32:52,A69-1105,90,152,#03\r\n         Sensor tag ID = 90, A2D = 152
    450149,002,2015-04-15 22:27:38,A69-1601,22296,#DB\r\n          Standard tag ID = 22296

    Sensor tags have 7 fields while standard tags have 6
    */

    char* strTokPtr;
    int tokens = 0;

    // Parse on comma. Serial through date
    char* response = strtok_r( deviceResponse_, ",", &strTokPtr );
    tokens++;
    while( tokens < STD_TAG_LEN - 2 )
    {
        response = strtok_r( NULL, ",", &strTokPtr );
        tokens++;
    }

    // Tag ID
    response = strtok_r( NULL, ",", &strTokPtr );
    if( response != NULL )
    {
        tagID_ = atoi( response );
        tokens++;
        logger_.syslog( "TagID:", tagID_, Syslog::DEBUG ); // *** DEBUG
    }

    // A/D if there
    response = strtok_r( NULL, ",", &strTokPtr );
    if( response != NULL && !strstr( response, "#" ) ) // This means we got an A/D field
    {
        adCount_ = atoi( response );
        tokens++;
        logger_.syslog( "A/D:", adCount_, Syslog::DEBUG ); // *** DEBUG
    }
    else
    {
        adCount_ = 0;
    }
}
