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

#include "Turner_Cyclops_rhodamine.h"
#include "Turner_Cyclops_rhodamineIF.h"

#include "data/ConfigReader.h"
#include "data/SimSlate.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"
#include "sensorModule/OnboardIF.h"


#define ACCURACY (50) // This has not been calibrated yet

Turner_Cyclops_rhodamine::Turner_Cyclops_rhodamine( const Module* module )
    : SyncSensorComponent( Turner_Cyclops_rhodamineIF::NAME, module ),
      boundingError_( false ),
      concentrationStandardCfg_( 0 ),
      voltsStandardCfg_( 0 ),
      voltsBlankCfg_( 0 ),
      maxBoundCfg_( nanf( "" ) ),
      minBoundCfg_( nanf( "" ) ),
      scale_( 0.00241 ), // will be overwritten by config value
      analogToDigital_( Turner_Cyclops_rhodamineIF::AD, Turner_Cyclops_rhodamineIF::AD_VREF, Turner_Cyclops_rhodamineIF::AD_RES, Turner_Cyclops_rhodamineIF::AD_TIMEOUT, !simulateHardware(), logger_ ),
      loadControl_( Turner_Cyclops_rhodamineIF::LOAD_CONTROL, !simulateHardware(), logger_, this ),
      debug_( false )
{
    // setRepeater( true );

    concentrationStandardCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::CON_STD_CFG );
    voltsStandardCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::VOLT_STD_CFG );
    voltsBlankCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::VOLT_BLANK_CFG );
    maxBoundCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::MAX_BOUND_CFG );
    minBoundCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::MIN_BOUND_CFG );
    scaleCfgReader_ = newConfigReader( Turner_Cyclops_rhodamineIF::SCALE_CFG );

    rhodamineWriter_ = newUniversalWriter( UniversalURI::SEA_WATER_RHODAMINE );
    rhodamineWriter_->setAccuracy( Units::PART_PER_BILLION, ACCURACY );
    adcCountWriter_ = newDataWriter( Turner_Cyclops_rhodamineIF::ADC_COUNT );
    rhodamineVoltsWriter_ = newDataWriter( Turner_Cyclops_rhodamineIF::VOLTAGE );

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

Turner_Cyclops_rhodamine::~Turner_Cyclops_rhodamine()
{}

