//
//  Copyright © 2004, 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:   PLCConfiguration.cpp
//
//  Project:    6046.
//
//  Author(s):  W. Arcus
//
//  Purpose:    Implements the class CPLCConfiguration to read and parse the new
//              xml based sensor and module configuration.
//
//  Notes:
//

#pragma warning( disable : 4786 )       // Disable this little gem from Microsoft. Keep
                                        // before the first header is included.

#include "StdAfx.h"
#include "PLCConfiguration.h"

//////////////////////////////////////////////////////////////////////
// CPLCConfigurationuration class implementation starting with the 
// public interface.

using std::vector;
using std::string;

CPLCConfiguration::CPLCConfiguration( void )
{
    try
    {
        m_iSearchStartPosition = 0;

        m_XML.Empty();
        m_ModuleInfo.clear();
        m_SensorInfo.clear();
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCConfiguration::CPLCConfiguration(), Unspecified exception caught!\n" ) );
    }
}

CPLCConfiguration::~CPLCConfiguration( void )
{
    try
    {
        m_SensorInfo.clear();
        m_ModuleInfo.clear();
        m_XML.Empty();

        m_iSearchStartPosition = 0;
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCConfiguration::~CPLCConfiguration(), Unspecified exception caught!\n" ) );
    }
}

void CPLCConfiguration::SetSearchPosition( const int &riPosition )
{
    m_iSearchStartPosition = riPosition;
}

bool CPLCConfiguration::ParseFile( const CString &rsFullFileName )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        SetSearchPosition( 0 );

        m_XML.Empty();

        if ( rsFullFileName.IsEmpty() )
        {
            ThrowMessage_m( "Invalid file name" );
        }
        else
        {
            bool  bRethrow  = false;
            FILE *fpXMLFile = NULL;

            try
            {
                if ( ( fpXMLFile = ::fopen( static_cast<LPCTSTR>( rsFullFileName ), "r" ) ) != NULL )
                {
                    // Read the XML file into a working buffer for parsing.

                    char cInputCharacter;

                    while ( ( cInputCharacter = static_cast<char>( fgetc( fpXMLFile ) ) ) != EOF )
                    {
                        m_XML += _T( cInputCharacter );
                    }

                    // Parse the XML string into the relevant module vector info.

                    if ( m_XML.IsEmpty() )
                    {
                        ThrowMessage_m( "File is empty" )
                    }
                    else if ( LocateTag( "RESONPayloadController", true ) )
                    {
                        unsigned long   ulAttribute;
                        int             iAttribute;
                        CString         sType;
                        CString         sAttribute;

                        if ( LocateTag( "InterfaceList", true ) )
                        {
                            unsigned long   ulModules   = 0UL;
                            MODULEINFO      sModuleInfo;

                            if ( Attribute( "Modules", "Number", ulAttribute ) )
                            {
                                ulModules = ulAttribute;
                            }

                            m_ModuleInfo.clear();

                            for ( unsigned long ulModule = 0UL; ulModule < ulModules; ulModule++ )
                            {
                                Clear( sModuleInfo );

                                sType.Format( "Module%lu", ulModule );

                                if ( LocateTag( sType, false ) )
                                {
                                    if ( Attribute( sType, "Id", ulAttribute ) )
                                    {
                                        sModuleInfo.m_ulModuleNumber = ulAttribute;
                                    }

                                    if ( Attribute( sType, "Name", sAttribute ) )
                                    {
                                        sModuleInfo.m_ModuleName = static_cast<LPCTSTR>( sAttribute );
                                    }

                                    if ( TagText( sType, sAttribute ) )
                                    {
                                        sModuleInfo.m_ModuleDescription = static_cast<LPCTSTR>( sAttribute );
                                    }
                                }

                                m_ModuleInfo.push_back( sModuleInfo );
                            }

                            bSuccess = true;
                        }
                        else
                        {
                            ThrowMessage_m( "Can't locate XML header tags" );
                        }

                        SetSearchPosition( 0 );

                        if ( LocateTag( "SensorList", true ) )
                        {
                            unsigned long   ulSensors   = 0UL;
                            SENSORINFO      sSensorInfo;

                            if ( Attribute( "Sensors", "Number", ulAttribute ) )
                            {
                                ulSensors = ulAttribute;
                            }

                            m_SensorInfo.clear();

                            for ( unsigned long ulSensor = 0UL; ulSensor < ulSensors; ulSensor++ )
                            {
                                Clear( sSensorInfo );

                                sType.Format( "Sensor%lu", ulSensor );

                                if ( LocateTag( sType, false ) )
                                {
                                    if ( Attribute( sType, "ModuleId", ulAttribute ) )
                                    {
                                        sSensorInfo.m_ulModuleNumber = ulAttribute;
                                    }

                                    if ( Attribute( sType, "SubsystemId", ulAttribute ) )
                                    {
                                        sSensorInfo.m_ulSubsystemId = ulAttribute;
                                    }

                                    if ( Attribute( sType, "StartDelay", iAttribute ) )
                                    {
                                        sSensorInfo.m_iDelay = iAttribute;
                                    }

                                    if ( Attribute( sType, "DeviceId", iAttribute ) )
                                    {
                                        sSensorInfo.m_ulDeviceId = static_cast<unsigned long>( iAttribute );
                                    }

                                    if ( Attribute( sType, "Enumerator", iAttribute ) )
                                    {
                                        sSensorInfo.m_unSystemEnumerator = static_cast<unsigned short>( iAttribute );
                                    }

                                    if ( Attribute( sType, "FailRestartSeconds", iAttribute ) )
                                    {
                                        sSensorInfo.m_iFailRestartPeriod = static_cast<unsigned short>( iAttribute );
                                    }

                                    if ( TagText( sType, sAttribute ) )
                                    {
                                        sSensorInfo.m_SensorDescription = static_cast<LPCTSTR>( sAttribute );
                                    }
                                }

                                m_SensorInfo.push_back( sSensorInfo );
                            }
                        }
                    }
                }
            }
            catch ( ... )
            {
                bRethrow = true;
            }

            // Close the file.

            if ( fpXMLFile != NULL )
            {
                ::fclose( fpXMLFile );
                fpXMLFile = NULL;
            }

            if ( bRethrow )
            {
                throw;
            }
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CPLCConfiguration::ParseFile(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CPLCConfiguration::ParseFile(), Unspecified exception caught!\n" ) );
    }

    return bSuccess;
}

