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

#include "Phins_Multibeam.h"
#include "Phins_MultibeamIF.h"

#include "units/Units.h"

Phins_Multibeam::Phins_Multibeam( const Module* module )
    : SyncSensorComponent( Phins_MultibeamIF::NAME, module ),
      loadControl_( Phins_MultibeamIF::LOAD_CONTROL, !simulateHardware(), logger_, this ),
      powerOffTimeout_( 30 ),
      powerOffTime_( Timestamp::NOT_SET_TIME ),
      powered_( false ),
      debug_( false )
{
    // Slate outputs
    Phins_MultibeamDataWriter_ = newDataWriter( Phins_MultibeamIF::PHINS_MULTIBEAM );

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

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

Phins_Multibeam::~Phins_Multibeam()
{}


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

    // Power up the LCB. Respond to a fault if encountered.
    if( !powerUpLoadControl() )
    {
        return STOP;
    }

    return RUNNABLE;
}

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

    return RUNNABLE; // Power is already on.
}

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


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


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


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

    // Stop if there's no need for power.
    if( !isDataRequested() )
    {
        if( powerOffTime_ == Timestamp::NOT_SET_TIME )
        {
            // Start the power off timer.
            powerOffTime_ = Timestamp::Now();
        }
        else if( powerOffTime_.elapsed() > powerOffTimeout_ )
        {
            return STOP;
        }
    }

    // Log the usual
    loadControl_.requestVoltageAndCurrent();
    if( loadControl_.hasError() )
    {
        logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
        stop();
    }

    // Let's have a check of the power state just in case something dropped behind our back. If we're here the state should be ON or powering up.
    if( loadControl_.getPowerState() != LoadControl::ON  && loadControl_.getPowerState() != LoadControl::POWER_UP )
    {
        logger_.syslog( "Reporting not powering/powered.", Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
        return STOP;
    }
    else
    {
        this->resetFailCount();
    }

    return RUNNABLE;
}

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

    // Disarm the power off timer.
    powerOffTime_ = Timestamp::NOT_SET_TIME;

    // Power down now, check for faults next cycle.
    powerDownLoadControl();

    return STOPPING;
}


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

    if( !simulateHardware() )
    {
        // See if anything went wrong that may have caused this request for uninitialize
        loadControl_.readFaults();
        if( loadControl_.hasError() )
        {
            // And things that set failures second
            logger_.syslog( " LCB fault: " + 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 Phins_Multibeam::stopped()
{
    if( debug_ ) logger_.syslog( "Stopped", Syslog::INFO );
    if( isDataRequested() )
    {
        return start();
    }

    return STOPPED;
}


bool Phins_Multibeam::isDataRequested()
{
    // Check to see if power is needed
    return Phins_MultibeamDataWriter_->isDataRequested();
}


bool Phins_Multibeam::powerUpLoadControl()
{
    logger_.syslog( "Powering up.", Syslog::INFO );

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

    powered_ = true;
    Phins_MultibeamDataWriter_->write( Units::BOOL, powered_ );
    return true;
}


bool Phins_Multibeam::powerDownLoadControl()
{
    logger_.syslog( "Powering down.", Syslog::INFO );

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

    powered_ = false;
    Phins_MultibeamDataWriter_->write( Units::BOOL, powered_ );
    return true;
}


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