void Turner_Cyclops_rhodamine::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 Turner_Cyclops_rhodamine::start()
{
    if( debug_ ) logger_.syslog( "Start", Syslog::INFO );

    if( simulateHardware() )
    {
        return STARTING;
    }

    if( !loadControl_.powerUp() )
    {
        logger_.syslog( Str( "Load controller failed to power up." ), Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
    }

    this->setAllowableFailures( 3 );
    if( !readConfig() )
    {
        logger_.syslog( "Error: Error loading configuration parameters.", Syslog::CRITICAL );
        return STOP;
    }
    analogToDigital_.startRead();

    return RUNNABLE;
}

/// Might follow a STOP...START sequence
Component::RunState Turner_Cyclops_rhodamine::starting()
{
    // State not used at this time. Just stop.
    if( debug_ ) logger_.syslog( "Starting", Syslog::INFO );
    return STOP;
}

/// Pause for a short period (indicated by pauseTime)
Component::RunState Turner_Cyclops_rhodamine::pause()
{
    // State not used at this time. Just stop.
    if( debug_ ) logger_.syslog( "Pause", Syslog::INFO );
    return STOP;
}

/// Should eventually follow a PAUSE request: should set continueTime
Component::RunState Turner_Cyclops_rhodamine::paused()
{
    if( debug_ ) logger_.syslog( "Paused", Syslog::INFO );
    return STOP;
}

Component::RunState Turner_Cyclops_rhodamine::resume()
{
    if( debug_ ) logger_.syslog( "Resume", Syslog::INFO );
    return STOPPED; // State not used at this time
}

Component::RunState Turner_Cyclops_rhodamine::resuming()
{
    if( debug_ ) logger_.syslog( "Resuming", Syslog::INFO );
    return STOPPED; // State not used at this time
}


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

    // Pause if we don't want data and we're not in the middle of a sample
    if( !isDataRequested() )
    {
        return STOP;
    }

    if( !simulateHardware() )
    {

//        if( isRepeating() )
//        {
//            analogToDigital_.startRead();
//            return RUNNABLE;
//        }

        Timestamp now( Timestamp::Now() );

        readConfig();

        float adcCount( nanf( "" ) ), rhodamine( nanf( "" ) ), volts( nanf( "" ) );

        if( !simulateHardware() )
        {
            // Read ADC
            volts = analogToDigital_.read( scale_ );
            if( isnan( volts ) )
            {
                setFailure( FailureMode::DATA );
                return STOP;
            }

// A sample in ppb is calculated as follows
//CSample = [(CStd)/(VoltsStd - VoltsBlank)] * (VoltsSample – VoltsBlank)
//CStd = Concentration value of standard used for calibration
//Csample = Concentration of sample
//VoltsStd = Voltage reading from standard concentration
//VoltsSample= Voltage reading from sample(s)
//VoltsBlank = Voltage reading from blank
            rhodamine = ( concentrationStandardCfg_ / ( voltsStandardCfg_ - voltsBlankCfg_ ) ) * ( volts - voltsBlankCfg_ );
        }

        if( !isnan( adcCount ) )
        {
            adcCountWriter_->write( Units::COUNT, adcCount, now );
        }

        if( ( rhodamine < maxBoundCfg_ ) && ( rhodamine > minBoundCfg_ ) )
        {
            boundingError_ = false;
        }
        else
        {
            rhodamineWriter_->setInvalid( true );
            rhodamineVoltsWriter_->setInvalid( true );
            if( !boundingError_ )
            {
                boundingError_ = true;
            }
        }

        if( !isnan( rhodamine ) )
        {
            rhodamineWriter_->setInvalid( false );
            rhodamineVoltsWriter_->setInvalid( false );

            rhodamineWriter_->write( Units::PART_PER_BILLION, rhodamine, now );
            rhodamineVoltsWriter_->write( Units::MILLIVOLT, volts, now );
            this->resetFailCount();
        }

    }
    analogToDigital_.startRead();
    return RUNNABLE;
}


Component::RunState Turner_Cyclops_rhodamine::stop()
{
    if( debug_ ) logger_.syslog( "Stop", Syslog::INFO );
    if( !simulateHardware() )
    {
        logger_.syslog( "Powering down", Syslog::INFO );
        if( !loadControl_.powerDown() )  // First power down then query for faults next cycle
        {
            logger_.syslog( "Failed to power down", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }
    }
    rhodamineWriter_->setInvalid( true );
    return STOPPING;
}


Component::RunState Turner_Cyclops_rhodamine::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 );
        }
    }
    // Currently used to delay one more cycle to allow for full power down
    return STOPPED;
}


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


bool Turner_Cyclops_rhodamine::readConfig()
{
    // Check if all the parameters are read correctly
    bool ok = true;

    ok &= concentrationStandardCfgReader_->read( Units::PART_PER_BILLION, concentrationStandardCfg_ );
    ok &= voltsStandardCfgReader_->read( Units::VOLT, voltsStandardCfg_ );
    ok &= voltsBlankCfgReader_->read( Units::VOLT, voltsBlankCfg_ );
    ok &= maxBoundCfgReader_->read( Units::PART_PER_BILLION, maxBoundCfg_ );
    ok &= minBoundCfgReader_->read( Units::PART_PER_BILLION, minBoundCfg_ );
    ok &= scaleCfgReader_->read( Units::MILLIVOLT, scale_ );

    return ok;
}


bool Turner_Cyclops_rhodamine::isDataRequested()
{
    return rhodamineWriter_->isDataRequested()
           || rhodamineVoltsWriter_->isDataRequested();
}


/// Should return [myNamespace]::SIMULATE_HARDWARE, or [myNamespace]::POWER, etc
ConfigURI Turner_Cyclops_rhodamine::getConfigURI( ConfigOption configOption ) const
{
    return configOption == CONFIG_SIMULATE_HARDWARE ? Turner_Cyclops_rhodamineIF::SIMULATE_HARDWARE : ConfigURI::NO_CONFIG_URI;
}

