//
//  Copyright © 2001 - 2002, RESON Inc. All Rights Reserved.
//
//  No part of this file may be reproduced or transmitted in any form or by
//  any means, electronic or mechanical, including photocopy, recording, or
//  information storage or retrieval system, without permission in writing
//  from RESON Inc.
//
//  Filename:   EdgeTechFSDW.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    Defines the initialization routines for the DLL and
//              export entry points for using the EdgeTech FS-DW server.
//
//  Notes:      1)  If this DLL is dynamically linked against the MFC
//                  DLLs, any functions exported from this DLL which
//                  call into MFC must have the AFX_MANAGE_STATE macro
//                  added at the very beginning of the function.
//                  The macro ManageDllState_m() is defined for this purpose (see below).
//
//                  See MFC Technical Notes 33 and 58 for additional info.
//

#include "StdAfx.h"
#include "EdgeTechFSDW.h"
#include "EdgeTechHeaders.h"

#include "..\..\Include\Exports.h"                                      // 6046 interface entry point definitions.
#include "..\..\Include\SurveyEventTypes.h"                             // Alarms and associated types.

#include "..\..\..\Utils\NetUtils\CriticalParameterTable.h"             // Critical parameter table definition.

#include <atlbase.h>

#pragma warning( push, 3 )                                              // Microsoft's STL is dirty so suppress necessary warnings.
#pragma warning( disable : 4018 )
#include <vector>
#include <algorithm>
#pragma warning( default : 4018 )
#pragma warning( pop )

#ifdef _DEBUG
#pragma comment( lib, "..\\..\\..\\Utils\\NetUtils\\Debug\\NetUtils.lib" )
#else
#pragma comment( lib, "..\\..\\..\\Utils\\NetUtils\\Release\\NetUtils.lib" )
#endif

namespace                       // Begin unnamed namespace.
{

    static LPCTSTR lpsz6046EdgeTechFSDWRegistryKey_c             = _T( ".DEFAULT\\SOFTWARE\\RESON\\6046\\EdgeTechFSDW" );
    static LPCTSTR lpsz6046EdgeTechFSDWAddressRegistryKey_c      = _T( "Address" );
    static LPCTSTR lpsz6046EdgeTechFSDWCommandRegistryKey_c      = _T( "Command" );
    static LPCTSTR lpsz6046EdgeTechFSDWDataRegistryKey_c         = _T( "Data" );
    static LPCTSTR lpsz6046EdgeTechFSDWAutoShutdownRegistryKey_c = _T( "AutoShutdown" );

    static LPCTSTR lpszDefaultSonarAddress_c                     = _T( "127.0.0.1" );

}                               // End unnamed namespace.

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CEdgeTechFSDWApp

BEGIN_MESSAGE_MAP(CEdgeTechFSDWApp, CWinApp)
    //{{AFX_MSG_MAP(CEdgeTechFSDWApp)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEdgeTechFSDWApp construction

CEdgeTechFSDWApp::CEdgeTechFSDWApp( void )

                 :CSensorInterface()
{
    m_bTerminationPending           = false;
                                    
    m_ulCommandPort                 = 1700UL;
    m_ulDataPort                    = 1701UL;
    m_ulAutoShutdown                = 0UL;
    m_pSentinel                     = NULL;
}

CEdgeTechFSDWApp::~CEdgeTechFSDWApp( void )
{
    __TRY
    {
        if ( m_pSentinel != NULL )
        {
            delete m_pSentinel;
            m_pSentinel = NULL;
        }
    }
    __FINALLY
    {
        ASSERT( m_pSentinel == NULL );

        m_pSentinel                 = NULL;
        m_ulCommandPort             = 1700UL;
        m_ulDataPort                = 1701UL;
        m_ulAutoShutdown            = 0UL;
        m_bTerminationPending       = false;

    }
    __ENDFINALLY
}

///////////////////////////////////////////////////////////////////////////////
// The one and only CEdgeTechFSDWApp object.

CEdgeTechFSDWApp theApp;

///////////////////////////////////////////////////////////////////////////////
// CEdgeTechFSDWApp initialization

