//
//  Copyright © 2003, 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:   SystemInfo.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    CSystemInfo class implementation.
//
//  Notes:      
//

#include "StdAfx.h"
#include "SystemInfo.h"
#include "7kSonarMessages.h"

#include "..\..\..\Utils\NetUtils\7kProtocol.h"

#include <memory>

namespace                                                                                       // Begin anonymous namespace.
{
    using std::auto_ptr;

    class CXMLParser
    {
    public:

        ///////////////
        // Services.

                                CXMLParser          (   const char             *pcXMLMessage,
                                                        const unsigned long    &rulLength );

        virtual                ~CXMLParser          (   void );

        bool                    IsValid             (   void )  const;

        CString                 SystemText          (   void );

        unsigned long           DeviceId            (   void );
        unsigned short          SubsystemID         (   void );
        unsigned short          SystemEnumerator    (   void );

        bool                    ParameterRange      (   LPCTSTR                 lpszParameter,
                                                        float                  &rfMin,
                                                        float                  &rfMax );
    private:

        ///////////////
        // Attributes.

        bool                    m_bValid;
        auto_ptr<CString>       m_psXML;

        ///////////////
        // Services.

        CString                 AttributeText       (   LPCTSTR                 lpszTag,
                                                        LPCTSTR                 lpszAttribute );

        CString                 TagText             (   LPCTSTR                 lpszTagName );

        CString                 ExtractAttributeData(   const int               &riStart );

        CString                 ExtractTagText      (   const int               &riStart );

        bool                    FindTag             (   LPCTSTR                 lpszTag,
                                                        int                     &riStart,
                                                        int                     &riStop );

        bool                    FindAttribute       (   LPCTSTR                 lpszAttribute,
                                                        int                     &riStart,
                                                        int                     &riStop  );

                                CXMLParser          (   void );                                 // Not implemented thus private.
                                CXMLParser          (   const CXMLParser        &rRhs );        // Not implemented thus private.
        CXMLParser &            operator =          (   const CXMLParser        &rRhs );        // Not implemented thus private.

    };

#pragma pack( push, SYSTEMINFO_CPP_PACK, 1 )

    typedef struct tagDEVICEHEADER
    {
        unsigned long           m_ulMagicNumber;                                                // Unique identifier number.
        BYTE                    m_byDescription[ 64 ];                                          // ASCII string.
        unsigned __int64        m_ui64SerialNumber;                                             // 
        unsigned long           m_ulInfoLength;                                                 // In Bytes.

        // NB: a dynamic number of bytes (of size m_InfoLength) apparently follows each header.
    }
    DEVICEHEADER, *PDEVICEHEADER;

#pragma pack( pop, SYSTEMINFO_CPP_PACK )

}                                                                                               // End anonymous namespace.

///////////////////////////////////////////////////////////////////////////////
// CSystemInfo class implementation.

using namespace N7kSonarMessages;

CSystemInfo::CSystemInfo( void )
{
    m_ui64SonarId       = 0;
    m_ulNumberOfDevices = 0UL;
    m_DeviceInfoTable.clear();
}

CSystemInfo::CSystemInfo( const CSystemInfo &rRhs )
{
    m_ui64SonarId       = rRhs.m_ui64SonarId;
    m_ulNumberOfDevices = rRhs.m_ulNumberOfDevices;
    m_DeviceInfoTable   = rRhs.m_DeviceInfoTable;
}

CSystemInfo::~CSystemInfo( void )
{
    m_DeviceInfoTable.clear();
    m_ulNumberOfDevices = 0UL;
    m_ui64SonarId       = 0;
}

CSystemInfo & CSystemInfo::operator = ( const CSystemInfo &rRhs )
{
    m_ui64SonarId       = rRhs.m_ui64SonarId;
    m_ulNumberOfDevices = rRhs.m_ulNumberOfDevices;
    m_DeviceInfoTable   = rRhs.m_DeviceInfoTable;

    return *this;
}

