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

#include "SCPI.h"
#include "SCPIIF.h"

#include "controlModule/VerticalControlIF.h"
#include "data/ConfigReader.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"


SCPI::SCPI( const Module* module )
    : SyncSensorComponent( SCPIIF::NAME, module ),
      sampleTime_( 720 ), // Time in seconds that the SCPI is programmed to sample plus the warmup time
      loadControl_( SCPIIF::LOAD_CONTROL, !simulateHardware(), logger_, this ),
      startTime_(),
      powerOnTimeout_( 10.0 ),
      debug_( false ),
      sampling_( false ),
      uart_( SCPIIF::UART, SCPIIF::BAUD, 0.10, logger_, 4095 )
{

    // Slate outputs
    samplingSCPIDataWriter_ = newDataWriter( SCPIIF::SAMPLE_SCPI );

    sampleTimeCfgReader_ = newConfigReader( SCPIIF::SAMPLE_TIME_CFG );

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

    deviceResponse_[0] = '\0';
    this->setAllowableFailures( 3 );
    this->setRetryTimeout( 150 );
}

SCPI::~SCPI()
{
}


bool SCPI::loadParams( void )
{
    return readConfig();
}

bool SCPI::readConfig( void )
{
    bool ok = true;
    // Config inputs
    float sampleTimeLoc;
    ok &= sampleTimeCfgReader_->read( Units::SECOND, sampleTimeLoc );
    sampleTime_ = Timespan::Seconds( sampleTimeLoc );
    return ok;
}

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

    // Set the time
    startTime_ = Timestamp::Now();
    if( !simulateHardware() )
    {
        // Open the uart
        uart_.open();
        if( uart_.hasError() )
        {
            logger_.syslog( "Error opening port: ", uart_.errorString(), Syslog::ERROR );
            this->setFailure( FailureMode::COMMUNICATIONS );
            return STOP;
        }
        uart_.flush();

        logger_.syslog( "Powering up", Syslog::INFO );
        if( !loadControl_.powerUp() )
        {
            logger_.syslog( "Failed to power up", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }
    }
    ok_ = true;
    if( !loadParams() )
    {
        ok_ = false;
        // Critical error
        logger_.syslog( Str( "Error: Error loading parameters in initialization routine. Returning.\n" ), Syslog::CRITICAL );
        this->setFailure( FailureMode::DATA );
        return START;
    }
    return STARTING;
}

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

    if( simulateHardware() )
    {
        return RUNNABLE;
    }

    if( startTime_.elapsed() > powerOnTimeout_ )
    {
        // Grab the boot up banner.
        uart_.flushCRLF().readLine( deviceResponse_, sizeof( deviceResponse_ ) );
        if( !uart_.hasError() )
        {
            return RUNNABLE;
        }
        logger_.syslog( "Init failed - response: ", deviceResponse_, Syslog::INFO );
        logger_.syslog( Str( "SCPI failed to initialize" ), Syslog::FAULT );
        this->setFailure( FailureMode::COMMUNICATIONS );

        return RUNNABLE;
    }
    else
    {
        return STARTING;
    }
    return STOP;
}

/// Pause for a short period (indicated by pauseTime)
Component::RunState SCPI::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 SCPI::paused()
{
    if( debug_ ) logger_.syslog( "Paused", Syslog::INFO );
    return STOP;
}

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

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


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

    readConfig();

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

    if( !simulateHardware() )
    {
        // 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 STOP;
        }
        // Check to see if the current sample is finished
        if( sampling_ )
        {
            if( startTime_.elapsed() > sampleTime_ + powerOnTimeout_ )
            {
                // we're done for now
                sampling_ = false;
                samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
                uart_.flush();
                this->resetFailCount();
            }
            // Still waiting
            else
            {
                samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
                return RUNNABLE;
            }
        }
        // Otherwise it's time to start a sample
        else
        {
            uart_.flush();
            uart_ << "sample now\r\n";
            startTime_ = Timestamp::Now(); // Start the clock
            sampling_ = true;
            samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
        }
    }
    else // Hardware is being simulated
    {
        // Check to see if the current sample is finished
        if( sampling_ )
        {
            if( startTime_.elapsed() > sampleTime_ + powerOnTimeout_ )
            {
                // we're done for now
                sampling_ = false;
                samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
            }
            // Still waiting
            else
            {
                samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
                return RUNNABLE;
            }
        }
        // Otherwise it's time to start a sample
        else
        {
            startTime_ = Timestamp::Now(); // Start the clock
            sampling_ = true;
            samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
        }

    }
    return RUNNABLE;
}


Component::RunState SCPI::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 );
        }
        uart_.close();
    }
    sampling_ = false;
    samplingSCPIDataWriter_->write( Units::BOOL, sampling_ );
    return STOPPING;
}


Component::RunState SCPI::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 SCPI::stopped()
{
    if( debug_ ) logger_.syslog( "Stopped", Syslog::INFO );
    if( isDataRequested() )
    {
        return start();
    }

    return STOPPED;
}

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

bool SCPI::isDataRequested()
{
    return samplingSCPIDataWriter_->isDataRequested();
}
