//
//  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:   Subsystem.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    Implementes the class CSubsystem... the work horse for the EdgeTech subsystem and 
//              provides the main interface to an EdgeTech FS-DW.
//
//  Notes:      1)  The stratagy for sending commands is to issue the command and then
//                  query the subsystem for the same setting thus maintaining the internal
//                  state of this object.
//
//              2)  Remember: Perfer et obdura; dolor hic tibi proderit olim
//                            (Be patient and tough; one day this pain will be useful to you).
//

#include "StdAfx.h"
#include "Subsystem.h"
#include "CommandProcessor.h"
#include "DataProcessor.h"

#include "EdgeTechSystemTime.h"

#include "..\..\..\Utils\NetUtils\7kProtocol.h"
#include "..\..\..\Utils\NetUtils\SystemTime.h"
#include "..\..\..\Utils\NetUtils\7kStatus.h"

#include <atlbase.h>
#include <math.h>
#include <string.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#define TRIGGERMODE_INTERNAL_COUPLED        0
#define TRIGGERMODE_EXTERNAL_UNCOUPLED      1

#pragma CompileMessage_m( "FOR PHASE 2, 7k RELEASE: USE EXTERNAL UNCOUPLED MODE." )

#define TRIGGERMODE     TRIGGERMODE_INTERNAL_COUPLED        // For phase 2, use external uncoupled mode.

// - Make reply mechanism (via client id) more general. Perhaps a queuing mechanism whereby the request is queued
//      along with the client index an when async data is ready we use that client info and remove from queue.
//      That is, replace the m_iReplyClientIndex driven mechanism that only handles one nofiticaion type.
// - Is alive monitoring and notification.
// - Change from internal (coupled) triggering to externally (coupled) trigger and make
//      it dynamically configurable.

///////////////////////////////////////////////////////////////////////////////
// Constants, macros and template methods.

namespace                                                   // Begin unnamed namespace.
{
    LPCTSTR             lpszEdgeTechFSDWKey_c   = _T( ".DEFAULT\\SOFTWARE\\RESON\\6046\\EdgeTechFSDW" );

    const unsigned long ulMaxPingSize_c         =   sizeof( RECORDHEADER7K ) +                                                  // 7k structure etc.
                                                        sizeof( SIDESCANRECORDTYPEHEADER ) +
                                                            ulMaxChannels_c * ( sizeof( CHANNELINFO ) + ulMaxBytesPerChannel_c ) +
                                                                sizeof( unsigned long );


}                                                           // End unnamed namespace.

///////////////////////////////////////////////////////////////////////////////
// Static members initializaton.

bool                                CSubsystem::m_bIdTableLoaded                         = false;
bool                                CSubsystem::m_bAutoShutdownOS                        = false;
int                                 CSubsystem::m_iRefCount                              = 0;

CSubsystem::SUBSYSTEMIDTABLEENTRY   CSubsystem::m_sSubsystemIdTable[ maxSubsystemTypes ] =
{
    { subsystemSBP,     "SBP",    0x00 },
    { subsystemSSSLF,   "SSSLF",  0x20 },
    { subsystemSSSHF,   "SSSHF",  0x21 }
};

//////////////////////////////////////////////////////////////////////
// CSubsytem class implementation starting with public methods.

CSubsystem::CSubsystem( CCommandProcessor *pSonarCommandProcessor,
                        CDataProcessor    *pSonarDataProcessor )

           :m_iInvalidSubsystemId           ( -1                ),
            m_dwInvalidThreadId             ( (DWORD) -1        ),
            m_ulMaxPingSize                 ( ulMaxPingSize_c   ),
            m_ulMaxFishTypes                ( ulMaxFishTypes_c  ),
            m_ulMaxPulseFiles               ( ulMaxPulseFiles_c ),
            m_fVersionSupportingOSShutdown  ( 8.1f              )
{
    m_iRefCount++;

    m_bInitialized                  = false;
    m_bActivateOnStart              = false;

    m_pSonarCommandProcessor        = pSonarCommandProcessor;
    m_pSonarDataProcessor           = pSonarDataProcessor;

    ASSERT( m_pSonarCommandProcessor != NULL );
    ASSERT( m_pSonarDataProcessor    != NULL );

    m_iSubsystemId                  = m_iInvalidSubsystemId;
    m_eSubsystemType                = subsystemUnknown;
    m_iDataChannels                 = 0;

    m_dwThreadId                    = m_dwInvalidThreadId;
    m_uiDataReadyMessageId          = 0U;
    m_uiReplyReadyMessageId         = 0U;

    m_bPulseFileListRecieved        = false;
    m_bPulseFileListReplyRequested  = false;
    m_iReplyClientIndex             = -1;
    m_fSoftwareVersion              = -1.0f;

    m_sStatus.Reset();
    m_Config.SetDefault();

    // Setup storage for the formatted 7k record.

    m_pby7kRecord = new BYTE [ m_ulMaxPingSize ];
    ASSERT( m_pby7kRecord != NULL );

    if ( m_pby7kRecord == NULL )
    {
        TRACE( _T( "CSubsystem::CSubsystem(), Can't allocate 7k record buffer...!\n" ) );
        return;
    }

    // Ok, now we're done successfully.

    m_bInitialized  = true;
}

CSubsystem::~CSubsystem( void )
{
    __TRY
    {
        m_StateDataGuard.Enter();

        m_iSubsystemId          = m_iInvalidSubsystemId;
        m_iDataChannels         = 0;
        m_uiDataReadyMessageId  = 0U;
        m_uiReplyReadyMessageId = 0U;

        m_eSubsystemType        = subsystemUnknown;
        m_dwThreadId            = m_dwInvalidThreadId;

        if ( m_pby7kRecord != NULL )
        {
            delete [] m_pby7kRecord;
            m_pby7kRecord = NULL;
        }
    }
    __FINALLY
    {
        m_StateDataGuard.Leave();
    }
    __ENDFINALLY
}

bool CSubsystem::IsInitialized( void ) const
{
    m_StateDataGuard.Enter();
    bool bInitialized = m_bInitialized;
    m_StateDataGuard.Leave();

    return bInitialized;
}

void CSubsystem::SubsystemId( const int &riId )
{
    m_StateDataGuard.Enter();
    m_iSubsystemId = riId;
    m_StateDataGuard.Leave();
}

int CSubsystem::SubsystemId( void ) const
{
    m_StateDataGuard.Enter();
    int iSubsystemId = m_iSubsystemId;
    m_StateDataGuard.Leave();

    return iSubsystemId;
}

CSubsystem::ESUBSYSTEMTYPE CSubsystem::SubsystemType( void ) const
{
    m_StateDataGuard.Enter();
    ESUBSYSTEMTYPE eSubsystemType = m_eSubsystemType;
    m_StateDataGuard.Leave();

    return eSubsystemType;
}

void CSubsystem::SubsystemType( const ESUBSYSTEMTYPE &reSubsystemType )
{
    m_StateDataGuard.Enter();
    m_eSubsystemType = reSubsystemType;
    m_StateDataGuard.Leave();
}

void CSubsystem::EnableAutoShutdown( const bool &rbEnable )
{
    m_bAutoShutdownOS = rbEnable;
}

///////////////////////////////////////////////////////////////////////////////
// Data and command handlers - called back from socket message processor threads.