unsigned long CPLCConfiguration::NumberOfModules( void ) const
{
    return static_cast<unsigned long>( m_ModuleInfo.size() );
}

unsigned long CPLCConfiguration::ModuleNumber( const unsigned long &rulModuleIndex ) const
{
    ASSERT( ! m_ModuleInfo.empty() );
    ASSERT( rulModuleIndex <= m_ModuleInfo.size() );

    return (&m_ModuleInfo[ rulModuleIndex ])->m_ulModuleNumber;
}

LPCTSTR CPLCConfiguration::ModuleName( const unsigned long &rulModuleIndex ) const
{
    ASSERT( ! m_ModuleInfo.empty() );
    ASSERT( rulModuleIndex <= m_ModuleInfo.size() );

    return static_cast<LPCTSTR>( (&m_ModuleInfo[ rulModuleIndex ])->m_ModuleName.c_str() );
}

LPCTSTR CPLCConfiguration::ModuleDescription ( const unsigned long &rulModuleIndex ) const
{
    ASSERT( ! m_ModuleInfo.empty() );
    ASSERT( rulModuleIndex <= m_ModuleInfo.size() );

    return static_cast<LPCTSTR>( (&m_ModuleInfo[ rulModuleIndex ])->m_ModuleDescription.c_str() );
}

unsigned long CPLCConfiguration::NumberOfSensors( void ) const
{
    return static_cast<unsigned long>( m_SensorInfo.size() );
}

unsigned long CPLCConfiguration::SensorModuleId( const unsigned long  &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_ulModuleNumber;
}

unsigned long CPLCConfiguration::SubsystemId( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_ulSubsystemId;
}

int CPLCConfiguration::Delay( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_iDelay;
}

unsigned long  CPLCConfiguration::DeviceId( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_ulDeviceId;
}

unsigned short CPLCConfiguration::Enumerator( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_unSystemEnumerator;
}

int CPLCConfiguration::FailRestart( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return (&m_SensorInfo[ rulSensorIndex ])->m_iFailRestartPeriod;
}

