/** \file
 *
 *  Contains the PowerOnly class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 *  This class simply provides power and a data writer to
 *  allowing a mission to request it
 */

#include "PowerOnly.h"
#include "PowerOnlyIF.h"
#include "Power24vConverterIF.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"

PowerOnly::PowerOnly( const Module* module )
    : SyncSensorComponent( PowerOnlyIF::NAME, module ),
      loadControls_( true, 3 ),
      sampling_( false ),
      debug_( false )
{
    power24vConverterDataReader_ = newDataReader( Power24vConverterIF::POWER_24V_CONVERTER );

    // Slate outputs
    samplingPowerOnlyDataWriter_ = newDataWriter( PowerOnlyIF::SAMPLE_POWERONLY );

    // Configuration inputs
    sampleTimeCfgReader_ = newConfigReader( PowerOnlyIF::SAMPLE_TIME_CFG );

    // Initialize PowerOnly Load Control Boards (LCBs)
    addLoadControl( PowerOnlyIF::LOAD_CONTROL, PowerOnlyIF::SAMPLE_TIME_CFG1, PowerOnlyIF::SAMPLE_LOAD1 );
    addLoadControl( PowerOnlyIF::LOAD_CONTROL2, PowerOnlyIF::SAMPLE_TIME_CFG2, PowerOnlyIF::SAMPLE_LOAD2 );
    addLoadControl( PowerOnlyIF::LOAD_CONTROL3, PowerOnlyIF::SAMPLE_TIME_CFG2, PowerOnlyIF::SAMPLE_LOAD3 );

    this->setAllowableFailures( 3 );
    this->setRetryTimeout( 150 );

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

PowerOnly::~PowerOnly()
{
    // Delete PowerOnly load controls
    while( !loadControls_.isEmpty() )
    {
        delete loadControls_.pop();
    }
}

bool PowerOnly::readConfig( void )
{
    bool ok = true;
    // Config inputs
    float sampleTimeLoc;
    Timespan sampleTime;

    ok &= sampleTimeCfgReader_->read( Units::SECOND, sampleTimeLoc );
    sampleTime = Timespan::Seconds( sampleTimeLoc );

    // To be backwards compatible with older missions and configs that use
    // sampleTime we try to read the sample time for a specific LCB first but
    // fall back to sampleTime if it is not set.
    for( unsigned i = 0; i < loadControls_.size(); i++ )
    {
        LCB& lcb = *loadControls_[i];
        ok &= lcb.sampleTimeCfgReader_->read( Units::SECOND, sampleTimeLoc );
        if( !isnan( sampleTimeLoc ) )
            lcb.sampleTime_ = Timespan::Seconds( sampleTimeLoc );
        else
            lcb.sampleTime_ = sampleTime;
    }

    return ok;

}

/// Do what needs to be done to run
Component::RunState PowerOnly::start()
{
    if( debug_ ) logger_.syslog( "Start", Syslog::INFO );

    if( !readConfig() )
    {
        logger_.syslog( "Failed to load configuration parameters.", Syslog::CRITICAL );
        this->setFailure( FailureMode::DATA );
        return START;
    }

    // Request 24v power
    power24vConverterDataReader_->requestData( true );

    // Power up requested LCBs
    for( unsigned i = 0; i < loadControls_.size(); ++i )
    {
        if( NULL != loadControls_[i] )
        {
            // Get the LCB
            LCB& lcb = *loadControls_[i];

            if( lcb.sampleRequest_ )
            {
                // Power up the LCB. Respond to a fault if encountered.
                if( !powerUpLoadControl( lcb ) ) return STOP;
            }
        }
        else
        {
            logger_.syslog( "Failed to retrieve a valid LCB instance for index# " + Str( ( int )i ), Syslog::FAULT );
            this->setFailure( FailureMode::SOFTWARE );
            return STOP;
        }
    }

    return STARTING;
}

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

    return RUNNABLE; // Power is already on. Just leaving this state available for future development
}

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

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

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


Component::RunState PowerOnly::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;
    }

    sampling_ = false;
    for( unsigned i = 0; i < loadControls_.size(); ++i )
    {
        if( NULL != loadControls_[i] )
        {
            // Get the LCB
            LCB& lcb = *loadControls_[i];

            // Log voltage and current and check for any faults
            if( !logVoltageAndCurrent( lcb ) ) return STOP;

            // Check to see if the LCB is in the right state
            if( lcb.samplingLoad_ != lcb.sampleRequest_ )
            {
                if( lcb.sampleRequest_ )
                {
                    // Power up the LCB. Respond to a fault if needed.
                    if( !powerUpLoadControl( lcb ) ) return STOP;
                }
                else
                {
                    // Power down the LCB. Respond to a fault if needed.
                    if( !powerDownLoadControl( lcb ) ) return STOP;
                }
            }

            // Log the LCB state and update the component state
            if( lcb.samplingLoad_ )
                lcb.samplingLoadWriter_->write( Units::BOOL, lcb.samplingLoad_ );
            sampling_ |= lcb.samplingLoad_;
        }
    }

    // Log the component state. True if we have at least one enabled LCB.
    samplingPowerOnlyDataWriter_->write( Units::BOOL, sampling_ );

    this->resetFailCount();
    return RUNNABLE;
}

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

    sampling_ = false;
    for( unsigned i = 0; i < loadControls_.size(); ++i )
    {
        if( NULL != loadControls_[i] )
        {
            // Get the LCB...
            LCB& lcb = *loadControls_[i];
            // ... and power down
            sampling_ |= !powerDownLoadControl( lcb ); // returns true when the LCB is successfully powered off.
        }
    }

    samplingPowerOnlyDataWriter_->write( Units::BOOL, sampling_ );

    // 24v power is no longer needed
    power24vConverterDataReader_->requestData( false );

    return STOPPING;
}


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

    if( !simulateHardware() )
    {
        for( unsigned i = 0; i < loadControls_.size(); ++i )
        {
            if( NULL != loadControls_[i] )
            {
                // Get the LCB
                LCB& lcb = *loadControls_[i];

                // See if anything went wrong that may have caused this request for uninitialize
                lcb.loadControl_.readFaults();
                if( lcb.loadControl_.hasError() )
                {
                    logger_.syslog( lcb.name_ + " LCB fault: " + lcb.loadControl_.errorString(), Syslog::FAULT );
                    this->setFailure( FailureMode::HARDWARE );
                    return STOP;
                }
            }
        }
    }
    // Currently used to delay one more cycle to allow for full power down
    return STOPPED;
}


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

    return STOPPED;
}

