//
//  Copyright © 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:   7kFileHeader.cpp
//
//  Project:    6046.
//
//  Author(s):  W. Arcus
//
//  Purpose:    
//
//  Notes:      

#include "StdAfx.h"
#include "7kFileHeader.h"

namespace                           // Begin unnamed namespace.
{

// RESON File id is GUID 0xF3302F43CFB04d6fA93E2AEC33DF577D
// Note sizeof( GUID ) = 16 bytes = 128 bits.

static const GUID sRESONFileId_c =  {
                                        0xf3302f43,
                                        0xcfb0,
                                        0x4d6f,
                                        { 0xa9, 0x3e, 0x2a, 0xec, 0x33, 0xdf, 0x57, 0x7d }
                                    };

}                                   // End unnamed namespace.

//////////////////////////////////////////////////////////////////////
// C7kFileHeader class implementation.

C7kFileHeader::C7kFileHeader( void )
              :C7kProtocol(),
               m_ulRecordHeaderSize( sizeof( RECORDHEADER7K ) ),
               m_ulRecordType( 7200UL )
{
    ManageDllState_m();
}

C7kFileHeader::~C7kFileHeader( void )
{
    ManageDllState_m();
}

bool C7kFileHeader::Decode( PBYTE                   pbyStream,
                            const unsigned long    &rulTotalBytes,
                            const bool             &rbUseChecksum,
                            PRECORDTYPEHEADER      &rpsRecordTypeHeader,
                            PRECORDDATAHEADER      &rpsRecordDataHeader,
                            PSUBSYSTEMENTRY        &rpsSystemEntryTable )
{
    ManageDllState_m();

    bool bSuccess = false;          // Assume false for now.

    rpsRecordTypeHeader  = NULL;
    rpsRecordDataHeader  = NULL;
    rpsSystemEntryTable  = NULL;

    try
    {
        if ( rulTotalBytes == 0UL )
        {
            bSuccess = true;
        }
        else if ( pbyStream != NULL )
        {
            RECORDHEADER7K *psRecordHeader = NULL;
            PBYTE           pbyDynamicData = NULL;
            unsigned long   ulDynamicBytes = 0UL;

            // Pull the record out of the 7k record header.

            if ( ! DecodeRecord( pbyStream, rulTotalBytes, rbUseChecksum, psRecordHeader, pbyDynamicData, ulDynamicBytes ) )
            {
                ThrowMessage_m( "Can't decode record" );
            }

            if ( ( pbyDynamicData == NULL ) || ( ulDynamicBytes == 0UL )  )
            {
                ThrowMessage_m( "Invalid dynamic data portion of message" );
            }

            if ( psRecordHeader->m_ulRecordType != m_ulRecordType )
            {
                ThrowMessage_m( "Unexpected record type" );
            }

            // Next decode the record type header.

            rpsRecordTypeHeader = reinterpret_cast<PRECORDTYPEHEADER>( pbyDynamicData );

            if ( rpsRecordTypeHeader->m_ulRecordDataSize > 0UL )
            {
                rpsRecordDataHeader = reinterpret_cast<PRECORDDATAHEADER>( pbyDynamicData + sizeof( RECORDTYPEHEADER ) );

                if ( rpsRecordTypeHeader->m_ulNumberOfSubsystem > 0UL )
                {
                    rpsSystemEntryTable = reinterpret_cast<PSUBSYSTEMENTRY>( pbyDynamicData + sizeof( RECORDTYPEHEADER ) + sizeof( RECORDDATAHEADER ) );
                }
            }

            bSuccess = true;
        }
        else
        {
            bSuccess = false;
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "C7kFileHeader::Decode(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "C7kFileHeader::Decode(), Unexpected exception caught\n" ) );
    }

    return bSuccess;
}

bool C7kFileHeader::Encode(   const unsigned long  &rulNumberOfSubsystems,
                              const PSUBSYSTEMENTRY psSubsystemEntryTable,
                              const bool           &rbUseCheckSum,               // = false
                              const unsigned short &runFormatVersion,            // = 1
                              const unsigned long  &rulSessionId,                // = 0
                              LPCTSTR               lpszProgramName,             // = NULL
                              LPCTSTR               lpszProgramVersion,          // = NULL
                              LPCTSTR               lpszUserName,                // = NULL
                              LPCTSTR               lpszNotes,                   // = NULL
                              const TIME7K         *ps7kTime )                   // = NULL

{
    ManageDllState_m();

    bool bSuccess = false;

    PBYTE pby7kRecord = NULL;

    try
    {
        const unsigned long ulRecordDataSize =  sizeof( RECORDDATAHEADER ) +
                                                    ( rulNumberOfSubsystems * sizeof( SUBSYSTEMENTRY ) );

        const unsigned long ulRecordSize     =  sizeof( RECORDHEADER7K ) +
                                                    sizeof( RECORDTYPEHEADER ) +
                                                        ulRecordDataSize +
                                                            Checksum7kSize_m();

        pby7kRecord = new BYTE [ ulRecordSize ];

        if ( pby7kRecord == NULL )
        {
            ThrowMessage_m( "Failed to allocate memory for 7k file header record." );
        }

        // Get a pointer to the 7k record header and intiailize it with sensible defaults.

        RECORDHEADER7K *psHeader = reinterpret_cast<PRECORDHEADER7K>( pby7kRecord );

        SetDefaultHeader( psHeader );

        if ( ps7kTime != NULL )
        {
            ASSERT( ps7kTime->IsValid() );
            psHeader->m_sTime7k = *ps7kTime;
        }

        // Explicitly assign parameters to the header based on the data to follow.

        psHeader->m_ulSize                      = ulRecordSize;                                                   // 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.

        psHeader->m_ulOffsetToOptionalData      = 0UL;                                                            // Data offset          u32 Offset in bytes to optional data field from start of record. Zero implies no optional data.
        psHeader->m_ulOptionalDataIdentifier    = 0UL;                                                            // Data idenfitifer     u32 Identifier for optional data field. Zero for no optional field. This identifier is described with each record type.

        psHeader->m_ulRecordType                = m_ulRecordType;                                                 // u32 Unique identifier of indicating the type of data embedded in this record.

        psHeader->m_ulDeviceId                  = 0UL;                                                            // u32 Identifier of the device that this data pertains.

#if ( DRF_VERSION_7K <= 2 )

#if ( DRF_VERSION_7K == 1 )

        psHeader->m_ulSubsystemId               = 0UL;

#else

        psHeader->m_unSubsystemId               = 0U;                                                             // u32 Identifier for the device subsystem

#endif

        psHeader->m_ulDataSetNumber             = 0UL;                                                            // u32 Data set number.

#endif

#if ( DRF_VERSION_7K >= 5 )

        psHeader->m_unRecordsVersion            = un7kRecordsVersion_c;                                           // u16 Record version within a given verion of the protocol.

#endif

        psHeader->m_ulRecordNumber              = 0UL;                                                            // u32 Sequential record counter.

        // Now initialize the record type header.

        PRECORDTYPEHEADER psRTH = reinterpret_cast<PRECORDTYPEHEADER>( pby7kRecord + sizeof( RECORDHEADER7K ) );

        ::memset( psRTH, 0x00, sizeof( RECORDTYPEHEADER ) );

        ::memcpy( &(psRTH->m_byRecordId[ 0 ]), &sRESONFileId_c, sizeof( GUID ) );

        *(reinterpret_cast<unsigned long *>( &(psRTH->m_bySessionId[ 0 ]) )) = rulSessionId;

        psRTH->m_unFormatVersion     = runFormatVersion;
        psRTH->m_ulRecordDataSize    = ulRecordDataSize;
        psRTH->m_ulNumberOfSubsystem = rulNumberOfSubsystems;

        // Now the record data header.

        PRECORDDATAHEADER psRDH = reinterpret_cast<PRECORDDATAHEADER>( reinterpret_cast<PBYTE>( psRTH ) + sizeof( RECORDTYPEHEADER ) );

        ::memset( psRDH, 0x00, sizeof( RECORDDATAHEADER ) );

        SetString( &(psRDH->m_szRecordingProgramName[ 0 ]),    lpszProgramName,     64  );
        SetString( &(psRDH->m_szRecordingProgramVersion[ 0 ]), lpszProgramVersion,  16  );
        SetString( &(psRDH->m_szUserDefinedName[ 0 ]),         lpszUserName,        64  );
        SetString( &(psRDH->m_szNotes[ 0 ]),                   lpszNotes,           128 );

        // Finally, fill in the entries of the subsystem table.

        if ( ( rulNumberOfSubsystems > 0UL ) && ( psSubsystemEntryTable != NULL ) )
        {
            PSUBSYSTEMENTRY psEntry = reinterpret_cast<PSUBSYSTEMENTRY>( (reinterpret_cast<PBYTE>( psRDH )) + sizeof ( RECORDDATAHEADER ) );

            for ( unsigned long ulSubsystem = 0; ulSubsystem < rulNumberOfSubsystems; ulSubsystem++ )
            {
                psEntry[ ulSubsystem ] = psSubsystemEntryTable[ ulSubsystem ];
            }
        }

        bSuccess = EncodeRecord( psHeader, reinterpret_cast<PBYTE> ( psRTH ), sizeof( RECORDTYPEHEADER ) + ulRecordDataSize, rbUseCheckSum );
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "C7kFileHeader::Encode(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "C7kFileHeader::Encode(), unspecified exception caught\n" ) );
    }

    if ( pby7kRecord != NULL )
    {
        delete [] pby7kRecord;
        pby7kRecord = NULL;
    }

    return bSuccess;
}

C7kFileHeader::operator BYTE * ( void ) const
{
    ManageDllState_m();

    ASSERT( m_pRecord.get() != NULL );

    return static_cast<BYTE *>(m_pRecord->GetAt( 0 ));
}

C7kFileHeader::operator unsigned long ( void ) const
{
    ManageDllState_m();

    // Size in bytes of entire record.

    return EncodedBytes();
}

///////////////////////////////////////////////////////////////////////////////
// Internal private helpers.

inline
void C7kFileHeader::SetDefaultHeader( RECORDHEADER7K *psRecordHeader )
{
    // Set sensible defaults for the 7k command header.

    if ( psRecordHeader != NULL )
    {
        ::memset( psRecordHeader, 0x00, m_ulRecordHeaderSize );

        psRecordHeader->m_unVersion                 = un7kDataProtocolVersion_c;                                     // u16 Version of this frame (e.g.: 1, 2, …)
        psRecordHeader->m_unOffset                  = static_cast<unsigned short>( m_ulRecordHeaderSize - 
                                                        ( 2 * sizeof( unsigned short ) ) );                             // 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.
        psRecordHeader->m_ulSyncPattern             = 0x0000ffff;                                                       // u32 0x0000FFFF
        psRecordHeader->m_ulSize                    = 0UL;                                                              // 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.

        psRecordHeader->m_ulOffsetToOptionalData    = 0UL;                                                              // Data offset          u32 Offset in bytes to optional data field from start of record. Zero implies no optional data.
        psRecordHeader->m_ulOptionalDataIdentifier  = 0UL;                                                              // Data idenfitifer     u32 Identifier for optional data field. Zero for no optional field. This identifier is described with each record type.

        SetDateTime( psRecordHeader );

        psRecordHeader->m_ulRecordType              = 0UL;                                                            // u32 Unique identifier of indicating the type of data embedded in this record.
        psRecordHeader->m_ulDeviceId                = 0UL;                                                            // u32 Identifier of the device that this data pertains.

#if ( DRF_VERSION_7K <= 2 )

#if ( DRF_VERSION_7K == 1 )

        psRecordHeader->m_ulSubsystemId             = 0UL;

#else

        psRecordHeader->m_unSubsystemId             = 0U;                                                             // u32 Identifier for the device subsystem

#endif

        psRecordHeader->m_ulDataSetNumber           = 0UL;                                                            // u32 Data set number.

#endif

        psRecordHeader->m_ulRecordNumber            = 0UL;                                                            // u32 Sequential record counter.

#if ( DRF_VERSION_7K <= 2 )

        psRecordHeader->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.
        psRecordHeader->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 )

        psRecordHeader->m_unRecordsVersion          = un7kRecordsVersion_c;                                           // u16 Record version within a given verion of the protocol.

#endif

        psRecordHeader->m_unFlags                   = 0x0000;                                                         // u16 BIT FIELD: Bit 1 - Valid Checksum
    }
}

inline
void C7kFileHeader::SetString( LPTSTR lpszDestination, LPCTSTR lpszSource, const int &riMaxSize )
{
    if ( ( lpszSource       != NULL ) &&
         ( lpszDestination  != NULL ) &&
         ( riMaxSize        >  1 )     )
    {
        int iLength = ::lstrlen( lpszSource );

        if ( ( lpszSource != NULL ) && ( iLength > 0 ) )
        {
            int iAdjustedLength = iLength;

            if ( iLength >= riMaxSize )
            {
                iAdjustedLength = riMaxSize - 1;
            }

            ::lstrcpyn( lpszDestination, lpszSource, iAdjustedLength + 1 );

            lpszDestination[ iAdjustedLength ] = '\0';
        }
    }
}