LPCTSTR CPLCConfiguration::SensorDescription( const unsigned long &rulSensorIndex ) const
{
    ASSERT( ! m_SensorInfo.empty() );
    ASSERT( rulSensorIndex <= m_SensorInfo.size() );

    return static_cast<LPCTSTR>( (&m_SensorInfo[ rulSensorIndex ])->m_SensorDescription.c_str());
}

bool CPLCConfiguration::WriteFile( const CString &rsFileName, SENSORINFO * const psSensorInfo, const unsigned long &rulSensors )
{
    bool    bSuccess = false;
    HANDLE  hFile    = NULL;

    try
    {
        CString sFileEntry;
        DWORD   dwBytesWritten;

        hFile = ::CreateFile( rsFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
        
        if ( hFile == INVALID_HANDLE_VALUE )
        {
            ThrowMessage_m( "Can't open specified file" );
        }

        // First, write out the file header and PLC headers...

        sFileEntry = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\r\n<RESONPayloadController>\r\n";

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(), &dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        // next, the interface (module) list...

        sFileEntry.Format( " <InterfaceList>\r\n    <Modules Number=\"%u\">Number of sensor interface modules in listed sequence</Modules>\r\n", m_ModuleInfo.size() );

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(), &dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        for ( unsigned long ulModule = 0UL; ulModule < m_ModuleInfo.size(); ulModule++ )
        {
            PMODULEINFO psModule = &m_ModuleInfo[ ulModule ];

            sFileEntry.Format( "    <Module%lu Id=\"%lu\" Name=\"%s\">%s</Module%lu>\r\n",  ulModule,
                                                                                            psModule->m_ulModuleNumber,
                                                                                            psModule->m_ModuleName.c_str(),
                                                                                            psModule->m_ModuleDescription.c_str(),
                                                                                            ulModule );

            if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
            {
                ThrowMessage_m( "Can't write to file" );
            }
        }

        sFileEntry = _T( " </InterfaceList>\r\n" );

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        // then the sensor list...

        sFileEntry.Format( " <SensorList>\r\n    <Sensors Number=\"%lu\">Number of sensors to load in listed sequence</Sensors>\r\n", rulSensors );

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        for ( unsigned long ulSensor = 0UL; ulSensor < rulSensors; ulSensor++ )
        {
            sFileEntry.Format( "    <Sensor%lu ModuleId=\"%lu\" SubsystemId=\"%d\" StartDelay=\"%d\" DeviceId=\"%d\" Enumerator=\"%d\" FailRestartSeconds=\"%d\">%s</Sensor%lu>\r\n",
                                    ulSensor,
                                        psSensorInfo[ ulSensor ].m_ulModuleNumber,
                                            psSensorInfo[ ulSensor ].m_ulSubsystemId,
                                                psSensorInfo[ ulSensor ].m_iDelay,
                                                    psSensorInfo[ ulSensor ].m_ulDeviceId,
                                                        psSensorInfo[ ulSensor ].m_unSystemEnumerator,
                                                            psSensorInfo[ ulSensor ].m_iFailRestartPeriod,
                                                                psSensorInfo[ ulSensor ].m_SensorDescription.c_str(),
                                                                    ulSensor );

            if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
            {
                ThrowMessage_m( "Can't write to file" );
            }
        }

        sFileEntry.Format( " </SensorList>\r\n" );

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        sFileEntry = _T( "</RESONPayloadController>\r\n" );

        if ( ! ::WriteFile( hFile, sFileEntry, sFileEntry.GetLength(),&dwBytesWritten, NULL ) )
        {
            ThrowMessage_m( "Can't write to file" );
        }

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
    }

    if ( hFile != NULL )
    {
        ::CloseHandle( hFile );
        hFile = NULL;
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// Private members.

inline
bool CPLCConfiguration::LocateTag( const CString &sTag, const bool &rbResetSearchPosition )
{
    // Adjust the start of search pointer to just following the specified tag, if possible.

    if ( rbResetSearchPosition )
    {
        SetSearchPosition( 0 );
    }

    int iStart  = m_iSearchStartPosition;
    int iStop   = iStart;

    if ( FindTag( sTag, iStart, iStop ) )
    {
        m_iSearchStartPosition = iStart;
        return true;
    }

    return false;
}

inline
bool CPLCConfiguration::TagText( const CString &rsTag, CString &rsText )
{
    rsText.Empty();

    int  iStart = m_iSearchStartPosition;
    int  iStop  = iStart;

    if ( FindTag( rsTag, iStart, iStop ) )
    {
        iStart = iStop;
        rsText = ExtractTagText( iStart );
    }

    return ( ! rsText.IsEmpty() );
}

inline
bool CPLCConfiguration::Attribute( const CString &rsTag, const CString &rsField, CString &rsAttribute )
{
    int iStart = m_iSearchStartPosition;
    int iStop  = iStart;

    rsAttribute.Empty();

    if ( FindTag( rsTag, iStart, iStop ) )
    {
        iStart = iStop;

        if ( FindAttribute( rsField, iStart, iStop ) )
        {
            iStart = iStop;
            rsAttribute = ExtractAttributeData( iStart );
        }
    }

    return ( ! rsAttribute.IsEmpty() );
}

inline
bool CPLCConfiguration::Attribute( const CString &rsTag, const CString &rsField, unsigned long &rulAttribute )
{
    CString sAttribute;

    if ( Attribute( rsTag, rsField, sAttribute ) )
    {
        rulAttribute = static_cast<unsigned long>( ::atol( static_cast<LPCTSTR>( sAttribute ) ) );
        return true;
    }

    return false;
}

inline
bool CPLCConfiguration::Attribute( const CString &rsTag, const CString &rsField, int &riAttribute )
{
    CString sAttribute;

    if ( Attribute( rsTag, rsField, sAttribute ) )
    {
        riAttribute = ::atoi( static_cast<LPCTSTR>( sAttribute ) );
        return true;
    }

    return false;
}

inline
bool CPLCConfiguration::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 );

            if ( ( iTagStart = m_XML.Find( SearchPattern, riStart ) ) != -1 )
            {
                riStart   = iTagStart;
                riStop    = iTagStart + SearchPattern.GetLength();
                bTagFound = true;
            }
        }
    }
    catch ( ... )
    {
        riStart   = 0;
        riStop    = 0;
        bTagFound = false;
    }

    return bTagFound;
}