bool PowerOnly::isDataRequested()
{
    bool sampleRequested( false );

    // Check to see if power is requested from all LCBs
    bool sampleAll = samplingPowerOnlyDataWriter_->isDataRequested();

    // check if at least one LCB is configured for Always on
    readConfig();
    for( unsigned i = 0; i < loadControls_.size(); i++ )
    {
        LCB& lcb = *loadControls_[i];
        if( lcb.sampleTime_ == Timespan::Seconds( 0 ) )
        {
            sampleRequested = true;
            break;
        }
    }

    for( unsigned i = 0; i < loadControls_.size(); ++i )
    {
        if( NULL != loadControls_[i] )
        {
            // Check to see if power is requested from a single LCB,
            // or simply enable if sampleAll is true.
            sampleRequested |= loadControls_[i]->isRequested( sampleAll );
        }
    }

    return sampleRequested;
}

// Log voltage and current and check for any faults
bool PowerOnly::logVoltageAndCurrent( LCB& lcb )
{
    bool ok( true );
    if( simulateHardware() )
        return ok;

    // Query the LCB
    lcb.loadControl_.requestVoltageAndCurrent();

    if( lcb.loadControl_.hasError() )
    {
        logger_.syslog( lcb.name_ + "LCB fault: " + lcb.loadControl_.errorString(), Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
        ok = false;
    }

    return ok;
}

void PowerOnly::addLoadControl( const ConfigURI& loadControlCfg, const ConfigURI& sampleTimeCfg, const DataURI& sampleLoadControl )
{
    StrValue loadCtrl;
    if( Slate::ReadOnce( loadControlCfg, loadCtrl, logger_ ) && !loadCtrl.asString().endsWith( "null" ) )
    {
        logger_.syslog( "Adding load control power supply at " + loadCtrl.asString(), Syslog::INFO );

        /// Parse a loadControl name from the URI string
        char *lcbName = strchr( ( char* )loadControlCfg.cStr(), '.' );

        if( !lcbName )
        {
            lcbName = ( char* )loadControlCfg.cStr();
        }
        else
        {
            // loadControl name starts after '.'
            lcbName++;
        }

        LCB* lcb = new LCB( loadControlCfg, !simulateHardware(), logger_, this, lcbName );
        lcb->samplingLoadWriter_ = newDataWriter( sampleLoadControl );
        lcb->sampleTimeCfgReader_ = newConfigReader( sampleTimeCfg );

        loadControls_.push( lcb );
    }
}

bool PowerOnly::powerUpLoadControl( LCB& lcb )
{
    logger_.syslog( "Powering up " + lcb.name_, Syslog::INFO );

    if( !simulateHardware() )
    {
        Timespan::Milliseconds( 15 ).sleepFor();
        if( !lcb.loadControl_.powerUp() )
        {
            logger_.syslog( "Failed to power up " + lcb.name_, Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
            return false;
        }
    }

    // Start the power timer
    lcb.samplingLoadWriter_->write( Units::BOOL, true );
    lcb.startTime_ = Timestamp::Now();
    lcb.samplingLoad_ = true;

    return true;
}

bool PowerOnly::powerDownLoadControl( LCB& lcb )
{
    logger_.syslog( "Powering down " + lcb.name_, Syslog::INFO );

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

    lcb.samplingLoadWriter_->write( Units::BOOL, false );
    lcb.startTime_ = Timestamp::NOT_SET_TIME;
    lcb.samplingLoad_ = false;

    return true;
}

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

/**
 * PowerOnly load control interface.
 *
 */
LCB::LCB( const ConfigURI& loadControlCfg, bool useHardware, Logger& logger, Component* component, const Str& name )
    : loadControl_( loadControlCfg, useHardware, logger, component, name ),
      startTime_( Timestamp::NOT_SET_TIME ),
      sampleTime_( 60 ),
      sampleRequest_( false ),
      samplingLoad_( false ),
      name_( name )
{
}

bool LCB::isRequested( bool request )
{
    if( samplingLoadWriter_->isDataRequested() || request )
    {
        // Service data request. Reset the clock.
        startTime_ = Timestamp::Now();
        sampleRequest_ = true;
    }
    else if( sampleTime_.asMillis() == 0 || startTime_.elapsed() < sampleTime_ )
    {
        // Clock still going.
        sampleRequest_ = true;
    }
    else
    {
        // No requests at this time.
        sampleRequest_ = false;
    }

    return sampleRequest_;
}
