//
//  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:   7kFile.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    Implementation file for the C7kFile class.
//
//  Notes:      
//

#include "StdAfx.h"
#include "7kFile.h"
#include "7kProtocol.h"

// Internal constants.

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// C7kFile class implementation.

C7kFile::C7kFile( void )
        :m_pszVersion( _T( "1.00.00" ) )
{
    ManageDllState_m();

    m_eLastError        = errorNone;
    m_hFile             = INVALID_HANDLE_VALUE;

    m_FileName.Empty();
}

C7kFile::C7kFile( const C7kFile &rRhs )                     // Not implemented.
        :m_pszVersion( _T( "1.00.00" ) )
{
    ManageDllState_m();

    UNREFERENCED_PARAMETER( rRhs );

    m_eLastError        = errorNone;
    m_hFile             = INVALID_HANDLE_VALUE;

    m_FileName.Empty();

    ASSERT( false );
}

C7kFile::~C7kFile( void )
{
    ManageDllState_m();

    Close();
}

C7kFile & C7kFile::operator = ( const C7kFile &rRhs )       // Not implemented.
{
    ManageDllState_m();

    UNREFERENCED_PARAMETER( rRhs );

    m_eLastError        = errorNone;

    m_hFile             = INVALID_HANDLE_VALUE;

    ASSERT( false );

    return *this;
}