bool CPLCConfiguration::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 );

            if ( ( iAttributeStart = m_XML.Find( SearchPattern, riStart ) ) != -1 )
            {
                riStart   = iAttributeStart;
                riStop    = iAttributeStart + SearchPattern.GetLength();
                bAttributeFound = true;
            }
        }
    }
    catch ( ... )
    {
        riStart = 0;
        riStop  = 0;
        bAttributeFound = false;
    }

    return bAttributeFound;
}

CString CPLCConfiguration::ExtractAttributeData( const int &riStart )
{
    CString  sAttributeData;

    for ( int iChar = riStart; iChar < m_XML.GetLength(); iChar++ )
    {
        if ( ( m_XML[ iChar ] == '\"' ) || ( m_XML[ iChar ] == '>' ) )
        {
            break;
        }
        else
        {
            sAttributeData += m_XML[ iChar ];
        }
    }

    // Trim off leading white space.

    sAttributeData.TrimLeft();

    ASSERT( ! sAttributeData.IsEmpty() );

    return sAttributeData;
}

CString CPLCConfiguration::ExtractTagText( const int &riStart )
{
    CString sTagText;

    int iTagTextStart = m_XML.Find( ">",  riStart );
    int iTagTextStop  = m_XML.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 += m_XML[ iChar ];
        }
    }

    // Now trim off leading white space.

    sTagText.TrimLeft();

    ASSERT( ! sTagText.IsEmpty() );

    return sTagText;
}

void CPLCConfiguration::Clear( MODULEINFO &rsModuleInfo )
{
    rsModuleInfo.m_ulModuleNumber       = 0UL;
    rsModuleInfo.m_ModuleName           = "";
    rsModuleInfo.m_ModuleDescription    = "";
}

void CPLCConfiguration::Clear( SENSORINFO &rsSensorInfo )
{
    rsSensorInfo.m_ulModuleNumber       = 0UL;
    rsSensorInfo.m_ulSubsystemId        = 0UL;
    rsSensorInfo.m_iDelay               = 0;
    rsSensorInfo.m_ulDeviceId           = 0UL;
    rsSensorInfo.m_unSystemEnumerator   = 0;
    rsSensorInfo.m_SensorDescription    = "";
    rsSensorInfo.m_iFailRestartPeriod   = 0;
}
