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

#include "PAR_Licor.h"
#include "PAR_LicorIF.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

PAR_Licor::PAR_Licor( const Module* module )
    : SyncSensorComponent( PAR_LicorIF::NAME, module ),
      boundingError_( false ),
      adcCalCfg_( nanf( "" ) ),
      multiplierCfg_( nanf( "" ) ),
      parCalCfg_( nanf( "" ) ),
      maxBoundCfg_( nanf( "" ) ),
      minBoundCfg_( nanf( "" ) ),
      analogToDigital_( PAR_LicorIF::AD, PAR_LicorIF::AD_VREF, PAR_LicorIF::AD_RES, PAR_LicorIF::AD_TIMEOUT, !simulateHardware(), logger_ ),
      loadControl_( PAR_LicorIF::LOAD_CONTROL, !simulateHardware(), logger_, this )
{
    setRepeater( true );

    adcCalCfgReader_ = newConfigReader( PAR_LicorIF::ADC_CAL_CFG );
    darkCountCfgReader_ = newConfigReader( PAR_LicorIF::DARK_COUNT_CFG );
    multiplierCfgReader_ = newConfigReader( PAR_LicorIF::MULTIPLIER_CFG );
    parCalCfgReader_ = newConfigReader( PAR_LicorIF::PAR_CAL_CFG );
    maxBoundCfgReader_ = newConfigReader( PAR_LicorIF::MAX_BOUND_CFG );
    minBoundCfgReader_ = newConfigReader( PAR_LicorIF::MIN_BOUND_CFG );
    maxValidPitchCfgReader_ = newConfigReader( PAR_LicorIF::MAX_VALID_PITCH_CFG );
    minValidPitchCfgReader_ = newConfigReader( PAR_LicorIF::MIN_VALID_PITCH_CFG );

    parWriter_ = newUniversalWriter( UniversalURI::DOWNWELLING_PHOTOSYNTHETIC_PHOTON_FLUX_IN_SEA_WATER );
    parWriter_->setAccuracy( Units::MICROMOLE_PER_SECOND_PER_SQUARE_METER, ACCURACY );
    pitchReader_ = newUniversalReader( UniversalURI::PLATFORM_PITCH_ANGLE );
    adcCountWriter_ = newDataWriter( PAR_LicorIF::ADC_COUNT );
}

PAR_Licor::~PAR_Licor()
{}

void PAR_Licor::initialize()
{
    loadControl_.deisolateLoad();
    this->setAllowableFailures( 3 );
    readConfig();
    analogToDigital_.startRead();
}

void PAR_Licor::run()
{
    if( isRepeating() )
    {
        analogToDigital_.startRead();
        return;
    }

    Timestamp now( Timestamp::Now() );

    readConfig();

    float adcCount( nanf( "" ) ), par( nanf( "" ) );

    if( !simulateHardware() )
    {
        // Read ADC
        adcCount = analogToDigital_.readCounts();

        // Convert from counts to uA
        float uA = ( adcCount - darkCountCfg_ ) * adcCalCfg_;

        // Convert from uA to PAR
        par = uA * multiplierCfg_ * parCalCfg_;

        if( isnan( par ) )
        {
            setFailure( FailureMode::DATA );
            return;
        }

    }

    float simDepth, simLon;
    // Always include simulated values, even if hardware exists.
    // If we are deployed, there will be no simulated values available.
    if( SimSlate::Read( SimSlate::DEPTH_METER, simDepth )
            &&  SimSlate::Read( SimSlate::LATITUDE_DEGREE, simLon ) )
    {
        // Super simple model of downwelling par based on depth,
        // longitude, and time of day
        double daySec = fmod( now.asDouble(), 86400.0 );
        double timeFactor = 0.1 + cos( ( daySec - 43200 ) * M_PI / 43200 + simLon );
        if( timeFactor < 0 )
        {
            timeFactor = 0;
        }
        double depthFactor = exp10( simDepth / -40 );
        par = 900 * timeFactor * depthFactor;
        if( isnan( adcCount ) )
        {
            adcCount = floor( par / multiplierCfg_ / parCalCfg_ / adcCalCfg_ ) + darkCountCfg_;
        }
    }

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

    if( ( par < maxBoundCfg_ ) && ( par > minBoundCfg_ ) )
    {
        boundingError_ = false;

        float pitch( nanf( "" ) );
        if( pitchReader_->read( Units::RADIAN, pitch ) && !isnan( pitch )
                && pitch >= minValidPitchCfg_ && pitch <= maxValidPitchCfg_ )
        {
            parWriter_->setInvalid( false );
        }
        else
        {
            parWriter_->setInvalid( true );
        }
    }
    else
    {
        parWriter_->setInvalid( true );
        if( !boundingError_ )
        {
            //.syslog( Str( "PAR reading out of range: " )  + par + " uMol/s/m2", Syslog::ERROR );
            boundingError_ = true;
        }
    }

    if( !isnan( par ) )
    {
        parWriter_->write( Units::MICROMOLE_PER_SECOND_PER_SQUARE_METER, par, now );
    }
}

void PAR_Licor::uninitialize()
{}

void PAR_Licor::readConfig()
{
    adcCalCfgReader_->read( Units::MICROAMPERE_PER_COUNT, adcCalCfg_ );
    darkCountCfgReader_->read( Units::COUNT, darkCountCfg_ );
    multiplierCfgReader_->read( Units::MICROMOLE_PER_SECOND_PER_SQUARE_METER_PER_MICROAMPERE, multiplierCfg_ );
    parCalCfgReader_->read( Units::NONE, parCalCfg_ );
    maxBoundCfgReader_->read( Units::MICROMOLE_PER_SECOND_PER_SQUARE_METER, maxBoundCfg_ );
    minBoundCfgReader_->read( Units::MICROMOLE_PER_SECOND_PER_SQUARE_METER, minBoundCfg_ );
    maxValidPitchCfgReader_->read( Units::RADIAN, maxValidPitchCfg_ );
    minValidPitchCfgReader_->read( Units::RADIAN, minValidPitchCfg_ );
}

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