bool CSubsystem::HandleSonarCommand(  const CEdgeTechMessage  &rMessage,
                                      const unsigned long     &rulTimeStamp )
{
    // Sonar command channel data recieve handler for this subsystem.
    // Note: this routine is called by a thread in the context of the sonar command channel
    // so access this subsystem's state data in a thread safe manner.

    const unsigned short unMessage = rMessage.SonarMessage();

    TRACE( _T( "CSubsystem::HandleSonarCommand(), Message %u recieved @ %lu\n" ), unMessage, rulTimeStamp );

    ESTATUSUPDATE eStatus = statusUpdateNone;

    switch ( unMessage )
    {
        case SONAR_MESSAGE_NONE:
            
            // Null message - can be used to test communications.

            m_StateDataGuard.Enter();
            m_sStatus.m_lTestMessageCounter++;
            eStatus = statusUpdateMessageNone;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_SYSTEM_VERSION:

            // Get the software version string (SonarMessageStringType)
            // Note:  This is a system command, the subsystem and channel numbers
            // must be 0.  The version string consists of an alphabetic string
            // followed by a version number of the form N.M where N and M are both
            // integers.

            m_StateDataGuard.Enter();
            ::strncpy( &m_sStatus.m_szVersion[0], (char *) rMessage, ulMaxVersionStringLength_c -1 );
            eStatus = statusUpdateSystemVersion;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_OVERRIDE:

            // Override data lockout caused by suspected system failure in the
            // sonar system.  Once an overrride message is sent, the sonar system
            // will not stop the data flow because of potential data quality
            // problems.

            TRACE( _T( "CSubsystem::HandleSonarCommand(), SONAR_MESSAGE_OVERRIDE recieved\n" ) );
            break;

        case SONAR_MESSAGE_RUN_POST_DIAGNOSTICS:

            // Run all of the power on self test diagnostics.  This will normally
            // cause an audible chirp on the subbottom (if present) and side scan
            // low (if present) and other internal diagnostics.  This can either
            // cause OR CLEAR a POST error code.  If a post error code is detected
            // sonar data WILL NOT be returned to the topside unless an override
            // message is sent.  The subsystem and channel should be set to 0 for
            // this message.  The sonarCommand field in the header should be set to
            // SONAR_COMMAND_GET as this command returns the post status bit field
            // as the lsb 8 bits of its return value (SonarMessageLongType).  See
            // the SonarMessageStatusType and the serviceNeeded field for a list
            // of the possible return values.

            TRACE( _T( "CSubsystem::HandleSonarCommand(), SONAR_MESSAGE_RUN_POST_DIAGNOSTICS recieved\n" ) );
            break;

        case SONAR_MESSAGE_DATA_NETWORK_WINDOW:

            // Window for data transmission (SonarMessageWindowType)
            // Subsystem and channel must be valid.

            m_StateDataGuard.Enter();
            m_sStatus.m_sDataNetworkWindow = *((SonarMessageWindowType *) rMessage);
            eStatus = statusUpdateDataNetworkWindow;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_DATA_ACTIVE:

            // Activate / Deactivate return data type.  The data for the subsystem
            // / channel specified in the header is activated / deactivated
            // (0 - deactivate : 1 - activate)   (SonarMessageLongType)
            // Subsystem and channel must be valid.

            m_StateDataGuard.Enter();
            m_sStatus.m_bDataActive = static_cast<bool>( ((long) rMessage) != 0 );
            eStatus = statusUpdateDataActive;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PROCESSING_ENHANCE_WINDOW:

            // Window for processing optimization (SonarMessageWindowType)
            // Subsystem and channel must be valid.

            //m_StateDataGuard.Enter();
            //m_sEnhanceWindow = *((SonarMessageWindowType *) rMessage);
            //eStatus = statusUpdateEnhanceWindow;
            //m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PROCESSING_DIRECT_PATH:

            // Samples to ignore due to direct path on AGC, normalization
            // algorithms (SonarMessageLongType)
            // Subsystem and channel must be valid.

            m_StateDataGuard.Enter();
            m_sStatus.m_lDirectPathHoldoff = (long) rMessage;
            eStatus = statusUpdateProcessingDirectPath;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING:

            // Enable/disable ping: 0 => disable, 1=>enable, 2 => single ping only
            // (SonarMessageLongType)
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lPingEnable = (long) rMessage;
            eStatus = statusUpdatePing;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_GAIN:

            // 1000.0 * DAC gain to scale outgoing pulse by (SonarMessageLongType)
            // Subsystem and channel must be valid.

            m_StateDataGuard.Enter();
            m_sStatus.m_lPingGain = (long) rMessage;
            eStatus = statusUpdatePingGain;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_LIST:

            // A set message takes no parameters. A set message resets the list of
            // pulse records so that the next get message will return the first
            // record in the list.
            // A get message with a (SonarMessageLongType) parameter, returns up to
            // the specified number of pulse records as an array of
            // (SonarMessagePingType) values, up to a maximum of 30.
            // A get message can also be sent with no parameters, in this case it
            // returns a single (SonarMessagePingType) structure.
            // The pulse record following the last valid pulse record will have a
            // NULL pulse name field.
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();

            {
                SonarMessagePingType *psPingType  = (SonarMessagePingType *) rMessage;
                unsigned long         ulNumPulses = rMessage.MessageSize() / sizeof( SonarMessagePingType );

                for ( unsigned long ulPulse = 0UL; ulPulse < min( ulNumPulses, m_ulMaxPulseFiles ); ulPulse++ )
                {
                    m_sStatus.m_asPulseFile[ ulPulse ] = psPingType[ ulPulse ];
                }

                m_bPulseFileListRecieved = true;
                m_sStatus.m_ulNumPulses = ulNumPulses;
            }

            eStatus = statusUpdatePingList;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_SELECT:

            // Select an outgoing set of pulses, and matched filters.
            // (SonarMessageStringType)
            // Note:  This is a subsystem command, the channel number must be 0.

            {
                m_StateDataGuard.Enter();
                char *pszFileName = (char *) rMessage;
                TRACE( _T( "Selected ping was: %s\n" ), pszFileName );
                strncpy( &(m_sStatus.m_szSelectedPulse[ 0 ]), pszFileName, ulMaxVersionStringLength_c -1 );
                eStatus = statusUpdatePingSelect;
                m_StateDataGuard.Leave();
            }

            break;

        case SONAR_MESSAGE_PING_RATE:

            // Number of pings pers second required * 1000
            // (SonarMessageLongType)
            // Actual ping rate may be slightly lower (2048 sample granuality)
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lPingRate = (long) rMessage;
            eStatus = statusUpdatePingRate;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_TRIGGER:

            // Set trigger for internal(0), external(1), coupled(2), or gated(3)
            // (SonarMessageLongType).  Coupled mode causes a system to be triggered
            // by another one (eg Sidescan triggered by Subbottom)
            // See SONAR_MESSAGE_PING_COUPLING_PARAMETERS message.  In gated mode
            // the external trigger is used as a trigger inhibit, and the inhibit
            // time is based on the coupling parameter delay.
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lTriggerMode = (long) rMessage;
            eStatus = statusUpdatePingTrigger;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_DELAY:

            // Set delay for external trigger in ms (SonarMessageLongType).  This
            // message applies only to the soft trigger.  A soft trigger uses the
            // ethernet to transmit trigger requests to an underwater electronics
            // bottle and is not present in an FS-SB standard system.  See the
            // SONAR_MESSAGE_PING_COUPLING_PARAMETERS message for a hardwired
            // trigger delay.
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lExternalSoftTriggerDelay = (long) rMessage;
            eStatus = statusUpdatePingDelay;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_MAX_SAMPLES:

            // A get message takes a parameter that is 1000 * the ping rate in Hz.
            // (SonarMessageLongType) and returns a (SonarMessageLongType) with the
            // maximum number of samples that can be received for that ping rate.
            // A get message can also be sent with no parameters, in this case it
            // returns the maximum number of samples for the current ping rate.
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lMaxSamplesPossible  = (long) rMessage;
            eStatus = statusUpdatePingMaxSamples;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_RANGE:

            // Set the ping rate for sidescan systems.  Sets the ping rate  based
            // on the range in millimeters. (SonarMessageLongType)
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lRangeInMillimeters = (long) rMessage;
            eStatus = statusUpdatePingRange;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_COUPLING_PARAMETERS:

            // Set the coupling parameters for this system when in trigger mode
            // coupled, and the hardware trigger delay in external trigger modes.
            // (SonarMessageCouplingParametersType)
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_sCouplingParameters = *((SonarMessageCouplingParametersType *) rMessage);
            eStatus = statusUpdatePingCouplingParameters;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_PING_FISH_LIST:

            // Get the list of human readable strings and fish IDs.  Only a
            // SONAR_COMMAND_GET message is meaningful.  Returns an array of
            // (SonarMessageFishType) records.  The number of records can be
            // determined by the size of the return message.
            // This message should be used to get the available EdgeTech fish to
            // present to the end user.  The user should first select the fish
            // which is attached to the sonar processing system, and should then
            // select a pulse only from the subset of the selected fish.
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();

            {
                SonarMessageFishType *pFish = (SonarMessageFishType *) rMessage;
                unsigned long         ulNumFish = rMessage.MessageSize() / sizeof( SonarMessageFishType );
            
                for ( unsigned long ulFish = 0UL; ulFish < min( ulNumFish, m_ulMaxFishTypes ); ulFish++ )
                {
                    m_sStatus.m_asFishType[ ulFish ] = pFish[ ulFish ];
                }
            }
            
            eStatus = statusUpdatePingFishList;

            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_ADC_GAIN:

            // Set gain factor for ADC when AGC disabled (SonarMessageLongType)
            // Value is * 1000.0 (only 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
            // and 2048 supported)
            // Subsystem and channel must be valid.  Ignored on sidescan systems.

            m_StateDataGuard.Enter();
            m_sStatus.m_lADCGain = (long) rMessage;
            eStatus = statusUpdateADCGain;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_ADC_AGC:

            // 0=> disable, 1=> enable (Automatic Gain Control)
            // (SonarMessageLongType)
            // Subsystem and channel must be valid. Ignored on sidescan systems.

            m_StateDataGuard.Enter();
            m_sStatus.m_lADCAGC = (long) rMessage;
            eStatus = statusUpdateADCAGC;
            m_StateDataGuard.Leave();

            break;

        case SONAR_MESSAGE_ADC_RATE:

            // ADC rate in Hz * 1000.  This is a read only value and changes with
            // the pulse selected (SonarMessageLongType)
            // Note:  This is a subsystem command, the channel number must be 0.

            m_StateDataGuard.Enter();
            m_sStatus.m_lADCRate = (long) rMessage;
            eStatus = statusUpdateADCRate;
            m_StateDataGuard.Leave();

            break;

        case SONAR_COMMAND_ERROR:

            HandleSonarError( rMessage, rulTimeStamp );
            break;

        default:

            TRACE( _T( "CSubsystem::HandleSonarCommand(), Unhandled message recieved (%d)\n" ), unMessage );
            break;
    }

    HandleUpdatedStatus( eStatus, rulTimeStamp );

    return true;
}

