/** \file
 *
 *  Contains the WorkSite class implementation.
 *
 *  Copyright (c) 2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "WorkSite.h"
#include "WorkSiteIF.h"
#include "bitModule/SBITIF.h"

#include "data/Location.h"

#include "data/ConfigReader.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"
#include "units/UnitRegistry.h"


const char WorkSite::WORKSITE_CFG[] = "Data/workSite.cfg";

const float WorkSite::WORKSITE_ACCURACY( 0.1 );

WorkSite::WorkSite( const Module* module )
    : SyncNavigationComponent( WorkSiteIF::NAME, module ),
      writeIntervalCfg_( 60 ),
      startTime_( Timestamp::NOT_SET_TIME ),
      readPositionOnce_( false ),
      verbosity_( 0 )
{
    // Configuration readers
    writeIntervalConfigReader_ = newConfigReader( WorkSiteIF::WRITE_INTERVAL_CFG );
    verbosityLevelConfigReader_ = newConfigReader( WorkSiteIF::VERBOSITY_CFG );

    // Universal readers
    latitudeReader_ = newUniversalReader( UniversalURI::LATITUDE );
    longitudeReader_ = newUniversalReader( UniversalURI::LONGITUDE );

    // Universal writers
    latitudeFixWriter_ = newUniversalWriter( UniversalURI::LATITUDE_FIX, Units::DEGREE, WORKSITE_ACCURACY );
    longitudeFixWriter_ = newUniversalWriter( UniversalURI::LONGITUDE_FIX, Units::DEGREE, WORKSITE_ACCURACY );
    timeFixWriter_ = newUniversalWriter( UniversalURI::TIME_FIX, Units::EPOCH_SECOND, WORKSITE_ACCURACY );

    // Slate readers
    sBITRunningReader_ = newDataReader( SBITIF::SBIT_RUNNING_STATE );

}

WorkSite::~WorkSite()
{}


void WorkSite::initialize( void )
{
    logger_.syslog( "Initializing WorkSite component." );

    float writeInterval( nanf( "" ) );
    if( writeIntervalConfigReader_->read( Units::SECOND, writeInterval ) && !isnan( writeInterval ) )
    {
        writeIntervalCfg_ = Timespan( writeInterval );
    }
    verbosityLevelConfigReader_->read( Units::COUNT, verbosity_ );

    startTime_ = Timestamp::Now();
}


void WorkSite::run( void )
{
    if( !readPositionOnce_ )
    {
        bool sbitRunning( false );
        if( sBITRunningReader_->read( sbitRunning ) && sbitRunning )
        {
            return;
        }

        // Read the last position from file and write the lat/lon universal
        double latFix( nanf( "" ) ), lonFix( nanf( "" ) );
        Timestamp timeFix( Timestamp::NOT_SET_TIME );
        if( readWorkSite( latFix, lonFix, timeFix ) )
        {
            publishToSlate( latFix, lonFix, timeFix );
        }

        readPositionOnce_ = true;
    }


    if( startTime_.elapsed() > writeIntervalCfg_ )
    {
        bool ok( true );
        double lat( nanf( "" ) ), lon( nanf( "" ) );
        Timestamp timestamp( Timestamp::NOT_SET_TIME );

        ok &= latitudeReader_->read( Units::DEGREE, lat ) && !isnan( lat );
        ok &= longitudeReader_->read( Units::DEGREE, lon ) && !isnan( lon );
        timestamp = latitudeReader_->getTimestamp();

        if( ok )
        {
            writeWorkSite( lat, lon, timestamp );
        }
    }
}


void WorkSite::writeWorkSite( double& lat, double& lon, Timestamp& time )
{
    if( verbosity_ > 0 ) logger_.syslog( "Writing workSite position to ", WORKSITE_CFG, Syslog::INFO );

    Str Lat = UniversalURI::LATITUDE + " = " + Str( lat ) + " " + UniversalURI::LATITUDE.getUnit().getName() + ";\n";
    Str Lon = UniversalURI::LONGITUDE + " = " + Str( lon ) + " " + UniversalURI::LONGITUDE.getUnit().getName() + ";\n";
    Str Time = UniversalURI::TIME + " = " + Str( time.asDouble() ) + " " + UniversalURI::TIME.getUnit().getName() + ";\n";

    // Overwrite last position entry
    FILE* fid = fopen( WORKSITE_CFG, "w+" );
    if( fid != NULL )
    {
        fwrite( Lat.cStr(), Lat.length(), 1, fid );
        fwrite( Lon.cStr(), Lon.length(), 1, fid );
        fwrite( Time.cStr(), Time.length(), 1, fid );
    }
    fclose( fid );

    startTime_ = Timestamp::Now();
}


bool WorkSite::readWorkSite( double& latitude, double& longitude, Timestamp& time )
{
    latitude = longitude = nanf( "" );
    if( latitudeReader_->read( Units::DEGREE, latitude ) && !isnan( latitude )
            && longitudeReader_->read( Units::DEGREE, longitude ) && !isnan( longitude ) )
    {
        logger_.syslog( "Already have valid position. Ignoring workSite position from ", WORKSITE_CFG, Syslog::INFO );
    }
    else
    {
        FILE* fid = fopen( WORKSITE_CFG, "rb" );
        if( NULL != fid )
        {
            logger_.syslog( "Reading workSite position from ", WORKSITE_CFG, Syslog::IMPORTANT );
            char line [ 256 ];
            while( fgets( line, sizeof line, fid ) != NULL )
            {
                Str name;
                DataValue* dataVal( NULL );
                if( parseLine( line, name, dataVal ) )
                {
                    if( name == UniversalURI::LATITUDE )
                    {
                        latitude = nanf( "" );
                        dataVal->copyTo( UniversalURI::LATITUDE.getUnit(), latitude );
                    }
                    else if( name == UniversalURI::LONGITUDE )
                    {
                        longitude = nanf( "" );
                        dataVal->copyTo( UniversalURI::LONGITUDE.getUnit(), longitude );
                    }
                    else if( name == UniversalURI::TIME )
                    {
                        double ti( nanf( "" ) );
                        time = Timestamp::NOT_SET_TIME;
                        if( dataVal->copyTo( UniversalURI::TIME.getUnit(), ti ) && !isnan( ti ) )
                        {
                            time = Timestamp( ti );
                        }
                    }
                    else
                    {
                        logger_.syslog( "Failed to read valid variable name from: ", line, Syslog::ERROR );
                    }
                }
            }
            fclose( fid );

            return ( !isnan( latitude ) && !isnan( longitude ) && time != Timestamp::NOT_SET_TIME );
        }
        else
        {
            logger_.syslog( "Failed to open ", WORKSITE_CFG, Syslog::ERROR );
        }
    }

    return false;
}


bool WorkSite::parseLine( char* buffer, Str& name, DataValue* &dataVal )
{
    char nameStr[16];
    char unitStr[16];
    double val( nanf( "" ) );
    if( 3 == sscanf( buffer, "%[^ =] = %lf %[^ ;\n];", nameStr, &val, unitStr ) )
    {
        name = Str( nameStr );

        if( isnan( val ) )
        {
            logger_.syslog( "Failed to parse value out of: ", buffer, Syslog::ERROR );
            return false;
        }

        const Unit* unit = UnitRegistry::FindUnit( Str( unitStr ) );
        if( NULL == unit )
        {
            logger_.syslog( "Failed to find unit: ", unitStr, Syslog::ERROR );
            return false;
        }

        dataVal = unit->dataValue( val, DOUBLE8 );
        return ( dataVal != NULL );
    }

    logger_.syslog( "Failed to parse: ", buffer, Syslog::ERROR );
    return false;
}


void WorkSite::publishToSlate( const double& latFix, const double& lonFix, const Timestamp& timeFix )
{
    logger_.syslog( "WorkSite fix at " + timeFix.toSmallString() + ": (" + Str( latFix ) + ", " + Str( lonFix ) + ")", Syslog::IMPORTANT );

    Timestamp dataTime = Timestamp::Now();

    timeFixWriter_->setInvalid( false );
    timeFixWriter_->write( Units::SECOND, timeFix.asDouble(), dataTime );
    latitudeFixWriter_->setInvalid( false );
    latitudeFixWriter_->write( Units::DEGREE, latFix, dataTime );
    longitudeFixWriter_->setInvalid( false );
    longitudeFixWriter_->write( Units::DEGREE, lonFix, dataTime );
}