bool C7kFile::Open( LPCTSTR             lpszFileName,
                    const EACCESSMODE  &reAccessMode )
{
    ManageDllState_m();

    bool    bSuccess = false;           // Assume failure for now.

    try
    {
        DWORD dwAccess = ( ( reAccessMode == accessModeWrite ) ? GENERIC_WRITE : GENERIC_READ );

        // Ensure any previously open file is closed first.

        Close();

        // Validate the file name and, if invalid, use a default for writing or its an error
        // for reading.

        if ( IsFileNameValid( lpszFileName ) )
        {
            m_FileName = lpszFileName;
        }
        else if ( reAccessMode == accessModeWrite )
        {
            m_FileName = BuildDefaultFileName();
        }
        else
        {
            throw errorInvalidFileName;
        }

        // Open the new file by name.

        m_hFile = ::CreateFile( m_FileName,
                                dwAccess,
                                FILE_SHARE_READ,
                                NULL,
                                OPEN_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
                                NULL );

        if ( IsInvalidFileHandle() )
        {
            TRACE( _T( "C7kFile::Open(), Win32 error code: %lu\n" ), ::GetLastError() );
            throw errorInvalidHandle;
        }

        // For writing, make sure we go to the end of the file, conversly, we go to the beginning
        // of the file.

        if ( reAccessMode == accessModeWrite )
        {
            SetEndOfFile();
        }
        else
        {
            SetBeginningOfFile();
        }

        bSuccess = true;
    }
    catch ( const E7KFILEERROR &reError )
    {
        bSuccess = false;
        m_eLastError = reError;
        TRACE( _T( "C7kFile::Open(), %d\n" ), reError );
    }
    catch ( ... )
    {
        bSuccess = false;
        m_eLastError = errorUnspecifiedException;
        TRACE( _T( "C7kFile::Open(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool C7kFile::Close( void )
{
    ManageDllState_m();

    if ( IsValidFileHandle() )
    {
        // Close the file and invalidate the handle.

        ::FlushFileBuffers( m_hFile );
        ::CloseHandle( m_hFile );

        m_hFile = INVALID_HANDLE_VALUE;
    }

    m_eLastError = errorNone;

    m_FileName.Empty();

    return IsInvalidFileHandle();
}

bool C7kFile::IsFileOpen( void ) const
{
    ManageDllState_m();

    return IsValidFileHandle();
}

///////////////////////////////////////////////////////////////////////////////
// Write and read public methods.

bool C7kFile::Write( BYTE *pbyRecord, const unsigned long &rulBytesToWrite )
{
    ManageDllState_m();

    // Writes a record to the end of the currently openned file. Note, this routine
    // assumes that the given stream is a valid 7k record and no validation is conducted
    // here.

    bool bSuccess = false;                      // Assume failure for now.

    try
    {
        if ( rulBytesToWrite == 0UL )
        {
            bSuccess = true;                    // Nothing to do.
        }
        else if ( pbyRecord != NULL )
        {
            if ( IsInvalidFileHandle() )
            {
                throw errorInvalidHandle;
            }

            DWORD dwBytesWritten = 0;

            if ( WriteFile( m_hFile, pbyRecord, rulBytesToWrite, &dwBytesWritten, NULL ) )
            {
                bSuccess = ( dwBytesWritten == (DWORD) rulBytesToWrite );
            }
            else
            {
                TRACE( _T( "C7kFile::Write(), WriteFile() failed with error code %lu\n"), ::GetLastError() );
                throw errorWrite;
            }
        }
        else
        {
            throw errorInvalidParameter;
        }
    }
    catch ( const E7KFILEERROR &reError )
    {
        bSuccess = false;
        m_eLastError = reError;
        TRACE( _T( "C7kFile::Write(), Error detected : %d\n" ), reError );
    }
    catch ( ... )
    {
        bSuccess = false;
        m_eLastError = errorUnspecifiedException;
        TRACE( _T( "C7kFile::Write(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool C7kFile::Read( BYTE                *pbyRecord,
                    unsigned long       &rulBytesRead,
                    const unsigned long &rulMaxBufferSize,
                    const bool          &rbUseChecksum )
{
    ManageDllState_m();

    bool bSuccess = false;                                                      // Assume failure for now.

    rulBytesRead = 0UL;

    try
    {
        DWORD       dwBytesRead         = 0;
        const DWORD dwRecordHeaderSize  = (DWORD) CRecordHeader7k::Size();      // Buffer must be at least as big as the frame (header + checksum).
        const DWORD dwSizeOfChecksum    = (DWORD) sizeof( unsigned long );      // Size of the checksum field.

        ASSERT( pbyRecord != NULL );
        ASSERT( rulMaxBufferSize > dwRecordHeaderSize );

        if ( ( pbyRecord == NULL ) || ( rulMaxBufferSize <= dwRecordHeaderSize ) )
        {
            throw errorInvalidParameter;
        }

        // Validate the file handle.

        ASSERT( IsValidFileHandle() );

        if ( IsInvalidFileHandle() )
        {
            throw errorInvalidHandle;
        }

        // Read the record header.

        if ( ::ReadFile( m_hFile, &pbyRecord[ 0 ], dwRecordHeaderSize, &dwBytesRead, NULL ) )
        {
            if ( dwBytesRead < dwRecordHeaderSize )
            {
                throw _T( "Can't read enough bytes for header... possibly at end of file" );
            }
        }
        else
        {
            throw _T( "Can't read file header" );
        }

        rulBytesRead += (unsigned long) dwBytesRead;
    
        // Use the CRecordHeader7k helper class to decode and validate it.

        CRecordHeader7k Record( pbyRecord, dwBytesRead );
    
        if ( ! Record.IsValid() )
        {
            throw _T( "Header is invalid" );
        }

        // Extract the expected dynamic size and check to see if the given input buffer is large enough.

        const unsigned long dwDynamicBytes = (DWORD) Record.DynamicBytes();

        ASSERT( rulMaxBufferSize >= ( dwRecordHeaderSize + dwDynamicBytes + dwSizeOfChecksum ) );

        if ( rulMaxBufferSize < ( dwRecordHeaderSize + dwDynamicBytes + dwSizeOfChecksum ) )
        {
            throw _T( "Input buffer too small for record" );
        }

        // Read the optional dynamic data section.

        if ( dwDynamicBytes > 0UL )
        {
            if ( ::ReadFile( m_hFile, &pbyRecord[ dwRecordHeaderSize ], dwDynamicBytes, &dwBytesRead, NULL ) )
            {
                // Check to see if we're at the end of file or otherwise can't read enough bytes.
                // We do this checking to see if the bytes read match the dynamic bytes requested.

                if ( dwBytesRead < dwDynamicBytes )
                {
                    throw _T( "Can't read enough bytes... possibly at end of file" );
                }

                rulBytesRead += (unsigned long) dwBytesRead;
            }
            else
            {
                throw _T( "Can't read dynamic data" );
            }
        }

        // Finally, read the checksum field

        if ( ::ReadFile( m_hFile, &pbyRecord[ dwRecordHeaderSize + dwDynamicBytes ], dwSizeOfChecksum, &dwBytesRead, NULL ) )
        {
            if ( dwBytesRead < dwSizeOfChecksum )
            {
                throw _T( "Can't read enough bytes for the checksum... possibly at end of file." );
            }

            rulBytesRead += (unsigned long) dwBytesRead;
        }
        else
        {
            throw _T( "Can't read checksum" );
        }

        // Finally, validate the checksum as appropriate.

        if ( ( bSuccess = ( ( ! rbUseChecksum ) || ( rbUseChecksum && Record.IsValidChecksum() ) ) ) != true )
        {
            throw _T( "Invalid checksum" );
        }
    }
    catch ( LPCTSTR lpszError )
    {
        bSuccess = false;
        m_eLastError = errorRead;
        TRACE( _T( "C7kFile::Read(), %s \n" ), lpszError );
    }
    catch ( const E7KFILEERROR &reError )
    {
        bSuccess = false;
        m_eLastError = reError;
        TRACE( _T( "C7kFile::Read(), Error detected : %d\n" ), reError );
    }
    catch ( ... )
    {
        bSuccess = false;
        m_eLastError = errorUnspecifiedException;
        TRACE( _T( "C7kFile::Read(), Unspecified exception caught\n" ) );
    }

    return bSuccess;
}

LPCTSTR C7kFile::GetVersion( void ) const
{
    // Returns the version string of this class.

    ManageDllState_m();

    return m_pszVersion;
}

C7kFile::E7KFILEERROR C7kFile::GetLastError( void ) const
{
    // Returns the last error.

    ManageDllState_m();

    return m_eLastError;
}

LPCTSTR C7kFile::FileName( void ) const
{
    // Returns a pointer to the file name string of the last openned file...
    // may or may not be valid.

    ManageDllState_m();

    return m_FileName;
}

bool C7kFile::Flush( void )
{
    ManageDllState_m();

    bool bSuccess = false;

    if ( IsValidFileHandle() )
    {
        bSuccess = ( ::FlushFileBuffers( m_hFile ) == TRUE );
    }

    return bSuccess;
}

double C7kFile::FileSize( void )
{
    ManageDllState_m();

    if ( IsValidFileHandle() )
    {
        DWORD   dwHighSize;
        DWORD   dwLowSize = ::GetFileSize( m_hFile, &dwHighSize );

        return ( (double) ( dwLowSize + dwHighSize * 4294967295UL) / 1048576.0 );
    }

    return 0.0;
}

///////////////////////////////////////////////////////////////////////////////
// External helper statics.

CString C7kFile::BuildDefaultFileName( void )
{
    ManageDllState_m();

    CString         FileName;
    SYSTEMTIME      sSystemTime;

    ::GetLocalTime( &sSystemTime );

    // Format the default (recommended) format "YYYYMMDD_HHMMSS.s7k"

    FileName.Format( "%04d%02d%02d_%02d%02d%02d.s7k",   sSystemTime.wYear,
                                                        sSystemTime.wMonth,
                                                        sSystemTime.wDay,
                                                        sSystemTime.wHour,
                                                        sSystemTime.wMinute,
                                                        sSystemTime.wSecond );
    return FileName;
}

bool C7kFile::IsFileNameValid( LPCTSTR lpszFileName )
{
    ManageDllState_m();

    bool bNameValid = false;

    CString FileName( lpszFileName );

    if ( ! FileName.IsEmpty() )
    {
        // Validate the file name here.

#ifdef DEVELOPMENT_PHASE_2
#pragma CompileMessage_m( "TODO: Add further validation" )
#endif

        bNameValid = true;
    }

    return bNameValid;
}


///////////////////////////////////////////////////////////////////////////////
// Internal private helpers; as such, they don't need to invoke ManageDllState_m()

inline
bool C7kFile::IsValidFileHandle( void ) const
{
    return ( ( m_hFile != NULL ) && ( m_hFile != INVALID_HANDLE_VALUE ) );
}

inline
bool C7kFile::IsInvalidFileHandle( void ) const
{
    return ( ! IsValidFileHandle() );
}

inline
void C7kFile::SetEndOfFile( void )
{
    ASSERT( IsValidFileHandle() );

    ::SetFilePointer( m_hFile, 0, 0, FILE_END );
}

inline
void C7kFile::SetBeginningOfFile( void )
{
    ASSERT( IsValidFileHandle() );

    ::SetFilePointer( m_hFile, 0, 0, FILE_BEGIN );
}