bool CSubsystem::HandleSonarData(   const CEdgeTechMessage  &rMessage,
                                    const unsigned long     &rulTimeStamp )
{
    // Sonar data recieve handler: the sonar sends data on a per channel basis for each subsystem
    // so we accumulate an entire ping and notify our calling host when it's ready.
    //
    // NB: this routine is called in the context of a thread running within the sonar channel handler
    // so care should be taken to access this subsystem's state in a thread safe manner.

    bool bSuccess = false;

    const unsigned short unMessage = rMessage.SonarMessage();

    //  Total message size is:  size of SonarMessageHeaderType (16 bytes) + 
    //                          size of ping header + 
    //                          size of ping data.

    //TRACE( _T(  "HandleSonarData(), %d, %d, %lu, %lu\n" ),  m_iSubsystemId, unMessageType, rMessage.MessageSize(), rulTimeStamp );

    switch ( unMessage )
    {
        case SONAR_MESSAGE_DATA:            // SBP data (SegyDataType)

            // Note: here we allow all SEG-Y data to pass since all collected using JStar and replayed will
            // be in SEG-Y format.

            if ( IsSBP() )
            {
                bSuccess = HandleSubbottomChannel( rulTimeStamp, (SegyDataType *) rMessage );
            }
            else
            {
                bSuccess = true;
            }

            break;

        case SONAR_MESSAGE_DATA_SIDESCAN:   // Side scan sonar data (SidescanHeaderType)

            // Ensure the subsystem is indeed a sidescan for this data type.

            if ( IsSSS() )
            {
                bSuccess = HandleSidescanChannel( rulTimeStamp, (SidescanHeaderType *) rMessage );
            }
            else
            {
                TRACE( _T( "CSubsystem::HandleSonarData(), Non sidescan subsystem recieved a sidescan sonar data packet ... !!!\n" ) );
            }

            break;

        default:                            // Unexpected or uninteresting message type on sonar data channel.

            bSuccess = true;
            TRACE( _T( "CSubsystem::HandleSonarData(), Unhandled message recieved (%d)\n" ), unMessage );
            break;
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// Protected services invoked on startup or shutdown by owning CSensor class.

bool CSubsystem::SetSystemParameters(   const int           &riSubsystemId,
                                        const int           &riSensorIndex,
                                        const DWORD         &rdwThreadId,
                                        const unsigned int  &ruiDataReadyMessageId,
                                        const unsigned int  &ruiReplyReadyMessageId )
{
    // Sets the dynamically configurable subsystem parameters.

    bool bSuccess = false;

    try
    {
        m_iSubsystemId          = riSubsystemId;
        m_iSensorIndex          = riSensorIndex;
        m_dwThreadId            = rdwThreadId;
        m_uiDataReadyMessageId  = ruiDataReadyMessageId;
        m_uiReplyReadyMessageId = ruiReplyReadyMessageId;

        m_eSubsystemType        = TypeFromId( riSubsystemId );

        m_iDataChannels         = ( m_eSubsystemType == subsystemSBP ) ? 1 : 2;

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SetSystemParameters(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SetSystemParameters(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CSubsystem::InitializeSubsystem(   const bool &rbActivateOnStart,
                                        const bool &rbKeepActivationState )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        //// Reset the sonar to its default parameters (as described in its .INI file) if required.
        //Reset();

        // Load and restore the subsystem's previously saved state (or default if not retrieved).

        if ( ! LoadConfiguration() )
        {
            TRACE( _T( "CSubsystem::InitializeSubsystem(), LoadConfiguration() failed... using default !\n" ) );
        }

        SetDataWindow( m_Config.m_iFrameDecimationFactor, m_Config.m_iSampleDecimationFactor );

        CString PulseFileName( m_Config.m_szPulseFileName );

        if ( ! PulseFileName.IsEmpty() )
        {
            SetPulseFile( PulseFileName, false );//PulseFileFromDescription( PulseFileName ) );
        }
        
        EnableAGC( m_Config.m_bAGCEnable );
        
        // Set the gain for each Tx channel.

#if 0   // TESTCODE
        for ( int iChannel = 0; iChannel < m_iDataChannels; iChannel++ )
        {
            SetTxChannelGain( iChannel, m_Config.m_aiChannelTxGain[ iChannel ], false );
        }
#endif
        
        SetRxGain( m_Config.m_iRxGain, false );

        if ( IsSSS() )
        {
            SetRange( m_Config.m_fRange, false );
        }
        else if ( IsSBP() )
        {
            SetPingDuration( m_Config.m_fPingPeriod, false );
        }

#if ( TRIGGERMODE == TRIGGERMODE_EXTERNAL_UNCOUPLED )

#pragma CompileMessage_m( "TODO: FOR PHASE 2, 7k RELEASE: use uncoupled EXTERNAL trigger mode" )

        // Hard code the triggering mode except for the coupling ID.
        
        if ( SubsystemId() == m_Config.m_lCoupledMasterId )
        {
            if ( m_Config.m_bUseExternalTrigger )
            {
                m_Config.m_eTriggerMode = triggerExternal;
            }
            else
            {
                m_Config.m_eTriggerMode = triggerInternal;
            }
        
            SetTriggerModeProperties( m_Config.m_eTriggerMode, 0UL, m_Config.m_lCoupledMasterId, 1 );
            SetTriggerMode( m_Config.m_eTriggerMode );
        }
        else
        {
            SetTriggerModeProperties( triggerCoupled, 0UL, m_Config.m_lCoupledMasterId, 1 );
            SetTriggerMode( triggerCoupled );
        }

#elif ( TRIGGERMODE == TRIGGERMODE_INTERNAL_COUPLED )

        // For phase 1, make the SSS LF the master and trigger internally in coupled mode.

        m_Config.m_bUseExternalTrigger = false;
        m_Config.m_eTriggerMode        = triggerInternal;
        m_Config.m_lCoupledMasterId    = IdOfSubsystemType( subsystemSSSLF );

#if 0
#pragma CompileMessage_m( "TESTCODE ENABLED" )

        SetTriggerModeProperties( m_Config.m_eTriggerMode, 0UL, m_Config.m_lCoupledMasterId, 1 );
        SetTriggerMode( m_Config.m_eTriggerMode );

#else

        if ( SubsystemId() == m_Config.m_lCoupledMasterId )
        {
            SetTriggerModeProperties( m_Config.m_eTriggerMode, 0UL, m_Config.m_lCoupledMasterId, 1 );
            SetTriggerMode( m_Config.m_eTriggerMode );
        }
        else
        {
            SetTriggerModeProperties( triggerCoupled, 0UL, m_Config.m_lCoupledMasterId, 1 );
            SetTriggerMode( triggerCoupled );
        }

#endif
#endif
        
        // Disable pinging for this subsystem initially.
        
        EnablePingDataTxOnSonarChannel( true );

        PingEnable( false, false );
        
        // Initiate a query for relevant state info.
        
        // Get the available fish.

        SendCommand( SONAR_MESSAGE_PING_FISH_LIST );

        // Get the software version number.

        SendCommand( SONAR_MESSAGE_SYSTEM_VERSION );

        // Get the list of supported fish for this subsystem.

        SendCommand( SONAR_MESSAGE_PING_FISH_LIST );

        // Get the currently selected pulse file.

        SendCommand( SONAR_MESSAGE_PING_SELECT );

        // Initiate the pulse file list query cycle to build a table of available pulses.

        QueryPulseFileListQuery();

        // Poll the subsystem for its general state parameters.

        QueryCommonSettings();

        // Finally, after we've initiated a request for the current state info, turn
        // pinging on or off based on the auto-start mode. Note: we don't set the ping enable
        // flag in persistent m_Config storage since it's determined each time here at run-time only.

        if ( rbKeepActivationState )
        {
            m_bActivateOnStart = rbActivateOnStart;
        }

        PingEnable( rbActivateOnStart, false );

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::InitializeSubsystem(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::InitializeSubsystem(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CSubsystem::ReInitializeSubsystem( CCommandProcessor *pSonarCommandProcessor,
                                        CDataProcessor    *pSonarDataProcessor )
{
    m_pSonarCommandProcessor = pSonarCommandProcessor;
    m_pSonarDataProcessor    = pSonarDataProcessor;

    return InitializeSubsystem( m_bActivateOnStart, false );
}

int CSubsystem::SensorIndex( void ) const
{
    return m_iSensorIndex;
}

bool CSubsystem::Shutdown( void )
{
    bool bSuccess = true;

    try
    {
        if ( ! SaveConfiguration() )
        {
            // Flag the error but don'w abort since we will probably want to continue
            // with the shutdown process, regardless.

            bSuccess = false;
            TRACE( _T( "CSubsystem::Shutdown(), SaveConfiguration() failed\n" ) );
        }

        // Set the state of sonar prior to shutdown as necessary.

        // Disable pinging.
        
        PingEnable( false, false );

        // Use reference counting to decide when the last subsystem is being shutdown on order to 
        // to handle any state finalization for the sonar system.

        if ( --m_iRefCount <= 0 )
        {
            if ( m_bAutoShutdownOS )
            {
                ShutdownSonar( shutdownTypeOSShutdown );
            }

            // Ensure the command gets through before the sonar's connection is terminated.

            Sleep( 200 );
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::Shutdown(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::Shutdown(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CSubsystem::SetSystemTime( void )
{
    bool bSuccess = false;

    try
    {
        CEdgeTechSystemTime SystemTime;

        SendCommand( SONAR_MESSAGE_SYSTEM_TIME, SONAR_COMMAND_SET, (BYTE *) (TimestampType *) SystemTime, (unsigned long) SystemTime );

        TRACE( _T( "CSubsystem::SetSystemTime(), Time (%ld) sent\n" ), SystemTime.time );

        // Wait for the subsytem to settle.

        Sleep( 200 );

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SetSystemTime(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

void CSubsystem::ShutdownSonar( const ESHUTDOWNTYPE &reShutdownType )
{
    if ( SoftwareVersion() >= m_fVersionSupportingOSShutdown )
    {
        long lShutdown = static_cast<long>( reShutdownType );

        SendCommand( SONAR_MESSAGE_SYSTEM_SHUTDOWN, SONAR_COMMAND_SET, (BYTE *) &lShutdown, sizeof( lShutdown ), 0, 0 );
    }
}

///////////////////////////////////////////////////////////////////////////////
// Private services.

CSubsystem::ESUBSYSTEMTYPE CSubsystem::TypeFromId( const int &riSubsystemId )
{
    ESUBSYSTEMTYPE eType = subsystemUnknown;

    // Ensure the id table is loaded from the registry first.

    LoadIdTable();

    // and then use the tabulated ids to identify the subsystem.

    for ( int iSubsystem = 0; iSubsystem < maxSubsystemTypes; iSubsystem++ )
    {
        if ( static_cast<DWORD>(riSubsystemId) == m_sSubsystemIdTable[ iSubsystem ].m_dwSubsystemId )
        {
            eType = m_sSubsystemIdTable[ iSubsystem ].m_eType;
            break;
        }
    }

    ASSERT( eType != subsystemUnknown );

    return eType;
}

inline
bool CSubsystem::IsSSS( void ) const
{
    m_StateDataGuard.Enter();
    ESUBSYSTEMTYPE eType = m_eSubsystemType;
    m_StateDataGuard.Leave();

    return ( ( eType == subsystemSSSLF ) || ( eType == subsystemSSSHF ) );
}

inline
bool CSubsystem::IsSBP( void ) const
{
    m_StateDataGuard.Enter();
    ESUBSYSTEMTYPE eType = m_eSubsystemType;
    m_StateDataGuard.Leave();

    return ( eType == subsystemSBP );
}

bool CSubsystem::SendCommand(   unsigned short      unMessage,
                                unsigned char       ucCommand,
                                BYTE               *pbyMessage,
                                unsigned long       ulBytesInMessage,
                                unsigned char       ucChannel,
                                int                 iSubsystemId )
{
    // Sends a command on the command channel. Channel and subsystem need to be supplied.

    bool bSuccess = false;      // Assume failure for now.

    try
    {
        CEdgeTechMessage Command;

        if ( ! Command.SetMessage(  unMessage, ucCommand, pbyMessage, ulBytesInMessage,
                                    (unsigned char) 0,                                  // Session ID,
                                    (unsigned char) (( iSubsystemId == -1 ) ? m_iSubsystemId : iSubsystemId ),
                                    (unsigned char) ucChannel ) )
        {
            ThrowMessage_m( "Can't encode message" );
        }

        if ( ! SendMessage( m_pSonarCommandProcessor, Command ) )
        {
            ThrowMessage_m( "SendMessage() failed" );
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SendCommand(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SendCommand(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

void CSubsystem::QueryCommonSettings( void )
{
    // Poll for common state data; subsequent responses will be recieved and processed
    // asynchronously by the command handler HandleSonarCommand().

    SendCommand( SONAR_MESSAGE_PING );                      // Enable/disable ping or single ping mode.
    SendCommand( SONAR_MESSAGE_PING_GAIN );                 // Outgoing pulse power multiplier.
    SendCommand( SONAR_MESSAGE_PING_RATE );                 // Ping rate.
    SendCommand( SONAR_MESSAGE_PING_RANGE );                // Range.
    SendCommand( SONAR_MESSAGE_PING_TRIGGER );              // Trigger mode.
    SendCommand( SONAR_MESSAGE_PING_DELAY );                // External trigger mode delay.
    SendCommand( SONAR_MESSAGE_PING_COUPLING_PARAMETERS );  // Trigger mode between coupled subsystems.

    SendCommand( SONAR_MESSAGE_ADC_GAIN );                  // ADC inbound gain - may not be supported for SSS.
    SendCommand( SONAR_MESSAGE_ADC_AGC );                   // AGC enabled/disabled.

    SendCommand( SONAR_MESSAGE_ADC_RATE );                  // ADC digitization rate - pulse dependent.
    SendCommand( SONAR_MESSAGE_DATA_NETWORK_WINDOW );       // Ping delay, decimation (sample and ping) and data packet size.
}

void CSubsystem::SetDataWindow( int             iFrameDecimationFactor,
                                int             iSampleDecimationFactor,
                                unsigned long   ulDelayInSamples,
                                unsigned long   ulTotalSamples,
                                unsigned long   ulMaxPacketSize )
{
    SonarMessageWindowType  sSonarDataWindow;

    ::memset( &sSonarDataWindow, 0x00, sizeof( SonarMessageWindowType ) );

    sSonarDataWindow.frameDecimation = static_cast<unsigned short>( iFrameDecimationFactor );
    sSonarDataWindow.decimation      = static_cast<unsigned short>( iSampleDecimationFactor );

    sSonarDataWindow.initialSkip     = ulDelayInSamples;

    sSonarDataWindow.totalSamples    = ulTotalSamples;
    sSonarDataWindow.maxPacketSize   = ulMaxPacketSize;

    // Note that although EdgeTech's documentation doesn't say so, this command
    // needs to be sent on a per channel basis and not per subsystem basis.

    ASSERT( m_iDataChannels > 0 );

    for ( int iChannel = 0; iChannel < m_iDataChannels; iChannel++ )
    {
        SendCommand( SONAR_MESSAGE_DATA_NETWORK_WINDOW, SONAR_COMMAND_SET, (BYTE *) &sSonarDataWindow, sizeof( sSonarDataWindow ),(BYTE) iChannel );

        // Query its setting.

        SendCommand( SONAR_MESSAGE_DATA_NETWORK_WINDOW, SONAR_COMMAND_GET, NULL, 0, (BYTE) iChannel );
    }
}

void CSubsystem::SetTxChannelGain( const int &riChannel, const int &riTxGain, const bool &rbUpdateConfig )
{
    long lTxGain = static_cast<long>( riTxGain );

    ASSERT( m_iDataChannels > 0 );
    ASSERT( riChannel < m_iDataChannels );

    if ( ( m_iDataChannels > 0 ) && ( riChannel < m_iDataChannels ) )
    {
        // Set the Tx gain...

        SendCommand( SONAR_MESSAGE_PING_GAIN, SONAR_COMMAND_SET, (BYTE *) &lTxGain, sizeof( lTxGain ), (BYTE) riChannel );

        // Query its setting.

        SendCommand( SONAR_MESSAGE_PING_GAIN );

        if ( rbUpdateConfig )
        {
            m_Config.m_aiChannelTxGain[ riChannel ] = riTxGain;
        }
    }
}

void CSubsystem::SetTxPower( const float &rfTxPower, const bool &rbUpdateConfig )
{
    // Here, Tx power is a percentage.

    ASSERT( ( rfTxPower >= 0.0f ) && ( rfTxPower <= 100.0f ) );

    const int iTxGain = static_cast<int>( floor( 10.0f * rfTxPower + 0.5 ) );

    for ( int iChannel = 0; iChannel < m_iDataChannels; iChannel++ )
    {
        SetTxChannelGain( iChannel, iTxGain, rbUpdateConfig );
    }
}

void CSubsystem::SetRxGain( int iRxGain, const bool &rbUpdateConfig )
{
    // AGC and Rx gain are un-supported for SSS.

    if ( ! IsSSS() )
    {
        long lRxGain = 1000L * static_cast<long>( iRxGain );

        SendCommand( SONAR_MESSAGE_ADC_GAIN, SONAR_COMMAND_SET, (BYTE *) &lRxGain, sizeof( lRxGain ) );

        // Query its setting.

        SendCommand( SONAR_MESSAGE_ADC_GAIN );

        if ( rbUpdateConfig )
        {
            m_Config.m_iRxGain = iRxGain;
        }
    }
}

void CSubsystem::EnableAGC( bool bEnable )
{
    // AGC and Rx gain are may be unsupported for SSS.

    if ( ! IsSSS() )
    {
        long lAGCEnable = (long) bEnable;

        SendCommand( SONAR_MESSAGE_ADC_AGC, SONAR_COMMAND_SET, (BYTE *) &lAGCEnable, sizeof( lAGCEnable ) );

        // Query its setting.

        SendCommand( SONAR_MESSAGE_ADC_AGC );
    }
}

void CSubsystem::PingEnable( bool bEnable, const bool &rbUpdateConfig )
{
    long lPingMode = bEnable ? 1L : 0L;

    TRACE( _T( "PingEnable = %d\n" ), bEnable );

    // Set the ping enable flag (continuious/disable).

    SendCommand( SONAR_MESSAGE_PING, SONAR_COMMAND_SET, (BYTE *) &lPingMode, sizeof( lPingMode ) );

    // Query its setting.

    SendCommand( SONAR_MESSAGE_PING );

    if ( rbUpdateConfig )
    {
        m_Config.m_bPingEnable = rbUpdateConfig;
    }
}

void CSubsystem::PingOnce( void )
{
    const long lPingSingle = 2L;

    SendCommand( SONAR_MESSAGE_PING, SONAR_COMMAND_SET, (BYTE *) &lPingSingle, sizeof( lPingSingle ) );
}

void CSubsystem::SetPulseFile( const CString &rPulseFileName, const bool &rbUpdateConfig )
{
    if ( ! rPulseFileName.IsEmpty() )
    {
        int                     iSize;
        SonarMessageStringType  sMessage;

        if ( ( iSize = rPulseFileName.GetLength() ) > 0 )
        {
            strncpy( &sMessage.name[ 0 ], (LPCTSTR) rPulseFileName, iSize );

            sMessage.name[ iSize ] = '\0';

            SendCommand( SONAR_MESSAGE_PING_SELECT, SONAR_COMMAND_SET, (BYTE *) &(sMessage.name[ 0 ]), sizeof( sMessage ) );

            TRACE( "Selected pulse file is : %s\n", sMessage.name );
            
            // Note that the center frequency now set when the response to the query below 
            // is received and processed by the approprite thread.

            // Query for the currently selected pulse file.

            SendCommand( SONAR_MESSAGE_PING_SELECT );

            if ( rbUpdateConfig )
            {
                ::strncpy( &(m_Config.m_szPulseFileName[ 0 ]), (LPCTSTR) rPulseFileName, ulMaxPulseFileNameLength_c - 1 );
            }
        }
    }
}

void CSubsystem::SetRange( float fRangeInMeters, const bool &rbUpdateConfig )
{
    long lRange = (long) floor( ( 1000.0f * fRangeInMeters ) + 0.5 );    // Convert from m to mm.

    SendCommand( SONAR_MESSAGE_PING_RANGE, SONAR_COMMAND_SET, (BYTE *) &lRange, sizeof( lRange ) );

    // Query its setting.

    SendCommand( SONAR_MESSAGE_PING_RANGE );

    if ( rbUpdateConfig )
    {
        m_Config.m_fRange = fRangeInMeters;
    }
}

void CSubsystem::SetTriggerMode( const long &rlTriggerMode )
{
    // Set the trigger mode.

    SendCommand( SONAR_MESSAGE_PING_TRIGGER, SONAR_COMMAND_SET, (BYTE *) &rlTriggerMode, sizeof( rlTriggerMode ) );

    // Query the set mode.

    SendCommand( SONAR_MESSAGE_PING_TRIGGER );
}

void CSubsystem::SetTriggerModeProperties(  const long &rlTriggerMode,
                                            long        lDelayInMicroseconds,
                                            long        lCoupledSubsystemIndex,
                                            long        lEventsToTriggerOn )
{
    // Based on the specified triggering mode, configure the triggering delay and other parameters
    // as required.
    //
    // 1) The triggering mode scenarios are:
    //
    //         Mode         User parameter                  Command(s)
    //         ====         ==============                  ==========
    //
    //     i)  Internal     Nil                             SONAR_MESSAGE_PING_TRIGGER.
    //
    //     ii) External                                     SONAR_MESSAGE_PING_TRIGGER,
    //                      Delay in us                     SONAR_MESSAGE_PING_COUPLING_PARAMETERS
    //                      Delay in us -> ms. (optional)   SONAR_MESSAGE_PING_DELAY
    //                      
    //     iii)Coupled                                      SONAR_MESSAGE_PING_TRIGGER,
    //                      Delay in us, Master's ID        SONAR_MESSAGE_PING_COUPLING_PARAMETERS
    //                      (Master's ID issued to each 
    //                      slaved subsystem)
    //                      
    //     iv) Gated                                        SONAR_MESSAGE_PING_TRIGGER
    //                      Delay in us & triggerDivisor    SONAR_MESSAGE_PING_COUPLING_PARAMETERS
    // 
    //     The sonar supports both hardware and software triggers as follows:
    // 
    //         A soft trigger occurs when a BNC on the topside translates to an ethernet trigger command
    //         at the sonar. The delay of which is governed by the SONAR_MESSAGE_PING_DELAY command.
    //         A more conventional (and deterministic) hard triggering mode whereby the programmable delay
    //         and trigger count divisor are able to be configured using the SonarMessageCouplingParametersType
    //         structure with the SONAR_MESSAGE_PING_COUPLING_PARAMETERS command.
    //
    // 2b) Presently, this server does not support soft trigger delay programmability although it can be easily
    //     added (see CSubsystem::SetTriggerModeProperties() and CSubsystem::SetSoftTrigger() below).
    //
    // 2c) Gated mode employs an inhibition scheme and is substantially different to the other modes.
    //
    // 2d) According to Jason, setting the triggerDivisor field in the SONAR_MESSAGE_PING_COUPLING_PARAMETERS
    //     WILL EFFECT ALL MODES, INCLUDING INTERNAL TRIGGER MODE !
    //
    // 3) In all modes, the ping rate (or equivalently, the range) will have an effect.

    SonarMessageCouplingParametersType  sCouplingParameters;
    memset( &sCouplingParameters, 0x00, sizeof( SonarMessageCouplingParametersType ) );

    sCouplingParameters.subSystem      = lCoupledSubsystemIndex;
    sCouplingParameters.triggerDivisor = lEventsToTriggerOn;
    sCouplingParameters.triggerDelay   = lDelayInMicroseconds;

    switch ( rlTriggerMode )
    {
        case triggerInternal:

            // Send the hard trigger command, even in internal mode, since the triggerDivisior
            // applies to all modes.

            SetHardTrigger( sCouplingParameters );
            break;

        case triggerExternal:

            // For now, we only bother commanding the hard trigger. The soft trigger is sent via the
            // top-side's BNC to the bottom-side via ethernet and is a seperate delay. Later we can 
            // enable this feature by simply adding another delay parameter.

            //SetSoftTrigger( lSoftTriggerDelayInMicroseconds );
            SetHardTrigger( sCouplingParameters );
            break;

        case triggerCoupled:

            SetHardTrigger( sCouplingParameters );
            break;

        case triggerGated:

            SetHardTrigger( sCouplingParameters );
            break;

        default:

            TRACE( "CSubsystem::SetTriggerModeProperties(), Unrecognised trigger mode (%ld)\n", rlTriggerMode );
            break;
    }
}

void CSubsystem::EnablePingDataTxOnSonarChannel( const bool &rbEnable )
{
    // Enables or disables the ping data transmission on the data port.
    
    // Note: data format is hard coded in the sonar's "sonar.ini" file and is not programmable.
    // The following example has been extracted from this file:
    //
    // TelemetryFormat=1
    // ;   Reporting format for data.  Choose from:
    // ;   0: Segy
    // ;   1: SideScan
    // ;   2: Private - Compressed for Telemetry Bandwidth Reduction

    long lEnable = (long) rbEnable;

    ASSERT( m_iDataChannels > 0 );

    for ( int iChannel = 0; iChannel < m_iDataChannels; iChannel++ )
    {
        SendCommand( SONAR_MESSAGE_DATA_ACTIVE, SONAR_COMMAND_SET, (BYTE *) &lEnable, sizeof( lEnable ), (BYTE) iChannel );
    }
}

void CSubsystem::Reset( void )
{
    // *** Caution !!!
    // Subsystem reset has been seen to be problematic with the FS-AU in the past in that 
    // there was contamination between subsystems... needs to be tested.

    // Issue a reset command to the sonar. Note also that the subsystem should be 255 to 
    // reset the entire system or the specific subsystem number.  The channel must be 0.

    SendCommand( SONAR_MESSAGE_SYSTEM_RESET, SONAR_COMMAND_SET );

    // Will probably need to wait for sonar to reset - EdgeTech to advise of safe period.

    // Sleep( 2000 );
}

inline
void CSubsystem::Sleep( const DWORD    &rdwWaitPeriodInMilliseconds,
                        bool            bBlockThread )
{
    CWinThread *pThread = AfxGetThread();

    if ( bBlockThread )
    {
        ::Sleep( rdwWaitPeriodInMilliseconds );
    }
    else if ( pThread != NULL )
    {
        // Loop until the specified wait period has expired or an error occurs. Note also that
        // during the wait period this thread relinquishes its time slice and therefore the actual
        // granularity will be affected by system's the thread scheduler.

        const DWORD dwInitialTime = CSystemTime::GetTickCount();

        for(;;)
        {
            if ( ( CSystemTime::GetTickCount() - dwInitialTime ) >= rdwWaitPeriodInMilliseconds )
            {
                break;              // Time to wait has expired.
            }
            else if ( pThread->PumpMessage() )
            {
                ::Sleep( 0 );       // Relinquish this thread's time slice.
            }
            else
            {
                break;              // Error executing the message pump so terminate.
            }
        }
    }
    else
    {
        TRACE( _T( "CSubsystem::Sleep(), pThread is NULL\n" ) );
    }
}

void CSubsystem::QueryPulseFileListQuery( void )
{
    // Reset to the top of the list.

    SendCommand( SONAR_MESSAGE_PING_LIST, SONAR_COMMAND_SET );

    // Solicit the entire pulse list.

    const long lNumberToGet = static_cast<long>( m_ulMaxPulseFiles );

    SendCommand( SONAR_MESSAGE_PING_LIST, SONAR_COMMAND_GET, (BYTE *) &lNumberToGet, sizeof( lNumberToGet ) );
}

void CSubsystem::RetrievePulseFileListQuery( int iClientIndex )
{
    // Check to see if the pulse file query list has already been retrieved. If not,
    // solicit it from the subsystem. Then queue a request for sending the host info back to
    // the 6046 for reply.

    m_StateDataGuard.Enter();
    bool bListAvailable = m_bPulseFileListRecieved;
    m_iReplyClientIndex = iClientIndex;
    m_StateDataGuard.Leave();

    if ( bListAvailable )
    {
        Reply( replyPingList );
    }
    else
    {
        m_StateDataGuard.Enter();
        m_bPulseFileListReplyRequested = true;
        m_StateDataGuard.Leave();

        QueryPulseFileListQuery();
    }
}

void CSubsystem::SetDefaultState( void )
{
#pragma CompileMessage_m( "TODO: IMPLEMENTATION NEEDED" )
}

inline
void CSubsystem::SetHardTrigger( const SonarMessageCouplingParametersType &rsCouplingParameters )
{
    // Used to set the trigger delay when using hard triggering in external mode or either of coupled 
    // or gated modes.

    // Send the command.

    SendCommand( SONAR_MESSAGE_PING_COUPLING_PARAMETERS, SONAR_COMMAND_SET, (BYTE *) &rsCouplingParameters, sizeof( SonarMessageCouplingParametersType ) );

    // Query its setting.

    SendCommand( SONAR_MESSAGE_PING_COUPLING_PARAMETERS );
}

inline
void CSubsystem::SetSoftTrigger( const long &rlDelayInMicroseconds )
{
    // Used to set the trigger delay when using soft triggering in external mode.

    long lDelayInMilliseconds = (long) rlDelayInMicroseconds / 1000;

    // Set the trigger mode.

    SendCommand( SONAR_MESSAGE_PING_DELAY, SONAR_COMMAND_SET, (BYTE *) &lDelayInMilliseconds, sizeof( lDelayInMilliseconds ) );

    // Query its setting.

    SendCommand( SONAR_MESSAGE_PING_DELAY );
}

///////////////////////////////////////////////////////////////////////////////
// SBP and SSS data handlers.

bool CSubsystem::HandleSubbottomChannel(    const unsigned long &rulTimeStamp,
                                            const SegyDataType  *psData )
{
    // Given a valid SBP ping (1 channel), do the following:
    //
    //      - Format as 7k record.
    //      - Buffer ready for host retrieval.
    //      - Notify client of data ready.

    bool bSuccess = false;

    try
    {
        if ( ! IsValid( psData ) )
        {
            ThrowMessage_m( "Invalid SEGY header" );
        }

#if 0
        TRACE( _T( "Subbottom ping: %lu, Channel: %d, Samples: %lu\n" ), psData->pingNum, psData->channelNum, psData->samples );
#endif


        if ( ! FormatEdgeTech7kSBPRecord( rulTimeStamp, psData ) )
        {
            ThrowMessage_m( "Can't format record" );
        }

        if ( ! Queue7kDataRecord() )
        {
            ThrowMessage_m( "Can't buffer record" );
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::HandleSubbottomChannel(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::HandleSubbottomChannel(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CSubsystem::HandleSidescanChannel( const unsigned long         &rulTimeStamp,
                                        const SidescanHeaderType    *psData )
{
    // Given a SSS channel, do the following:
    //
    //      - Accumulate channels for the current ping.
    //      - If (entire ping ready) then
    //          Format as 7k record.
    //          Buffer ready for client retrieval.
    //          Notify client of data ready.
    //        endif

    bool bSuccess = false;

    try
    {
        // Validate the incoming header.

        if ( ! IsValid( psData ) )
        {
            ThrowMessage_m( "Invalid Sidescan header" );
        }

        // Add the new channel data to the channel sequenencer.

        CHANNELITEM sChannelItem;

        sChannelItem.m_ulTimeStamp  = rulTimeStamp;
        sChannelItem.m_ulChannel    = psData->channelNum;
        sChannelItem.m_ulSubsystem  = psData->subsystem;
        sChannelItem.m_ulPingNumber = psData->pingNum;

        ASSERT( psData->samples > 0UL );

        const unsigned long ulSampleSize = sizeof( short int );
        const unsigned long ulBytes      = sizeof( SidescanHeaderType ) + ulSampleSize * psData->samples;

#if 0
        TRACE( _T( "Sidescan ping number: %lu, Channel: %d, Subsystem: %d, Samples: %lu\n" ), psData->pingNum, psData->channelNum, psData->subsystem, psData->samples );
#endif

        if ( ! m_ChannelSequencer.Add( (BYTE *) psData, const_cast<unsigned long &>(ulBytes), sChannelItem ) )
        {
            ThrowMessage_m( "Can't add channel data to sequencer" );
        }

        if ( m_ChannelSequencer.IsPingComplete( psData->pingNum ) )
        {
#if 0
            TRACE( _T( "Sidescan ping %lu, subsystem %d complete\n" ), psData->pingNum, psData->subsystem );
#endif

            if ( ! FormatEdgeTech7kSSSRecord( psData->pingNum ) )
            {
                ThrowMessage_m( "Can't format record" );
            }

            if ( ! Queue7kDataRecord() )
            {
                ThrowMessage_m( "Can't buffer record" );
            }

            // Remove the component ping channels from the channel sequencer.

            if ( ! m_ChannelSequencer.RemovePingChannels( psData->pingNum ) )
            {
                ThrowMessage_m( "Can't remove ping from sequencer" );
            }
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::HandleSidescanChannel(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::HandleSidescanChannel(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CSubsystem::NotifyHostOfReply( void )
{
    bool bNotified = false;

    ASSERT( m_dwThreadId != m_dwInvalidThreadId );

    if ( m_dwThreadId != m_dwInvalidThreadId )
    {
        bNotified = ( ::PostThreadMessage( m_dwThreadId, m_uiReplyReadyMessageId, (WPARAM) m_iSensorIndex, (LPARAM) m_iReplyClientIndex ) != FALSE );
    }

    ASSERT( bNotified );

    return bNotified;
}

inline
bool CSubsystem::IsValid( const SidescanHeaderType *psData )
{
    // For SSS data, we expect 1 of 2 possible channels of envelope data.

    return ( ( psData != NULL ) ? ( ( psData->samples    >  0 )  && 
                                    ( psData->dataFormat == 0 )  &&
                                    ( ( psData->channelNum == 0 ) || ( psData->channelNum == 1 ) ) ) : false );
}

inline
bool CSubsystem::IsValid( const SegyDataType *psData )
{
    // For SBP data, we expect one channel of complex data.

    return ( ( psData != NULL ) ? ( ( psData->samples    >  0 ) && 
                                    ( psData->dataFormat == 1 ) &&
                                    ( psData->channelNum == 0 )  ) : false );
}

inline
bool CSubsystem::FormatEdgeTech7kSSSRecord( const unsigned long &rulPingNumber )
{
    // Given an accumulated ping is available for formatting, format the 
    // 7k SSS record, with the EdgeTech headers as optional data, as follows:
    //
    // - Data record frame      (7k header)
    // - Record type header     (General SSS)
    // - Channel data           (Channel header + channel imagery (port then starboard))
    // - Optional data          (EdgeTech channel data (port then starboard))

    bool bSuccess = false;

    try
    {
        const unsigned long     ulChannelHeaderSize = sizeof( SidescanHeaderType );
        const unsigned long     ulSampleSize        = sizeof( short int );

        unsigned long           ulTimestamp         = 0UL;
        CDynamicBuffer<BYTE>   *pPort               = NULL;
        CDynamicBuffer<BYTE>   *pStbd               = NULL;

        if ( ! m_ChannelSequencer.GetAccumulatedPingChannels( rulPingNumber, ulTimestamp, pPort, pStbd ) )
        {
            ThrowMessage_m( "Can't retrieve accumulated ping channels" );
        }

        if ( ( pPort == NULL ) || ( ( pPort->Size() * sizeof( BYTE ) ) <= ulChannelHeaderSize ) )
        {
            ThrowMessage_m( "Invalid port data channel" );
        }

        if ( ( pStbd == NULL ) || ( ( pStbd->Size() * sizeof( BYTE ) ) <= ulChannelHeaderSize ))
        {
            ThrowMessage_m( "Invalid stbd data channel" );
        }

        const SidescanHeaderType   *psPortHeader = reinterpret_cast<SidescanHeaderType *> (pPort->GetAt( 0 ));
        const SidescanHeaderType   *psStbdHeader = reinterpret_cast<SidescanHeaderType *> (pStbd->GetAt( 0 ));

        ASSERT( psPortHeader->pingNum == rulPingNumber );
        ASSERT( psStbdHeader->pingNum == rulPingNumber );

#if 0
        TRACE( _T( "Sidescan ping number: %lu, Channel: %d, Subsystem: %d, Samples: %lu being formatted\n" ), psPortHeader->pingNum, psPortHeader->channelNum, psPortHeader->subsystem, psPortHeader->samples );
        TRACE( _T( "Sidescan ping number: %lu, Channel: %d, Subsystem: %d, Samples: %lu being formatted\n" ), psStbdHeader->pingNum, psStbdHeader->channelNum, psStbdHeader->subsystem, psPortHeader->samples );
#endif

        const unsigned long ulPortImagerySize = psPortHeader->samples * ulSampleSize;
        const unsigned long ulStbdImagerySize = psStbdHeader->samples * ulSampleSize;
        const unsigned long ulTotalRecordSize = sizeof( DATARECORDFRAME ) + 
                                                    sizeof( SIDESCANRECORDTYPEHEADER ) +
                                                        sizeof( CHANNELINFO ) + ulPortImagerySize +
                                                            sizeof( CHANNELINFO ) + ulStbdImagerySize +
                                                                2 * ulChannelHeaderSize +
                                                                    Checksum7kSize_m();

        // Determine the byte offsets to each component of the 7k record.

        BYTE * const pbyDRF          = &m_pby7kRecord[ 0 ];
        BYTE * const pbyRTH          = pbyDRF          + sizeof( DATARECORDFRAME );
        BYTE * const pbyPortChanInfo = pbyRTH          + sizeof( SIDESCANRECORDTYPEHEADER );
        BYTE * const pbyPortImagery  = pbyPortChanInfo + sizeof( CHANNELINFO );
        BYTE * const pbyStbdChanInfo = pbyPortImagery  + ulPortImagerySize;
        BYTE * const pbyStbdImagery  = pbyStbdChanInfo + sizeof( CHANNELINFO );
        BYTE * const pbyHeader1      = pbyStbdImagery  + ulStbdImagerySize;
        BYTE * const pbyHeader2      = pbyHeader1      + ulChannelHeaderSize;

        // Now, let's format the data record frame.

        DATARECORDFRAME * const psDataRecordFrame = reinterpret_cast<DATARECORDFRAME *> (pbyDRF);

        // Set the Data Record Frame.

        ::memset( psDataRecordFrame, 0x00, sizeof( DATARECORDFRAME ) );

        CSystemTime::TimeStampTo7kTime( ulTimestamp, &(psDataRecordFrame->m_sTime7k) ); 

        psDataRecordFrame->m_unVersion                  = un7kDataProtocolVersion_c;        // Version              u16 Version of this frame (e.g.: 1, 2, …)
        psDataRecordFrame->m_unOffset                   = sizeof( DATARECORDFRAME ) - 4;    // Offset               u16 Offset in bytes from the start of the sync pattern to the start of the DATA SECTION. This allows for expansion of the header whilst maintaining backward compatibility.
        psDataRecordFrame->m_ulSize                     = ulTotalRecordSize;                // Size                 u32 Size in bytes of this record from the start of the version field to the end of the Checksum. It includes the embedded data size.
        psDataRecordFrame->m_ulSyncPattern              = 0x0000ffff;                       // Sync pattern         u32 0x0000FFFF

        // optional data fields. Offset is from start of record. Zero implies no optional data.

        psDataRecordFrame->m_ulOffsetToOptionalData     = ulTotalRecordSize - ( 2 * ulChannelHeaderSize + Checksum7kSize_m() - 1UL );
        psDataRecordFrame->m_ulOptionalDataIdentifier   = 1UL;
        psDataRecordFrame->m_ulRecordType               = recordTypeEdgeTechFSDWSSS;        // Record type          u32 Unique identifier of indicating the type of data embedded in this record.

        switch ( SubsystemType() )
        {
            case subsystemUnknown:

                psDataRecordFrame->m_ulDeviceId = deviceIdUnknown;
                break;

            case subsystemSSSLF:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWLFSSS;
                break;

            case subsystemSSSHF:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWHFSSS;
                break;

            case subsystemSBP:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWSBP;
                break;
        }

        // Following are not used or maintained by 6046.
        
#if ( DRF_VERSION_7K <= 2 )

#if ( DRF_VERSION_7K == 1 )

        psDataRecordFrame->m_ulSubsystemId          = static_cast<unsigned long>( SubsystemId() ); // Subsystem identifier u16 Identifier for the device subsystem

#else

        psDataRecordFrame->m_unSubsystemId          = static_cast<unsigned short>( SubsystemId() ); // Subsystem identifier u16 Identifier for the device subsystem

#endif

        psDataRecordFrame->m_ulDataSetNumber        = 0UL;                                                            // u32 Data set number.

#endif

        psDataRecordFrame->m_ulRecordNumber         = 0UL;                                                            // u32 Sequential record counter.

#if ( DRF_VERSION_7K <= 2 )

        psDataRecordFrame->m_i64PreviousRecord      = (__int64) -1;                                                   // i64 Pointer to the previous record of the same type (in bytes from start of file). This is an optional field for files and shall be -1 if not used.
        psDataRecordFrame->m_i64NextRecord          = (__int64) -1;                                                   // i64 Pointer to the next record of the same type in bytes from start of file. This is an optional field for files and shall be -1 if not used.

#endif

#if ( DRF_VERSION_7K >= 5 )

        psDataRecordFrame->m_unRecordsVersion       = un7kRecordsVersion_c;                                           // u16 Record version within a given verion of the protocol.

#endif

        psDataRecordFrame->m_unFlags = 0x0000;                                              // Flags                u16 BIT FIELD: Bit 1 - Valid Checksum

        // Set the various members of the Record Type Header.

        SIDESCANRECORDTYPEHEADER *psRecordTypeHeader = reinterpret_cast<SIDESCANRECORDTYPEHEADER *>( pbyRTH );

        ::memset( psRecordTypeHeader, 0x00, sizeof( SIDESCANRECORDTYPEHEADER ) );

        psRecordTypeHeader->m_ulTimestamp          = ulTimestamp;                   // Milliseconds since Windows started when ping recieved.
        psRecordTypeHeader->m_ulPingNumber         = psPortHeader->pingNum;         // Device specific byte.
        psRecordTypeHeader->m_ulNumberOfChannels   = 2;                             // Number of channels to follow.
        psRecordTypeHeader->m_ulTotalBytes         = ulTotalRecordSize -            // Total bytes in channel data to follow plus additional optional data.
                                                        sizeof( DATARECORDFRAME ) -
                                                            sizeof( SIDESCANRECORDTYPEHEADER ) -
                                                             Checksum7kSize_m();

        psRecordTypeHeader->m_ulDataFormat         = psPortHeader->dataFormat;      // 0 - Envelope, 1 - Complex (i.e., I and Q)., 2 - raw (pre-matched filter), real part of analytic signal.

        // Port then starboard imagery with channel header.

        const short * pnPortImagery = reinterpret_cast<short *> (((BYTE *) psPortHeader) + ulChannelHeaderSize );
        const short * pnStbdImagery = reinterpret_cast<short *> (((BYTE *) psStbdHeader) + ulChannelHeaderSize );

        CHANNELINFO * psChannelInfo;

        // Port channel first.

        psChannelInfo = reinterpret_cast<CHANNELINFO *> (pbyPortChanInfo);

        ::memset( psChannelInfo, 0x00, sizeof( CHANNELINFO ) );

        psChannelInfo->m_ucChannelNumber                = 0;                            // 0, 1, ... number of channels - 1.
        psChannelInfo->m_ucChannelType                  = 0;                            // 0 = Port, 1 = Stbd, 2 = SBP
        psChannelInfo->m_ucDataType                     = 0;                            // 0 = Slantrange, 1 = Ground range
        psChannelInfo->m_ucPolarity                     = 0;                            // 0 - bipolar, 1 - unipolar.
        psChannelInfo->m_ucBytesPerSample               = ulSampleSize;                 // Bytes per sample in this channel.

        psChannelInfo->m_ulNumberOfSamples              = psPortHeader->samples;        // Number of samples in this channel.
        psChannelInfo->m_ulTimeStartOffset              = 0UL;                          // Start of first sample from ping timestamp (us).
        psChannelInfo->m_ulSampleIntervalMicroseconds   = psPortHeader->sampleInterval / 1000UL;    // Sample interval (us).

        psChannelInfo->m_fRange                         = 0.1f * (float) psPortHeader->rangeSetting; // Range (m) (slant or ground depending on m_ucDataType).
        psChannelInfo->m_fVoltageFSD                    = -1.0f;                        // Equivalent maximum voltage amplitude.

        strcpy( &(psChannelInfo->m_szName[0]), "Port" );                                // Channel name (NULL terminated).

        ::memcpy( pbyPortImagery, pnPortImagery, ulPortImagerySize );

        // Stbd channel next.

        psChannelInfo = reinterpret_cast<CHANNELINFO *> (pbyStbdChanInfo);

        ::memset( psChannelInfo, 0x00, sizeof( CHANNELINFO ) );

        psChannelInfo->m_ucChannelNumber                = 1;                            // 0, 1, ... number of channels - 1.
        psChannelInfo->m_ucChannelType                  = 1;                            // 0 = Port, 1 = Stbd, 2 = SBP
        psChannelInfo->m_ucDataType                     = 0;                            // 0 = Slantrange, 1 = Ground range
        psChannelInfo->m_ucPolarity                     = 0;                            // 0 - bipolar, 1 - unipolar.
        psChannelInfo->m_ucBytesPerSample               = ulSampleSize;                 // Bytes per sample in this channel.
        psChannelInfo->m_ulNumberOfSamples              = psStbdHeader->samples;        // Number of samples in this channel.

        psChannelInfo->m_ulTimeStartOffset              = 0UL;                          // Start of first sample from ping timestamp (us).
        psChannelInfo->m_ulSampleIntervalMicroseconds   = psStbdHeader->sampleInterval / 1000UL;    // Sample interval (ns).

        psChannelInfo->m_fRange                         = 0.1f * (float) psStbdHeader->rangeSetting; // Range (m) (slant or ground depending on m_ucDataType).
        psChannelInfo->m_fVoltageFSD                    = -1.0f;                        // Equivalent maximum voltage amplitude.

        strcpy( psChannelInfo->m_szName, "Stbd" );                                      // Channel name (NULL terminated).

        ::memcpy( pbyStbdImagery, pnStbdImagery, ulStbdImagerySize );

        // Optional data, i.e., port then starboard EdgeTech channel headers...

        ::memcpy( pbyHeader1, psPortHeader, ulChannelHeaderSize );
        ::memcpy( pbyHeader2, psStbdHeader, ulChannelHeaderSize );

        // Finally, set the checksum to 0.

        *(reinterpret_cast<Checksum7k_t *> (&pbyHeader2[ ulChannelHeaderSize ])) = 0UL;

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::FormatEdgeTech7kSSSRecord(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::FormatEdgeTech7kSSSRecord(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CSubsystem::FormatEdgeTech7kSBPRecord( const unsigned long &rulTimeStamp,
                                            const SegyDataType  *psData )
{
    // Format the 7k SBP data comprising of:
    //
    // - Data record frame              (Standard 7k header)
    // - Record type header             (General SBP)
    // - Record data                    (Channel imagery (single channel data, complex).
    // - Optional data                  (EdgeTech Seg-Y channel data)

    bool bSuccess = false;

    try
    {
        // Compute the various sizes of the dynamic record components.

        const unsigned long ulChannelHeaderSize = sizeof( SegyDataType );
        const unsigned long ulSampleSize        = ( ( psData->dataFormat == 1 ) ? 2 : 1 ) * sizeof( short int );
        const unsigned long ulImagerySize       = psData->samples * ulSampleSize;
        const unsigned long ulTotalRecordSize   = sizeof( DATARECORDFRAME ) + 
                                                    sizeof( SUBBOTTOMRECORDTYPEHEADER ) +
                                                        sizeof( CHANNELINFO ) + ulImagerySize +
                                                                ulChannelHeaderSize +
                                                                    Checksum7kSize_m();

        // Compute the offsets into the 7k record for the various components.

        BYTE * const pbyDRF      = &m_pby7kRecord[ 0 ];
        BYTE * const pbyRTH      = pbyDRF          + sizeof( DATARECORDFRAME );
        BYTE * const pbyChanInfo = pbyRTH          + sizeof( SUBBOTTOMRECORDTYPEHEADER );
        BYTE * const pbyImagery  = pbyChanInfo     + sizeof( CHANNELINFO );
        BYTE * const pbyHeader   = pbyImagery      + ulImagerySize;

        // Set the various data components.

        DATARECORDFRAME * const psDataRecordFrame = reinterpret_cast<DATARECORDFRAME *> (pbyDRF);

        // Set the various members of the Data Record Frame.

        ::memset( psDataRecordFrame, 0x00, sizeof( DATARECORDFRAME ) );

        CSystemTime::TimeStampTo7kTime( rulTimeStamp, &(psDataRecordFrame->m_sTime7k) ); 

        psDataRecordFrame->m_unVersion           = un7kDataProtocolVersion_c;                   // Version              u16 Version of this frame (e.g.: 1, 2, …)
        psDataRecordFrame->m_unOffset            = sizeof( DATARECORDFRAME ) - 4;               // Offset               u16 Offset in bytes from the start of the sync pattern to the start of the DATA SECTION. This allows for expansion of the header whilst maintaining backward compatibility.
        psDataRecordFrame->m_ulSyncPattern       = 0x0000ffff;                                  // Sync pattern         u32 0x0000FFFF
        psDataRecordFrame->m_ulSize              = ulTotalRecordSize;                           // Size                 u32 Size in bytes of this record from the start of the version field to the end of the Checksum. It includes the embedded data size.

        psDataRecordFrame->m_ulOffsetToOptionalData   = ulTotalRecordSize - ( 1 * ulChannelHeaderSize + Checksum7kSize_m() - 1UL );
        psDataRecordFrame->m_ulOptionalDataIdentifier = 2UL;                                    // Data idenfitifer     u32 Identifier for optional data field. Zero for no optional field. This identifier is described with each record type.

        psDataRecordFrame->m_ulRecordType        = recordTypeEdgeTechFSDWSBP;                   // Record type          u32 Unique identifier of indicating the type of data embedded in this record.

        switch ( SubsystemType() )
        {
            case subsystemUnknown:

                psDataRecordFrame->m_ulDeviceId = deviceIdUnknown;
                break;

            case subsystemSSSLF:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWLFSSS;
                break;

            case subsystemSSSHF:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWHFSSS;
                break;

            case subsystemSBP:

                psDataRecordFrame->m_ulDeviceId = deviceIdEdgeTechFSDWSBP;
                break;
        }

        // Not set or maintained by 6046.

#if ( DRF_VERSION_7K <= 2 )

#if ( DRF_VERSION_7K == 1 )

        psDataRecordFrame->m_ulSubsystemId          = static_cast<unsigned long>( SubsystemId() ); // Subsystem identifier u16 Identifier for the device subsystem

#else

        psDataRecordFrame->m_unSubsystemId          = static_cast<unsigned short>( SubsystemId() ); // Subsystem identifier u16 Identifier for the device subsystem

#endif

        psDataRecordFrame->m_ulDataSetNumber           = 0UL;                                                            // u32 Data set number.

#endif

        psDataRecordFrame->m_ulRecordNumber            = 0UL;                                                            // u32 Sequential record counter.

#if ( DRF_VERSION_7K <= 2 )

        psDataRecordFrame->m_i64PreviousRecord         = (__int64) -1;                                                   // i64 Pointer to the previous record of the same type (in bytes from start of file). This is an optional field for files and shall be -1 if not used.
        psDataRecordFrame->m_i64NextRecord             = (__int64) -1;                                                   // i64 Pointer to the next record of the same type in bytes from start of file. This is an optional field for files and shall be -1 if not used.

#endif

#if ( DRF_VERSION_7K >= 5 )

        psDataRecordFrame->m_unRecordsVersion          = un7kRecordsVersion_c;                                           // u16 Record version within a given verion of the protocol.

#endif

        psDataRecordFrame->m_unFlags = 0x0000;                                                  // Flags                u16 BIT FIELD: Bit 1 - Valid Checksum

        // Set the various members of the Record Type Header.

        SUBBOTTOMRECORDTYPEHEADER * const psRecordTypeHeader = reinterpret_cast<SUBBOTTOMRECORDTYPEHEADER *> (pbyRTH);

        psRecordTypeHeader->m_ulTimestamp          = rulTimeStamp;                              // Milliseconds since Windows started when ping recieved.
        psRecordTypeHeader->m_ulPingNumber         = psData->pingNum;                           // Device specific byte.
        psRecordTypeHeader->m_ulNumberOfChannels   = 1;                                         // Number of channels to follow.
        psRecordTypeHeader->m_ulTotalBytes         = ulTotalRecordSize -                        // Total bytes in channel data to follow plus additional optional data.
                                                        sizeof( DATARECORDFRAME ) -
                                                            sizeof( SUBBOTTOMRECORDTYPEHEADER ) -
                                                             Checksum7kSize_m();

        psRecordTypeHeader->m_ulDataFormat              = psData->dataFormat;                   // 0 - Envelope, 1 - Complex (i.e., I and Q)., 2 - raw (pre-matched filter), real part of analytic signal.

        CHANNELINFO *psChannelInfo = reinterpret_cast<CHANNELINFO *> (pbyChanInfo);

        ::memset( psChannelInfo, 0x00, sizeof( CHANNELINFO ) );

        psChannelInfo->m_ucChannelNumber                = 0;                                    // 0, 1, ... number of channels - 1.
        psChannelInfo->m_ucChannelType                  = 2;                                    // 0 = Port, 1 = Stbd, 2 = SBP
        psChannelInfo->m_ucDataType                     = 0;                                    // 0 = uncorrected, 1 = Ground range corrected.

        psChannelInfo->m_ucPolarity                     = 0;                                    // 0 - bipolar, 1 - unipolar.
        psChannelInfo->m_ucBytesPerSample               = (unsigned char) ulSampleSize;         // Bytes per sample in this channel.

        psChannelInfo->m_ulNumberOfSamples              = psData->samples;                      // Number of samples in this channel.
        psChannelInfo->m_ulTimeStartOffset              = 0;                                    // Start of first sample from ping timestamp (us).
        psChannelInfo->m_ulSampleIntervalMicroseconds   = psData->sampleInterval / 1000UL;      // Sample interval (us).

        psChannelInfo->m_fRange                         = -1.0f;                                // Range (m) (slant or ground depending on m_ucDataType).
        psChannelInfo->m_fVoltageFSD                    = -1.0f;                                // Equivalent maximum voltage amplitude.

        strcpy( psChannelInfo->m_szName, "SBP" );                                               // Channel name (NULL terminated).

        ::memcpy( pbyImagery, ((BYTE *) psData) + ulChannelHeaderSize, ulImagerySize );

        // Optional data, i.e., port then starboard EdgeTech channel headers...

        ::memcpy( pbyHeader, psData, ulChannelHeaderSize );

        // Finally, the checksum.

        *(reinterpret_cast<Checksum7k_t *> (&pbyHeader[ ulChannelHeaderSize ])) = 0UL;

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::FormatEdgeTech7kSBPRecord(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::FormatEdgeTech7kSBPRecord(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CSubsystem::Queue7kDataRecord( void )
{
    bool bSuccess = false;

    ASSERT( m_pby7kRecord != NULL );

    if ( m_pby7kRecord != NULL )
    {
        DATARECORDFRAME *psFrame = reinterpret_cast<DATARECORDFRAME *>( m_pby7kRecord );

        if ( psFrame->m_ulSize >= sizeof( DATARECORDFRAME ) )
        {
            PINGRECORDTYPEHEADER *psPingRecordHeader = reinterpret_cast<PINGRECORDTYPEHEADER *> (&m_pby7kRecord[ sizeof( DATARECORDFRAME ) ]);

            if ( psPingRecordHeader != NULL )
            {
                RECORDHEADER sRecordHeader = { 0 };

                sRecordHeader.m_iSensorIndex           = m_iSensorIndex;
                sRecordHeader.m_ulRecordCounter        = 0UL;
                sRecordHeader.m_ulRecordSizeInBytes    = psFrame->m_ulSize;
                sRecordHeader.m_ulMillisecondTimestamp = psPingRecordHeader->m_ulTimestamp;

                bSuccess = m_SensorDataPool.Write( &sRecordHeader, m_pby7kRecord, psFrame->m_ulSize );

                //TRACE( _T( "Queue7kDataRecord(), queued %lu bytes @ %lu\n" ), psFrame->m_ulSize, sMessage.m_ulTimeStamp );
            }
        }
    }

    if ( ! bSuccess )
    {
        TRACE( _T( "CSubsystem::Queue7kDataRecord(), about to return false\n" ) );
    }

    return bSuccess;
}

//inline
//bool CSubsystem::Queue7kDataRecord( void )
//{
//    bool bSuccess = false;
//
//    ASSERT( m_pby7kRecord != NULL );
//
//    if ( m_pby7kRecord != NULL )
//    {
//        DATARECORDFRAME *psFrame = reinterpret_cast<DATARECORDFRAME *> (m_pby7kRecord);
//
//        if ( psFrame->m_ulSize >= sizeof( DATARECORDFRAME ) )
//        {
//            PINGRECORDTYPEHEADER *psPingRecordHeader = reinterpret_cast<PINGRECORDTYPEHEADER *> (&m_pby7kRecord[ sizeof( DATARECORDFRAME ) ]);
//
//            if ( psPingRecordHeader != NULL )
//            {
//                // Queue the formatted 7k record for later retrieval by the host (through Load()). Note: needs to 
//                // be thread safe since client will be retriving using its own thread.
//
//                // Here we construct an empty queued item object then queue the 7k record.
//
//                QUEUEDMESSAGE sMessage;
//
//                sMessage.m_ulTimeStamp = psPingRecordHeader->m_ulTimestamp;
//
//                m_DataGuard.Enter();
//                bSuccess = m_DataRecordQueue.Add( m_pby7kRecord, psFrame->m_ulSize, sMessage );
//                m_DataGuard.Leave();
//
//                //TRACE( _T( "Queue7kDataRecord(), queued %lu bytes @ %lu\n" ), psFrame->m_ulSize, sMessage.m_ulTimeStamp );
//            }
//        }
//    }
//
//    if ( ! bSuccess )
//    {
//        TRACE( _T( "CSubsystem::Queue7kDataRecord(), about to return false\n" ) );
//    }
//
//    return bSuccess;
//}

bool CSubsystem::Queue7kStatusRecord( BYTE *pbyRecord, const unsigned long &rulBytes )
{
    bool bSuccess = false;

    DBG_UNREFERENCED_PARAMETER( rulBytes );

    ASSERT( pbyRecord != NULL );

    if ( pbyRecord != NULL )
    {
        DATARECORDFRAME *psFrame = reinterpret_cast<DATARECORDFRAME *> (pbyRecord);

        if ( psFrame->m_ulSize >= sizeof( DATARECORDFRAME ) )
        {
            QUEUEDMESSAGE sMessage;

            sMessage.m_ulTimeStamp = CSystemTime::GetTickCount();

            m_StatusQueueGuard.Enter();
            bSuccess = m_StatusQueue.Add( pbyRecord, psFrame->m_ulSize, sMessage );
            m_StatusQueueGuard.Leave();

            bSuccess = true;
        }
    }

    if ( ! bSuccess )
    {
        TRACE( _T( "CSubsystem::Queue7kStatusRecord(), about to return false\n" ) );
    }

    return bSuccess;
}

bool CSubsystem::LoadConfiguration( void )
{
    // Load the previously saved configuration (or default if not retrieved)
    // into the various state parameters for subsequent setting.

    bool bSuccess = false;

    if ( m_Config.Open( m_iSubsystemId, CSubsystemConfig::accessRead ) )
    {
        bSuccess = m_Config.Read();

        if ( ! bSuccess )
        {
            m_Config.SetDefault();
        }
    }
    else if ( m_Config.Open( m_iSubsystemId, CSubsystemConfig::accessWrite ) )
    {
        m_Config.SetDefault();

        m_Config.m_eTriggerMode     = triggerInternal;
        m_Config.m_lCoupledMasterId = IdOfSubsystemType( subsystemSSSLF );

        bSuccess = m_Config.Write();
    }

    m_Config.Close();

    ASSERT( bSuccess );

    return bSuccess;
}

bool CSubsystem::SaveConfiguration( void )
{
    bool bSuccess = false;      // Assume failure for now.

    if ( m_Config.Open( m_iSubsystemId, CSubsystemConfig::accessWrite ) )
    {
        bSuccess = m_Config.Write();

        m_Config.Close();
    }

    return bSuccess;
}

inline
void CSubsystem::HandleUpdatedStatus(   const ESTATUSUPDATE &reStatus,
                                        const unsigned long &rulTimeStamp )
{
    m_StateDataGuard.Enter();
    m_sStatus.m_ulTimestampOfLastStatusUpdate = rulTimeStamp;
    m_StateDataGuard.Leave();

    switch ( reStatus )
    {
        case statusUpdatePingList:

            {
                m_StateDataGuard.Enter();
                bool bReply = m_bPulseFileListReplyRequested;
                m_bPulseFileListReplyRequested = false;
                m_StateDataGuard.Leave();

                if ( bReply )
                {
                    Reply( replyPingList );
                }
            }

            break;

        case statusUpdateSystemVersion:
            
            SetSoftwareVersion();
            break;

        default:

            // Nothing to do for now.

            break;
    }
}

void CSubsystem::SetPingDuration(   const float &rfPingDuration,
                                    const bool  &rbUpdateConfig )
{
    // Note: Ping duration and therefore ping rate is essentially a function for SBP and not SSS.
    //       For the SBP, we need to relate the ping duration back to the ping rate.

    ASSERT( IsSBP() );

    if ( IsSBP() )
    {
        // Ceil the ping period to >= 10 ms and convert to s.

        float fDuration = 0.001f * static_cast<float>( max( fabs( rfPingDuration ), 10.0 ) );

        // Compute the equivalent ping rate in Hz x 1000 as required by EdgeTech.

        long lPingRate = static_cast<long>( 1000.0f / fDuration + 0.5f );

        // Enact the command.

        SendCommand( SONAR_MESSAGE_PING_RATE, SONAR_COMMAND_SET, (BYTE *) &lPingRate, sizeof( lPingRate ) );

        // Query its setting.

        SendCommand( SONAR_MESSAGE_PING_RATE );

        // And, if necessary, update the percistent configuration.

        if ( rbUpdateConfig )
        {
            m_Config.m_fPingPeriod = rfPingDuration;
        }
    }
}

long CSubsystem::IdOfSubsystemType( const ESUBSYSTEMTYPE &reSubsystemType ) const
{
    // First, ensure the id table has been loaded from the registry (if present).

    LoadIdTable();

    long lSubsystemId = 0x20;    // EdgeTech default for SSS LF.

    for ( int iSubsystem = 0; iSubsystem < maxSubsystemTypes; iSubsystem++ )
    {
        if ( reSubsystemType == m_sSubsystemIdTable[ iSubsystem ].m_eType )
        {
            lSubsystemId = static_cast<long>( m_sSubsystemIdTable[ iSubsystem ].m_dwSubsystemId );
            break;
        }
    }

    return lSubsystemId;
}

inline
void CSubsystem::LoadIdTable( void ) const
{
    if ( ! m_bIdTableLoaded )
    {
        CRegKey Key;

        Key.Attach( HKEY_USERS );

        CString sKey;

        for ( int iSubsystem = 0; iSubsystem < maxSubsystemTypes; iSubsystem++ )
        {
            sKey.Format( "%s\\%s", lpszEdgeTechFSDWKey_c, m_sSubsystemIdTable[ iSubsystem ].m_szKey );

            if ( Key.Open( HKEY_USERS, sKey ) == ERROR_SUCCESS )
            {
                DWORD dwValue;

                if ( Key.QueryValue( dwValue, _T( "SubsystemId" ) ) == ERROR_SUCCESS )
                {
                    m_sSubsystemIdTable[ iSubsystem ].m_dwSubsystemId = dwValue;
                }

                Key.Close();
            }
        }

        m_bIdTableLoaded = true;
    }
}

bool CSubsystem::Reply( const EREPLYTYPE &reReplyType )
{
    // Formats and queues a 7k status message pertaining to the given reply type
    // then notifies the host that a message is available for further handling.
    // The host (typically 6046) should then handle the status message as appropriate.
    //
    // NB: Alarms and general replys are to be handled in the same manner.

    bool bSuccess = false;

    try
    {
        C7kStatus               Status;
        CString                 Message;
        C7kStatus::EMESSAGETYPE eMessageType = C7kStatus::messageTypeStatus;
        C7kStatus::EMODE        eMode        = C7kStatus::modeUnspecified;
        C7kStatus::EMESSAGEID   eMessageId   = C7kStatus::messageIdSensorReply;

        switch ( reReplyType )
        {
            case replyPingList:

                CString PulseFile;

                m_StateDataGuard.Enter();

                Message.Format( "PulseFiles: %d", m_sStatus.m_ulNumPulses );

                for ( unsigned long ulPulseFile = 0UL; ulPulseFile < m_sStatus.m_ulNumPulses; ulPulseFile++ )
                {
                    PulseFile.Format( ", %s", m_sStatus.m_asPulseFile[ ulPulseFile ].fileName );
                    Message += PulseFile;
                }

                m_StateDataGuard.Leave();

                break;
        }

        if ( ! Message.IsEmpty() )
        {
            // Format the 7k status message.

            if ( ! Status.Encode( eMessageType, eMode, eMessageId, Message, false ) )
            {
                ThrowMessage_m( "Error encoding 7k status message" );
            }

            // Now populate the 7k header as necessary.

            PRECORDHEADER7K ps7kHeader = reinterpret_cast<PRECORDHEADER7K>( static_cast<BYTE *>( Status ) );
            ASSERT( ps7kHeader != NULL );

            if ( ps7kHeader != NULL )
            {

#if   ( DRF_VERSION_7K == 2 )

                ps7kHeader->m_unSubsystemId = static_cast<unsigned short>( SubsystemId() );

#elif ( DRF_VERSION_7K == 1 )

                ps7kHeader->m_ulSubsystemId = static_cast<unsigned long>( SubsystemId() );

#else

                switch ( SubsystemType() )
                {
                    case subsystemSSSLF:

                        ps7kHeader->m_ulDeviceId = deviceIdEdgeTechFSDWLFSSS;
                        break;

                    case subsystemSSSHF:

                        ps7kHeader->m_ulDeviceId = deviceIdEdgeTechFSDWHFSSS;
                        break;

                    case subsystemSBP:

                        ps7kHeader->m_ulDeviceId = deviceIdEdgeTechFSDWSBP;
                        break;

                    default:

                        TRACE( _T( "CSubsystem::Reply(), Unknown system type hence device id unknown.\n" ) );
                        ps7kHeader->m_ulDeviceId = 0UL;
                        break;
                }

#endif

#if ( DRF_VERSION_7K >= 5 )

                ps7kHeader->m_unRecordsVersion = un7kRecordsVersion_c;

#endif

            }

            // Queue the message ready for subsequent retrieval...

            if ( ! Queue7kStatusRecord( (BYTE *) Status, (unsigned long) Status ) )
            {
                ThrowMessage_m( "Error queuing 7k status record" );
            }

            // and finally, notify the host that a status message is queued and is ready for retrieval.

            if ( ! NotifyHostOfReply() )
            {
                ThrowMessage_m( "Error notifying host of reply" );
            }

            // Reset the reply client index.

            m_iReplyClientIndex = -1;
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::Reply(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::Reply(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
void CSubsystem::SetSoftwareVersion( void )
{
    if ( SoftwareVersion() == -1.0f )
    {
        m_StateDataGuard.Enter();

        try
        {
            CString Version( m_sStatus.m_szVersion );

            if ( ! Version.IsEmpty() )
            {
                int iLength = Version.GetLength();
                int iPos    = 0;

                while ( ( iPos < iLength ) && ( isdigit( Version[ iPos ] ) == 0 ) )
                {
                    iPos++;
                }

                if ( iPos < iLength )
                {
                    m_fSoftwareVersion = static_cast<float>( ::atof( (char *) (LPCTSTR) Version.Right( iLength - iPos ) ) );
                }
            }
        }
        catch ( ... )
        {
            TRACE( _T( "CSubsystem::SetSoftwareVersion(), Exception caught\n" ) );
        }

        m_StateDataGuard.Leave();
    }
}

inline
float CSubsystem::SoftwareVersion( void ) const
{
    m_StateDataGuard.Enter();
    float fVersion = m_fSoftwareVersion;
    m_StateDataGuard.Leave();

    return fVersion;
}

template <typename tSonarChannel>
bool CSubsystem::SendMessage(   tSonarChannel      *pSonarChannel,
                                CEdgeTechMessage   &rMessage )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        if ( pSonarChannel != NULL )
        {
            bSuccess = pSonarChannel->SendCommand( rMessage );
        }
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSubsystem::SendMessage, Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
void CSubsystem::ScaleChannelData   (   short                   *pnDest,
                                        const short             *pnSource,
                                        const short             &rnWeightFactor,
                                        const unsigned long     &rulSamples )
{
    float fScaleFactor = (float) pow( 2.0, -rnWeightFactor );

    for ( unsigned long ulSample = 0UL; ulSample < rulSamples; ulSample++ )
    {
        pnDest[ ulSample ] = (short) ( fScaleFactor * ((float) pnSource[ ulSample ]) );
    }
}

inline
CString CSubsystem::PulseFileFromDescription( const CString &rPulseDescription )
{
    CString PulseFileName;

    DBG_UNREFERENCED_PARAMETER( rPulseDescription );

    //for ( unsigned long ulPing = 0; ulPing < pStatus->ulNumPingTypes; ulPing++ )
    //{
    //    CString ThisPulsesDescription = &(pStatus->asPingType[ ulPing ].description[ 0 ]);
    //
    //    if ( ThisPulsesDescription.Find( rPulseDescription ) != -1 )
    //    {
    //        PulseFileName = &(pStatus->asPingType[ ulPing ].fileName[ 0 ]);
    //        break;
    //    }
    //}

    return PulseFileName;
}

inline
void CSubsystem::HandleSonarError(  const CEdgeTechMessage  &rMessage,
                                    const unsigned long     &rulTimeStamp )
{
    UNREFERENCED_PARAMETER( rulTimeStamp );

    ASSERT( rMessage.SonarMessage() == SONAR_COMMAND_ERROR );

    if ( rMessage.SonarMessage() == SONAR_COMMAND_ERROR )
    {
        const long lErrorCode = static_cast<long>( rMessage );

        TRACE( _T( "CSubsystem::HandleSonarError(), Sonar error code (%ld) detected\n" ), lErrorCode );

        switch ( lErrorCode )
        {
            case SONAR_MESSAGE_ERROR_NONE:              // No error.
            case SONAR_MESSAGE_ERROR_FAILED:            // General command failure.
            case SONAR_MESSAGE_ERROR_DATA_SIZE:         // Message size mismatch.
            case SONAR_MESSAGE_ERROR_SUBSYSTEM:         // Subsystem number is not present.
            case SONAR_MESSAGE_ERROR_CHANNEL:           // Channel number is not present.
            case SONAR_MESSAGE_ERROR_UNKNOWN:           // Sonar message field contains unknown command.
            case SONAR_MESSAGE_ERROR_PULSE_FILE:        // Pulse file error... pulse not loaded.
            case SONAR_MESSAGE_ERROR_SESSION_ID:        // Session id error... command rejected.
            case SONAR_MESSAGE_ERROR_OVERRIDE_REQUIRED: // Override required... command rejected.

                // Nothing to do for now.

                break;

            default:

                // Nothing to do for now.

                break;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
// CSubsystem::STATUS class internal helpers.

inline
CSubsystem::tagSTATUS::tagSTATUS( void )
{
    Reset();
}

CSubsystem::tagSTATUS::~tagSTATUS( void )
{
}

inline
void CSubsystem::tagSTATUS::Reset( void )
{
    ::memset( this, 0x00, Size() );
}

inline
size_t CSubsystem::tagSTATUS::Size( void )
{
    return sizeof( STATUS );
}