void CSystemInfo::DecodeDeviceInfo( const BYTE *pby7kRecord )
{
    try
    {
        ASSERT( pby7kRecord != NULL );

        if ( pby7kRecord != NULL )
        {
            m_DeviceInfoTable.clear();

            const CONFIGURATIONRTH *pRTH = reinterpret_cast<const CONFIGURATIONRTH *>( pby7kRecord + tagRECORDHEADER7K::Size() );

            m_ui64SonarId       = pRTH->m_ui64SonarId;
            m_ulNumberOfDevices = pRTH->m_ulNumberOfDevices;

            const BYTE *pbyStartOfHeader = pby7kRecord + tagRECORDHEADER7K::Size() + sizeof( CONFIGURATIONRTH );

            for ( unsigned long ulDevice = 0UL; ulDevice < pRTH->m_ulNumberOfDevices; ulDevice++ )
            {
                const DEVICEHEADER *psHeader = reinterpret_cast<const DEVICEHEADER *>( pbyStartOfHeader );

                DEVICEINFO sDeviceInfo;

                if ( DecodeXMLInfo( reinterpret_cast<const char *>( pbyStartOfHeader + sizeof( DEVICEHEADER ) ), psHeader->m_ulInfoLength, sDeviceInfo ) )
                {
                    m_DeviceInfoTable.push_back( sDeviceInfo );
                }

                pbyStartOfHeader += ( sizeof( DEVICEHEADER ) + psHeader->m_ulInfoLength );
            }

            ASSERT( m_DeviceInfoTable.size() == m_ulNumberOfDevices );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CSystemInfo::DecodeDeviceInfo(), Unspecified exception caught\n" ) );
        ASSERT( false );
    }
}

CSystemInfo::DeviceInfo_t CSystemInfo::DeviceInfo( void ) const
{
    return m_DeviceInfoTable;
}

bool CSystemInfo::DecodeXMLInfo(    const char          *pcXMLMessage,
                                    const unsigned long &rulLength,
                                    DEVICEINFO          &rDeviceInfo )
{
    bool bSuccess = false;

    if ( ( rulLength > 0UL ) && ( pcXMLMessage != NULL ) )
    {
        CXMLParser XML( pcXMLMessage, rulLength );

        if ( XML.IsValid() )
        {
            rDeviceInfo.m_Name                  = static_cast<const char *>( static_cast<LPCTSTR>( XML.SystemText() ) );
            rDeviceInfo.m_ulDeviceId            = XML.DeviceId();
            rDeviceInfo.m_unSubsystemId         = XML.SubsystemID();
            rDeviceInfo.m_unSystemEnumerator    = XML.SystemEnumerator();

            XML.ParameterRange( "Range",         rDeviceInfo.m_fMinRange,       rDeviceInfo.m_fMaxRange       );
            XML.ParameterRange( "Gain",          rDeviceInfo.m_fMinGain,        rDeviceInfo.m_fMaxGain        );
            XML.ParameterRange( "Power",         rDeviceInfo.m_fMinPower,       rDeviceInfo.m_fMaxPower       );
            XML.ParameterRange( "TxPulseLength", rDeviceInfo.m_fMinPulseLength, rDeviceInfo.m_fMaxPulseLength );
            XML.ParameterRange( "PingRate",      rDeviceInfo.m_fMinPingRate,    rDeviceInfo.m_fMaxPingRate    );
        }

        bSuccess = true;
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// CXMLParser class implementation -- an internal helper.

CXMLParser::CXMLParser( const char          *pcXMLMessage,
                        const unsigned long &rulLength )
{
    try
    {
        m_psXML  = auto_ptr<CString>( new CString( pcXMLMessage, rulLength ) );
        m_bValid = ( m_psXML.get() != NULL );
    }
    catch ( ... )
    {
        m_bValid = false;
    }

    ASSERT( m_bValid );
}

CXMLParser::~CXMLParser( void )
{
    m_bValid = false;

    if ( m_psXML.get() != NULL )
    {
        delete ( m_psXML.release() );
        m_psXML = auto_ptr<CString>( NULL );
    }
}

bool CXMLParser::IsValid( void ) const
{
    return m_bValid;
}

CString CXMLParser::SystemText ( void )
{
    return TagText( "Name" );
}

unsigned long  CXMLParser::DeviceId( void )
{
    CString sDeviceId = AttributeText( "Name", "deviceid" );

    if ( ! sDeviceId.IsEmpty() )
    {
        return static_cast<unsigned long>( atol( static_cast<LPCTSTR>( sDeviceId ) ) );
    }

    ASSERT( false );

    return 0UL;
}

unsigned short CXMLParser::SubsystemID( void )
{
    CString sSubsystemId = AttributeText( "Name", "subsystemid" );

    if ( ! sSubsystemId.IsEmpty() )
    {
        return static_cast<unsigned short>( atoi( static_cast<LPCTSTR>( sSubsystemId ) ) );
    }

    ASSERT( false );

    return 0;
}

unsigned short CXMLParser::SystemEnumerator( void )
{
    CString sEnumerator = AttributeText( "Name", "enumerator" );

    if ( ! sEnumerator.IsEmpty() )
    {
        return static_cast<unsigned short>( atoi( static_cast<LPCTSTR>( sEnumerator ) ) );
    }

    ASSERT( false );

    return 0;
}

bool CXMLParser::ParameterRange(    LPCTSTR     lpszParameter,
                                    float      &rfMin,
                                    float      &rfMax )
{
    CString sMin,
            sMax;

    sMin = AttributeText( lpszParameter, "min" );
    sMax = AttributeText( lpszParameter, "max" );

    if ( ( ! sMin.IsEmpty() ) && ( ! sMax.IsEmpty() ) )
    {
        rfMin = static_cast<float>( atof( static_cast<LPCTSTR>( sMin ) ) );
        rfMax = static_cast<float>( atof( static_cast<LPCTSTR>( sMax ) ) );
        return true;
    }

    return false;
}

CString CXMLParser::AttributeText( LPCTSTR lpszTag, LPCTSTR lpszAttribute )
{
    CString sAttribute;
    int     iStart = 0;
    int     iStop  = 0;

    if ( FindTag( lpszTag, iStart, iStop ) )
    {
        iStart = iStop;

        if ( FindAttribute( lpszAttribute, iStart, iStop ) )
        {
            iStart = iStop;

            sAttribute = ExtractAttributeData( iStart );
        }
    }

    ASSERT( ! sAttribute.IsEmpty() );

    return sAttribute;
}

CString CXMLParser::TagText( LPCTSTR lpszTag )
{
    CString sTagData;
    int     iStart = 0;
    int     iStop  = 0;

    if ( FindTag( lpszTag, iStart, iStop ) )
    {
        iStart = iStop;

        sTagData = ExtractTagText( iStart );
    }

    ASSERT( ! sTagData.IsEmpty() );

    return sTagData;
}

bool CXMLParser::FindTag(   LPCTSTR     lpszTag,
                            int        &riStart,
                            int        &riStop )
{
    bool bTagFound = false;

    try
    {
        riStart = max( 0, riStart );
        riStop  = riStart;

        if ( lpszTag != NULL )
        {
            int     iTagStart;
            CString SearchPattern;

            SearchPattern.Format( "<%s ", lpszTag );

            ASSERT( m_psXML.get() != NULL );

            if ( ( iTagStart = m_psXML->Find( SearchPattern, riStart ) ) != -1 )
            {
                riStart   = iTagStart;
                riStop    = iTagStart + SearchPattern.GetLength();
                bTagFound = true;
            }
        }
    }
    catch ( ... )
    {
        riStart   = 0;
        riStop    = 0;
        bTagFound = false;
    }

    return bTagFound;
}

bool CXMLParser::FindAttribute( LPCTSTR     lpszAttribute,
                                int        &riStart,
                                int        &riStop )
{
    bool bAttributeFound = false;

    try
    {
        riStart = max( 0, riStart );
        riStop  = max( riStart, riStop );

        if ( lpszAttribute != NULL )
        {
            int     iAttributeStart;
            CString SearchPattern;

            SearchPattern.Format( "%s=\"", lpszAttribute );

            ASSERT( m_psXML.get() != NULL );

            if ( ( iAttributeStart = m_psXML->Find( SearchPattern, riStart ) ) != -1 )
            {
                riStart   = iAttributeStart;
                riStop    = iAttributeStart + SearchPattern.GetLength();
                bAttributeFound = true;
            }
        }
    }
    catch ( ... )
    {
        riStart = 0;
        riStop  = 0;
        bAttributeFound = false;
    }

    return bAttributeFound;
}

CString CXMLParser::ExtractAttributeData( const int &riStart )
{
    CString  sAttributeData;

    ASSERT( m_psXML.get() != NULL );

    const CString &rXML = *(m_psXML.get());

    for ( int iChar = riStart; iChar < rXML.GetLength(); iChar++ )
    {
        if ( ( rXML[ iChar ] == '\"' ) || ( rXML[ iChar ] == '>' ) )
        {
            break;
        }
        else
        {
            sAttributeData += rXML[ iChar ];
        }
    }

    ASSERT( ! sAttributeData.IsEmpty() );

    return sAttributeData;
}

CString CXMLParser::ExtractTagText( const int &riStart )
{
    CString sTagText;

    ASSERT( m_psXML.get() != NULL );

    const CString &rXML = *(m_psXML.get());

    int iTagTextStart = rXML.Find( ">",  riStart );
    int iTagTextStop  = rXML.Find( "</", riStart );

    // Extract the text between these two pointers, if valid.

    if ( ( iTagTextStart != -1 ) && ( iTagTextStop != -1 ) )
    {
        for ( int iChar = iTagTextStart + 1; iChar < iTagTextStop; iChar++ )
        {
            sTagText += rXML[ iChar ];
        }
    }

    ASSERT( ! sTagText.IsEmpty() );

    return sTagText;
}