BOOL CEdgeTechFSDWApp::InitInstance( void )
{
    // First, Initialize windows sockets.

    if ( ! AfxSocketInit() )
    {
        TRACE( _T( "CEdgeTechFSDWApp::InitInstance(), Windows sockets initialization failure\n" ) );
        return FALSE;
    }

    SonarSettingsFromRegistry();

    // All other significant initialization should go in here.

    return TRUE;
}

int CEdgeTechFSDWApp::ExitInstance( void )
{
    // Place significant clean up here.

    return CWinApp::ExitInstance();
}

CSensorList & CEdgeTechFSDWApp::SensorList( void ) const
{
    return m_SensorList;
}

CEdgeTechFSDWApp * CEdgeTechFSDWApp::This( void )
{
    return ( static_cast<CEdgeTechFSDWApp *>( CSensorInterface::This() ) );
}

void CEdgeTechFSDWApp::SonarConnectionSettings( CString         &rsSonarAddress,
                                                unsigned long   &rulCommandPort,
                                                unsigned long   &rulDataPort )
{
    rsSonarAddress = m_sSonarAddress;
    rulCommandPort = m_ulCommandPort;
    rulDataPort    = m_ulDataPort;
}

bool CEdgeTechFSDWApp::SonarAutoShutdown( void )
{
    return ( m_ulAutoShutdown != 0UL );
}

void CEdgeTechFSDWApp::SonarSettingsFromRegistry( void )
{
    try
    {
        if ( ! m_bRetrievedSonarSettings )
        {
            CRegKey Key;

            Key.Attach( HKEY_USERS );

            if ( Key.Open( HKEY_USERS, lpsz6046EdgeTechFSDWRegistryKey_c ) == ERROR_SUCCESS )
            {
                LONG lResult;

                DWORD dwLength = _MAX_PATH;
                lResult = Key.QueryValue( m_sSonarAddress.GetBuffer( _MAX_PATH ), lpsz6046EdgeTechFSDWAddressRegistryKey_c, &dwLength );
                m_sSonarAddress.ReleaseBuffer();
                ASSERT( lResult == ERROR_SUCCESS );

                lResult = Key.QueryValue( m_ulCommandPort, lpsz6046EdgeTechFSDWCommandRegistryKey_c );
                ASSERT( lResult == ERROR_SUCCESS );

                lResult = Key.QueryValue( m_ulDataPort, lpsz6046EdgeTechFSDWDataRegistryKey_c );
                ASSERT( lResult == ERROR_SUCCESS );

                lResult = Key.QueryValue( m_ulAutoShutdown, lpsz6046EdgeTechFSDWAutoShutdownRegistryKey_c );
                ASSERT( lResult == ERROR_SUCCESS );

                Key.Close();
            }

            if ( m_sSonarAddress.IsEmpty() )
            {
                m_sSonarAddress = lpszDefaultSonarAddress_c;
                TRACE( _T( "CEdgeTechFSDWApp::SonarSettingsFromRegistry(), Can't retrieve sonar address from registry... defaulting to %s\n" ), lpszDefaultSonarAddress_c );
            }

            m_bRetrievedSonarSettings = true;
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CEdgeTechFSDWApp::SonarSettingsFromRegistry(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "CEdgeTechFSDWApp::SonarSettingsFromRegistry(), unspecified exception caught\n" ) );
    }
}

void CEdgeTechFSDWApp::HandleSubscribedCriticalParameterUpdate( const int                          &riSensorIndex,
                                                                const unsigned long                &rulTableSize,
                                                                const CRITICALPARAMENTRY * const    psTable )
{
    // Virtual override from base class CEdgeTechFSDWApp. NB: this member represents a callback that will be invoked
    // from a thread running within the 6046 host and therefore it needs to appropriately synchronize access to shared 
    // resources.

    try
    {
        if ( ( rulTableSize == 0UL ) || ( riSensorIndex < 0 ) || ( psTable == NULL ) )
        {
            ThrowMessage_m( "Invalid parameter" );
        }

        m_Critical.Enter();

        try
        {
            CSensor *pSensor = m_SensorList.GetSensor( riSensorIndex );

            if ( pSensor != NULL )
            {
                for ( unsigned long ulEntry = 0UL; ulEntry < rulTableSize; ulEntry++ )
                {
                    if ( psTable[ ulEntry ].m_ulParameterId == static_cast<unsigned long>( criticalParameterPulseRepPeriod0 + riSensorIndex ) )
                    {
#if 1
#pragma CompileMessage_m( "TODO: UPDATE SENSOR RANGE/DURATION BASED ON EXTERNAL TRIGGER PULSE REP PERIOD SET... TBD" )
#else
                        if ( ! pSensor->TriggerPeriodUpdate( static_cast<float>( psTable[ ulEntry ].m_dValue ) ) )
                        {
                            TRACE( _T( "[CEdgeTechFSDWApp DLL] CEdgeTechFSDWApp::HandleSubscribedCriticalParameterUpdate(), pSensor->TriggerPeriodUpdate() failed\n" ) );
                        }
#endif
                        break;
                    }
                }
            }
        }
        catch ( ... )
        {
            TRACE( _T( "[CEdgeTechFSDWApp DLL] CEdgeTechFSDWApp::HandleSubscribedCriticalParameterUpdate(), Unspecified exception caught\n" ) );
        }

        m_Critical.Leave();
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "[CEdgeTechFSDWApp DLL] CEdgeTechFSDWApp::HandleSubscribedCriticalParameterUpdate(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "[CEdgeTechFSDWApp DLL] CEdgeTechFSDWApp::HandleSubscribedCriticalParameterUpdate(), Unspecified exception caught.\n" ) );
    }
}

bool CEdgeTechFSDWApp::IsSonarAvailable( const CString &rsSonarAddress, const unsigned long &rulCommandPort )
{
    bool bSonarAvailable = false;

    if ( SensorRefCount() > 0 )
    {
        CSensorList & rSensorList = SensorList();
        bSonarAvailable = rSensorList.IsHealthy();
    }
    else
    {
        CAsyncSocket *pSocket = NULL;

        try
        {
            pSocket = new CAsyncSocket;

            if ( pSocket != NULL )
            {
                BOOL  bDontLinger   = TRUE;
                DWORD dwNonBlocking = 0UL;
                long  lEventMask    = 0L;     // FD_CONNECT | FD_CLOSE

                if ( ! pSocket->Create( 0, SOCK_STREAM, lEventMask ) )
                {
                    ThrowMessage_m( "pSocket->Create() failed" );
                }

                // Here we set the socket to blocking mode. Note IOCtl alone may not be sufficient so issue 
                // an AsyncSelect(0) first.

                if ( ! pSocket->AsyncSelect( 0 ) )
                {
                    ThrowMessage_m( "pSocket->AsyncSelect() failed" );
                }

                if ( ! pSocket->IOCtl( FIONBIO, &dwNonBlocking ) )
                {
                    ThrowMessage_m( "pSocket->IOCtl failed" );
                }

                // Set non-lingering mode so that when we do a close on the socket, all the blocking calls
                // terminate. This is important so that our read thread operates and terminates correctly.

                if ( ! pSocket->SetSockOpt( SO_DONTLINGER, &bDontLinger, sizeof( bDontLinger ) ) )
                {
                    ThrowMessage_m( "pSocket->SetSockOpt() failed" );
                }

                // Now, try and connect this client socket to our listening server.

                unsigned int uiPort = rulCommandPort;

                if ( ! pSocket->Connect( rsSonarAddress, uiPort ) )
                {
                    ThrowMessage_m( "pSocket->Connect() failed" );
                }

                bSonarAvailable = true;

                pSocket->Close();
            }
        }
        catch ( LPCTSTR lpszMessage )
        {
            bSonarAvailable = false;
            TRACE( _T( "CEdgeTechFSDWApp::IsSonarAvailable(), %s. ErrorCode: %d\n" ), lpszMessage, pSocket->GetLastError() );
        }
        catch ( ... )
        {
            bSonarAvailable = false;
            TRACE( _T( "CEdgeTechFSDWApp::IsSonarAvailable(), Unspecified exception caught.\n" ) );
        }

        if ( pSocket != NULL )
        {
            delete pSocket;
            pSocket = NULL;
        }
    }

    return bSonarAvailable;
}

void CEdgeTechFSDWApp::Sentinel( const bool &rbStart )
{
    if ( rbStart )
    {
        ASSERT( m_pSentinel == NULL );

        if ( m_pSentinel == NULL )
        {
            __TRY
            {
                m_pSentinel = new CWatchSentinel;
            }
            __FINALLY
            {
                ASSERT( m_pSentinel != NULL );
            }
            __ENDFINALLY

            if ( ( m_pSentinel == NULL ) || ( ! m_pSentinel->SetHealthCheckCallback( CheckSensorStatus, this ) ) )
            {
                LogEvent( eventLogDiagnostic,
                            iUnspecified_c,
                                iUnspecified_c,
                                    "[EdgeTech FS-DW] Can not start the sonar watchdog sentinel." );
            }
        }
    }
    else if ( m_pSentinel != NULL )
    {
        delete m_pSentinel;
        m_pSentinel = NULL;
    }
}

void CEdgeTechFSDWApp::CheckSensorStatus( void *pvParam, const int &riSensorIndex )
{
    CEdgeTechFSDWApp *pthis = static_cast<CEdgeTechFSDWApp *>( pvParam );
    ASSERT( pthis != NULL );

    if ( pthis != NULL )
    {
        pthis->SensorCheck( riSensorIndex );
    }
}

void CEdgeTechFSDWApp::SensorCheck( const int &riSensorIndex )
{
    // This is a method may be executed within the context of arbitrary threads and needs to be thread save accordingly.
    // For example, the watchdog thread or socket thread on termination may invoke this method.

    //TRACE( _T( "CEdgeTechFSDWApp::SensorCheck(), About to check sensor health. Index:%d.\n" ), riSensorIndex );

    try
    {
        CSensorList &rSensorList = SensorList();

        if ( rSensorList.NumberOfSensors() > 0 )
        {
            typedef std::vector<int> List_t;
            typedef List_t::iterator ListIterator_t;

            List_t                   IndexList = rSensorList.SensorIndicies();

            if ( riSensorIndex >= 0 )
            {
                if ( std::find( IndexList.begin(), IndexList.end(), riSensorIndex ) != IndexList.end() )
                {
                    HandleSensorAlarm( riSensorIndex, rSensorList.IsHealthy() );
                }
            }
            else if ( riSensorIndex == -1 )
            {
                ListIterator_t pIndex   = IndexList.begin();
                bool           bHealthy = rSensorList.IsHealthy();

                while ( pIndex != IndexList.end() )
                {
                    HandleSensorAlarm( *pIndex, bHealthy );
                    pIndex++;
                }
            }
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CEdgeTechFSDWApp::SensorCheck(), Unspecified exception caught.\n" ) );
    }
}

void CEdgeTechFSDWApp::HandleSensorAlarm( const int &riSensorIndex, const bool &rbHealthy )
{
    if ( rbHealthy )
    {
        LogEvent(   eventLogAlarm,
                        eventActionCleared,
                            alarmIdSensorNotResponding,
                                "%d, Sensor not responding", riSensorIndex );

    }
    else
    {
        LogEvent(   eventLogAlarm,
                        eventActionAlarmSet,
                            alarmIdSensorNotResponding,
                                "%d, Sensor not responding", riSensorIndex );

    }
}

void CEdgeTechFSDWApp::SocketDisconnect( void )
{
    // A socket connection has just terminated so we check to see if its unexpected. If so, try to check 
    // the sensor's health and handle alarms as appropriate.

    // Caution: This method is invoked within the context of the (about to terminate) socket thread 
    //          so care is needed to ensure we are thread safe. Also the invoking thread won't be around 
    //          much longer.

    if ( ! m_bTerminationPending )
    {
        TRACE( _T( "CEdgeTechFSDWApp::SocketDisconnect(), A socket connection has just terminated unexpectedly... checking sensors !\n" ) );

        SensorCheck();
    }
}

void  CEdgeTechFSDWApp::TerminationPending( const bool &rbTerminationPending )
{
    m_bTerminationPending = rbTerminationPending;
}

///////////////////////////////////////////////////////////////////////////////
// Exported interface functions for 6046 main software.

extern "C" __declspec( dllexport )
int WINAPI Startup( SENSORINFO     *pSensorInfo,
                    DWORD           dwThreadId,
                    UINT            uiDataReadyMessageId,
                    UINT            uiReplyReadyMessageId,
                    void           *vpfnLog,
                    void           *vpCriticalParameters )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;      // Assume failure for now.

    try
    {
        bool                bFirstConnected = false;
        unsigned long       ulCommandPort   = 0UL;
        unsigned long       ulDataPort      = 0UL;

        CString             sSonarAddress;
        CSensor            *pSensor         = NULL;
        CEdgeTechFSDWApp   *pApp            = GetApp();

        // Get our application object pointer.

        if ( pApp == NULL )
        {
            ThrowMessage_m( "Can't acquire DLL APP object" );
        }

        if ( pApp->SensorRefCount() == 0 )
        {
            pApp->TerminationPending( false );
            pApp->EventLog( vpfnLog );
        }

        // Get the sonar connection information.

        pApp->SonarConnectionSettings( sSonarAddress, ulCommandPort, ulDataPort );

        // Try to establish whether the sonar is available for connection. If it is not, bail out and
        // report the failure ASAP -- before we add the sensor to the list.

        if ( ! pApp->IsSonarAvailable( sSonarAddress, ulCommandPort ) )
        {
            ThrowMessage_m( "Sonar is unavailable for connection" );
        }

        // Ok, sensor is available so go ahead and try to connect, initialize etc.

        pApp->AddSensorRef();

        if ( pApp->SensorRefCount() == 1 )
        {
            //pApp->EventLog( vpfnLog );
#if 1
#pragma CompileMessage_m( "TODO: RE-ENABLE CRITICAL PARAMETER HANDLING ONCE MECHAMISM IS COMPLETED" )
            DBG_UNREFERENCED_PARAMETER( vpCriticalParameters );
#else
            pApp->CriticalParameterTable( vpCriticalParameters );
#endif

            //pApp->Sentinel( true );
        }

        CSensorList & rSensorList = pApp->SensorList();

        if ( ! rSensorList.ConnectToSonar( (char *) (LPCTSTR) sSonarAddress, ulCommandPort, ulDataPort, ulMaxEdgeTechDynamicDataSize_c, bFirstConnected ) )
        {
            ThrowMessage_m( "Can't connect to sonar" );
        }

        // Initially, sensors within a module were to determine their own index. Now, however,
        // the sensor index is determined by the 6046 so that it maybe determined across the
        // entier sensor spectrum.

        ASSERT( pSensorInfo->m_iSensorIndex >= 0 );

        int iSensorIndex = pSensorInfo->m_iSensorIndex;

        pSensor = rSensorList.AddSensor( &iSensorIndex );

        // Note: sensor id is a 6046 specific code for each sensor; see ESENSORID of processinf.h whereas
        // deviceIdedgeTechFSDW... is the 7k device id per the 7k ICD. They shouldn't be confused. The latter
        // is used to form the 7k File Header record (#7200) during logging.

        //pSensorInfo->iSensorIndex           = iSensorIndex;               // Sensor index is now assigned by the PLC; it is no longer determined by the sensor.
        pSensorInfo->m_iPriority            = 1;
        pSensorInfo->m_iFilter              = 0;
        pSensorInfo->m_iSensorId            = sensorIdEdgeTechFSDW;         // 6046 specific index for this device type.
        pSensorInfo->m_iMediaType           = mediaTypeNIC;
        pSensorInfo->m_ulDeviceId           = 0UL;                          // 7k device id unknown for now.
        pSensorInfo->m_iAccepts7kRecords    = 0;                            // Sensors of FS-DW are note capable of accepting routed 7k records -- at least not for now.

        // Specific sensor type is not known until the subsystem is started.

        pSensorInfo->m_iSensorType  = -1;
        strcpy( pSensorInfo->m_szName, "Unknown" );

        // Note: iSubsytemId defines the subsytem type. After the sensor is started, we retrieve the 
        // type and populate the remaining fields of the pSensorInfo struct.

        pApp->LogEvent( eventLogAdvisory,
                            iUnspecified_c,
                                iUnspecified_c,
                                    "[EdgeTech FS-DW] Subsystem startup (ID: %d, Index: %d, AutoStart: %d)",
                                        pSensorInfo->m_iSubsystemId,
                                            iSensorIndex,
                                                pSensorInfo->m_iActivateOnStart );

        if ( ! pSensor->Startup( pSensorInfo->m_iSubsystemId, iSensorIndex, dwThreadId, uiDataReadyMessageId, uiReplyReadyMessageId, pSensorInfo->m_iActivateOnStart ) )
        {
            pApp->SensorList().RemoveSensor( iSensorIndex );
            ThrowMessage_m( "Sensor startup failure" );
        }

        // If it's truely the first connected subsystem then use it to set the EdgeTech system's time.

        if ( bFirstConnected && ( pSensor != NULL ) )
        {
            if ( ! pSensor->SetSystemTime() )
            {
                pApp->LogEvent( eventLogAlarm,
                                    eventActionAlarmSet,
                                        alarmIdSensorFailedToSetTime,
                                            "%d, Sensor failed to set specified time", iSensorIndex );
            }
        }

        // Now retrieve the sensor type (SBP, SSSLF, SSSHF) and set the 
        // sensor type and name fields.

        switch ( pSensor->SubsystemType() )
        {
            case CSubsystem::subsystemSBP:

                pSensorInfo->m_iSensorType = sensorTypeSBP;
                pSensorInfo->m_ulDeviceId  = deviceIdEdgeTechFSDWSBP;           // 7k device id.
                strcpy( pSensorInfo->m_szName, "EdgeTech SBP" );
                break;

            case CSubsystem::subsystemSSSLF:

                pSensorInfo->m_iSensorType = sensorTypeSSSLF;
                pSensorInfo->m_ulDeviceId  = deviceIdEdgeTechFSDWLFSSS;         // 7k device id.
                strcpy( pSensorInfo->m_szName, "EdgeTech SSSLF" );
                break;

            case CSubsystem::subsystemSSSHF:

                pSensorInfo->m_iSensorType = sensorTypeSSSHF;
                pSensorInfo->m_ulDeviceId  = deviceIdEdgeTechFSDWHFSSS;         // 7k device id.
                strcpy( pSensorInfo->m_szName, "EdgeTech SSSHF" );
                break;

            case CSubsystem::subsystemUnknown:
            default:

                ThrowMessage_m( "Unknown subsystem type (based on subsystem id)" );
                break;
        }

        if ( pApp->SonarAutoShutdown() )
        {
            pSensor->EnableAutoShutdown( true );
        }

#if 1
#pragma CompileMessage_m( "TODO: SUBSCRIBE TO APPROPRIATE CRITICAL PARAMETER ONCE SCHEME FINALIZED ... TBD" )
#else
        CCriticalParameterTable::ParameterList_t CriticalParameters;

        CriticalParameters.push_back( criticalParameterPulseRepPeriod0 + riSensorIndex );

        if ( ! pApp->SubscribeToCriticalParameters( iSensorIndex, CriticalParameters ) )
        {
            ThrowMessage_m( "Can't subscribe for critical update" );
        }
#endif

        iSuccess = ERROR_CODE_SUCCESS;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, Startup() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, Startup(), unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI Shutdown( int iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;      // Assume failure for now.

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        pApp->LogEvent( eventLogAdvisory,
                            iUnspecified_c,
                                iUnspecified_c,
                                    "[EdgeTech FS-DW] Shutdown (Index: %d).", iSensorIndex );

        CSensorList &rSensorList = pApp->SensorList();
        CSensor     *pSensor     = rSensorList.GetSensor( iSensorIndex );

        // Go ahead and shutdown. Here we assume success then preserve a fail code for return
        // if one is detected. We don't through an eception since we want the successive steps
        // to follow. Eg. remove and disconect even if a sensor shutdown has failed.

        iSuccess = ERROR_CODE_SUCCESS;

        if ( pSensor != NULL )
        {
#if 1
#pragma CompileMessage_m( "TODO: UNSUBSCRIBE FROM APPROPRIATE CRITICAL PARAMETER ONCE SCHEME FINALIZED ... TBD" )
#else
            if ( pApp->UnsubscribeFromCriticalParameters( iSensorIndex ) )
            {
                iSuccess = ERROR_CODE_FAILURE;
            }
#endif

            pApp->ReleaseSensorRef();

            int iReferenceCount = pApp->SensorRefCount();

            if ( iReferenceCount == 0 )
            {
                pApp->TerminationPending( true );
                //pApp->Sentinel( false );
            }

            if ( ! pSensor->Shutdown( iSensorIndex ) )
            {
                iSuccess = ERROR_CODE_FAILURE;
            }

            if ( ! rSensorList.RemoveSensor( iSensorIndex ) )
            {
                iSuccess = ERROR_CODE_FAILURE;
            }

            if ( ! rSensorList.DisconnectFromSonar( false ) )
            {
                iSuccess = ERROR_CODE_FAILURE;
            }

            if ( iReferenceCount == 0 )
            {
                pApp->EventLog( NULL );
            }
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, Startup() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "Shutdown(), CEdgeTechFSDW DLL - unspecified exception\n" ) );
    }
  
    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI Identify( SENSORINFO *pSensorInfo, int iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;      // Assume failure for now.

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        CSensor *pSensor = rSensorList.GetSensor( iSensorIndex );

        if ( pSensor == NULL )
        {
            ThrowMessage_m( "pSensor is NULL" );
        }

        iSuccess = ( pSensor->Identify( pSensorInfo ) ) ?  ERROR_CODE_SUCCESS : ERROR_CODE_FAILURE;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, Identify() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, Identify() - unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI Load(    BYTE           *pbyData,
                    unsigned long  *pulSize,
                    unsigned long  *pulTimestamp,
                    int             iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;

    // NB: Load() is redundant as sensors now load their data directly into the PLC's
    // shared memory (CSubsystem::m_SensorDataPool). CSubsystem has been changed accordingly.

    UNREFERENCED_PARAMETER( pbyData );
    UNREFERENCED_PARAMETER( pulSize );
    UNREFERENCED_PARAMETER( pulTimestamp );
    UNREFERENCED_PARAMETER( iSensorIndex );

//    try
//    {
//        CEdgeTechFSDWApp *pApp = GetApp();
//        ASSERT( pApp != NULL );
//
//        if ( pApp == NULL )
//        {
//            ThrowMessage_m( "pApp is NULL" );
//        }
//
//        CSensorList & rSensorList = pApp->SensorList();
//
//        CSensor *pSensor = rSensorList.GetSensor( iSensorIndex );
//
//        if ( pSensor == NULL )
//        {
//            ThrowMessage_m( "pSensor is NULL" );
//        }
//
//        // Now load the data. If there are no records left to read then we should return
//        // a fail code. This doesn't really mean failure, rather, no data is pending.
//
//        if ( pSensor->Load( pbyData, pulSize, pulTimestamp ) )
//        {
//            iSuccess = ERROR_CODE_SUCCESS;
//        }
//        else
//        {
//            iSuccess = ERROR_CODE_FAILURE;
//        }
//    }
//    catch ( LPCTSTR lpszMessage )
//    {
//        iSuccess = ERROR_CODE_FAILURE;
//        TRACE( _T( "CEdgeTechFSDW DLL, Load() failed: %s\n" ), lpszMessage );
//    }
//    catch ( ... )
//    {
//        iSuccess = ERROR_CODE_FAILURE;
//        TRACE( _T( "CEdgeTechFSDW DLL, Load() - unspecified exception\n" ) );
//    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
double WINAPI GetModuleVersion( void )
{
    ManageDllState_m();

    double dVersion = 0.0;
    CEdgeTechFSDWApp *pApp = GetApp();
    ASSERT( pApp != NULL );

    if ( pApp != NULL )
    {
        dVersion = pApp->Version();
    }

    return dVersion;
}

extern "C" __declspec( dllexport )
int WINAPI SendCommand( int     iSensorIndex,
                        int     iClientIndex,
                        char   *pszCommand )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;      // Assume failure for now.

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        CSensor *pSensor = rSensorList.GetSensor( iSensorIndex );

        if ( pSensor == NULL )
        {
            ThrowMessage_m( "pSensor is NULL" );
        }

        if ( ! pSensor->SendCommand( iClientIndex, pszCommand ) )
        {
            ThrowMessage_m( "pSensor->SendCommand() failed" );
        }

        iSuccess = ERROR_CODE_SUCCESS;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, SendCommand() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, SendCommand() - unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI RetrieveStatus(  BYTE           *pbyData,
                            unsigned long  *pulSize,
                            unsigned long  *pulTimestamp,
                            int            *piClientIndex,
                            int             iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        CSensor *pSensor = rSensorList.GetSensor( iSensorIndex );

        if ( pSensor == NULL )
        {
            ThrowMessage_m( "pSensor is NULL" );
        }

        // Here, the "fail" code simply means no more queued data is available.

        iSuccess = ( pSensor->RetrieveStatus( pbyData, pulSize, pulTimestamp, piClientIndex ) ) ? ERROR_CODE_SUCCESS : ERROR_CODE_FAILURE;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, RetrieveStatus() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, RetrieveStatus() - unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI RouteMessage(    BYTE           *pbyMessage,
                            unsigned long   ulSize,
                            unsigned long   ulTimeStamp,
                            int             iSocketIndex,
                            int             iSensorIndex )
{
    ManageDllState_m();

    UNREFERENCED_PARAMETER( iSocketIndex );

    int iSuccess = ERROR_CODE_FAILURE;

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        CSensor *pSensor = rSensorList.GetSensor( iSensorIndex );

        if ( pSensor == NULL )
        {
            ThrowMessage_m( "pSensor is NULL" );
        }

        iSuccess = ( pSensor->RouteMessage( pbyMessage, ulSize, ulTimeStamp ) ) ? ERROR_CODE_SUCCESS : ERROR_CODE_FAILURE;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, RouteMessage() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, RouteMessage() - unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI ResetSensor( int iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;

    UNREFERENCED_PARAMETER( iSensorIndex );

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        iSuccess = ( rSensorList.Reset() ) ? ERROR_CODE_SUCCESS : ERROR_CODE_FAILURE;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, ResetSensor() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, ResetSensor() - unspecified exception\n" ) );
    }

    return iSuccess;
}

extern "C" __declspec( dllexport )
int WINAPI IsSensorHealthy( int iSensorIndex )
{
    ManageDllState_m();

    int iSuccess = ERROR_CODE_FAILURE;

    UNREFERENCED_PARAMETER( iSensorIndex );

    try
    {
        CEdgeTechFSDWApp *pApp = GetApp();
        ASSERT( pApp != NULL );

        if ( pApp == NULL )
        {
            ThrowMessage_m( "pApp is NULL" );
        }

        CSensorList & rSensorList = pApp->SensorList();

        iSuccess = ( rSensorList.IsHealthy() ) ? ERROR_CODE_SUCCESS : ERROR_CODE_FAILURE;
    }
    catch ( LPCTSTR lpszMessage )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, IsSensorHealthy() failed: %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        iSuccess = ERROR_CODE_FAILURE;
        TRACE( _T( "CEdgeTechFSDW DLL, IsSensorHealthy() - unspecified exception\n" ) );
    }

    return iSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// File scope helpers.

CEdgeTechFSDWApp * GetApp( void )
{
    CEdgeTechFSDWApp * pApp = CEdgeTechFSDWApp::This();

    ASSERT( pApp != NULL );
    
    return pApp;
}

