//
//  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:   Alarm.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    
//
//  Notes:      
//

#include "StdAfx.h"
#include "Alarm.h"

#include "..\Utils\NetUtils\SystemTime.h"
#include "..\Utils\NetUtils\7kStatus.h"

#include <algorithm>

#include <tchar.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CAlarm class implementation.

using std::find;
using std::vector;

CAlarm::CAlarm( void )
       :m_ulInvalidThreadId( static_cast<unsigned long>( -1 ) ),
        m_uiInvalidThreadMessage( static_cast<unsigned int> ( -1 ) )
{
    m_bNotification    = false;
    m_ulThreadToNotify = m_ulInvalidThreadId;
    m_uiThreadMessage  = m_uiInvalidThreadMessage;

    m_ActiveAlarmList.clear();
    m_ClearedAlarmList.clear();
}

CAlarm::~CAlarm( void )
{
    try
    {
        m_bNotification    = false;
        m_ulThreadToNotify = m_ulInvalidThreadId;
        m_uiThreadMessage  = m_uiInvalidThreadMessage;

        m_ActiveAlarmList.clear();
        m_ClearedAlarmList.clear();
    }
    catch ( ... )
    {
        TRACE( _T( "CAlarm::~CAlarm(), unspecified exception caught.\n" ) );
    }
}

void CAlarm::SetThreadNotification( const bool          &rbEnable,
                                    const unsigned long &rulThreadId,
                                    const unsigned int  &ruiMessageId )
{
    m_bNotification    = rbEnable;
    m_ulThreadToNotify = rulThreadId;
    m_uiThreadMessage  = ruiMessageId;
}

bool CAlarm::Report(    const EEVENTACTION &reEventAction,
                        const EALARMID     &reAlarmId,
                        const DWORD        &rdwTimestamp,
                        LPCTSTR             lpszMessage )
{
    // NB: this member will likely be exectued within the context of arbitrary threads and therefore
    // MUST provide thread safe access to shared resources.

    enum EACTION
    {
        actionNone,
        actionAdd,
        actionRemove
    };

    bool bSuccess = false;      // Assume failure for now.

    // Try to find the spcified id in the active alarm linked list. If it is either not in the active list or it 
    // active and goes inactive, then notify the main thread that there is a new alsrm state. The main thread will then
    // retrive the state of this alarm and notify connected remote hosts.

    bool bStateChanged            = false;
    int  iNotifyingSecondaryIndex = iUnspecified_c;

    m_Critical.Enter();

    try
    {
        // Here we search through our list of alarms. If the alarm is to be set and the alarm and secondary
        // indicies (if relevant) are in the list already, then there is nothing to do. Conversly, if the
        // alarm and seconday indicies (if relevant) are in the list and it's to be removed then flag it as such.

        EACTION             eAction         = actionNone;
        AlarmListIterator_t pAlarmItem      = m_ActiveAlarmList.end();
        int                 iSecondaryIndex = iUnspecified_c;
        bool                bSecondaryAlarm = false;

        if ( m_ActiveAlarmList.empty() )
        {
            if ( reEventAction == eventActionAlarmSet )
            {
                eAction = actionAdd;
            }
        }
        else
        {
            pAlarmItem = m_ActiveAlarmList.begin();

            if ( reEventAction == eventActionCleared )
            {
                while ( pAlarmItem != m_ActiveAlarmList.end() )
                {
                    bSecondaryAlarm = false;
                    iSecondaryIndex = SecondaryIndex( reAlarmId, lpszMessage, bSecondaryAlarm );

                    if ( IsAlarmMatch( reAlarmId, iSecondaryIndex, *pAlarmItem ) )
                    {
                        eAction = actionRemove;
                        break;
                    }
                    else
                    {
                        pAlarmItem++;
                    }
                }
            }
            else if ( reEventAction == eventActionAlarmSet )
            {
                eAction = actionAdd;

                while ( pAlarmItem != m_ActiveAlarmList.end() )
                {
                    bSecondaryAlarm = false;
                    iSecondaryIndex = SecondaryIndex( reAlarmId, lpszMessage, bSecondaryAlarm );

                    if ( IsAlarmMatch( reAlarmId, iSecondaryIndex, *pAlarmItem ) )
                    {
                        eAction = actionNone;
                        break;
                    }
                    else
                    {
                        pAlarmItem++;
                    }
                }
            }
        }

        switch ( eAction )
        {
            case actionNone:
                break;

            case actionAdd:

                {
                    bool bSecondaryAlarm = false;
                    const int iSecondaryIndex = SecondaryIndex( reAlarmId, lpszMessage, bSecondaryAlarm );
                    AddAlarm( reAlarmId, rdwTimestamp, lpszMessage, iSecondaryIndex );
                    iNotifyingSecondaryIndex = iSecondaryIndex;
                }

                bStateChanged = true;

                break;

            case actionRemove:

                ASSERT( pAlarmItem != m_ActiveAlarmList.end() );

                if ( pAlarmItem != m_ActiveAlarmList.end() )
                {
                    // Add our alarm to be deleted to the cleared list ahead of client notification.

                    m_ClearedAlarmList.push_back( *pAlarmItem );

                    m_ActiveAlarmList.erase( pAlarmItem );
                    iNotifyingSecondaryIndex = iSecondaryIndex;
                    bStateChanged = true;
                }

                break;
        }

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CAlarm::Report(), unspecified exception caught\n" ) );
    }

    m_Critical.Leave();

    if ( bStateChanged )
    {
        // Post a message back to client thread to handle changed alarm state.

        if ( ! NotifyClient( reAlarmId, iNotifyingSecondaryIndex ) )
        {
            bSuccess = false;
            TRACE( _T( "CAlarm::Report(), Failed to notify client thread of state change\n" ) );
        }
    }

    return bSuccess;
}

bool CAlarm::IsAlarmActive( const EALARMID &reAlarmId,
                            const int      &riSecondaryIndex )
{
    bool bActive = false;

    m_Critical.Enter();

    try
    {
        if ( m_ActiveAlarmList.empty() )
        {
            bActive = false;
        }
        else if ( riSecondaryIndex == iUnspecified_c )
        {
            bActive = ( find( m_ActiveAlarmList.begin(), m_ActiveAlarmList.end(), reAlarmId ) != m_ActiveAlarmList.end() );
        }
        else
        {
            for ( AlarmListIterator_t pAlarmItem = m_ActiveAlarmList.begin();
                    pAlarmItem != m_ActiveAlarmList.end();
                        pAlarmItem++ )
            {
                if ( IsAlarmMatch( reAlarmId, riSecondaryIndex, *pAlarmItem ) )
                {
                    bActive = true;
                    break;
                }
            }
        }
    }
    catch ( ... )
    {
        bActive = false;
        TRACE( _T( "CAlarm::IsAlarmActive(), unspecified exception caught\n" ) );
    }

    m_Critical.Leave();

    return bActive;
}

bool CAlarm::RetrieveAlarm( const EALARMID      &reAlarmId,
                            const int           &riSecondaryIndex,
                            ALARMENTRY * const   psAlarmEntry )
{
    bool bRetrieved = false;

    if ( psAlarmEntry != NULL )
    {
        AlarmListIterator_t pAlarmItem;

        ::memset( psAlarmEntry, 0x00, sizeof( ALARMENTRY ) );

        m_Critical.Enter();

        if ( m_ActiveAlarmList.empty() )
        {
            // Here we ignore the reAlarmId field and reply with an "all clear" alarm message.

            psAlarmEntry->m_dwTimestamp    = CSystemTime::GetTickCount();
            psAlarmEntry->m_eAlarmId       = alarmIdAllClear;
            psAlarmEntry->m_szMessage[ 0 ] = static_cast<TCHAR>( '\0' );
        }
        else if ( riSecondaryIndex == iUnspecified_c )
        {
            pAlarmItem = find( m_ActiveAlarmList.begin(), m_ActiveAlarmList.end(), reAlarmId );

            if ( pAlarmItem != m_ActiveAlarmList.end() )
            {
                ::memcpy( psAlarmEntry, static_cast<ALARMENTRY *>( *pAlarmItem ), sizeof( ALARMENTRY ) );
                bRetrieved = true;
            }
        }
        else
        {
            for ( pAlarmItem = m_ActiveAlarmList.begin(); pAlarmItem != m_ActiveAlarmList.end(); pAlarmItem++ )
            {
                if ( IsAlarmMatch( reAlarmId, riSecondaryIndex, *pAlarmItem ) )
                {
                    ::memcpy( psAlarmEntry, static_cast<ALARMENTRY *>( *pAlarmItem ), sizeof( ALARMENTRY ) );
                    bRetrieved = true;
                    break;
                }
            }
        }

        m_Critical.Leave();
    }

    return bRetrieved;
}

bool CAlarm::Retrieve7kAlarmRecord( const EALARMID         &reAlarmId,
                                    const int              &riSecondaryIndex,
                                    CDynamicBuffer<BYTE>   &rAlarmRecord )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        C7kStatus   AlarmRecord;
        ALARMENTRY sAlarmEntry;

        bool bEncode = false;

        rAlarmRecord.Reset();

        if ( IsAlarmActive( reAlarmId, riSecondaryIndex ) )
        {
            if ( RetrieveAlarm( reAlarmId, riSecondaryIndex, &sAlarmEntry ) )
            {
                ASSERT( sAlarmEntry.m_eAlarmId == reAlarmId );

                if ( ! AlarmRecord.Encode( C7kStatus::messageTypeAlarm,
                                                C7kStatus::modeAlarmActive,
                                                    reAlarmId,
                                                        sAlarmEntry.m_szMessage,
                                                            false ) )
                {
                    ThrowMessage_m( "Can't encode active alarm record" );
                }

                bEncode = true;
            }
        }
        else if ( IsAlarmCleared( reAlarmId, riSecondaryIndex ) )
        {
            if ( RetrieveClearedAlarm( reAlarmId, riSecondaryIndex, &sAlarmEntry, true ) )
            {
                ASSERT( sAlarmEntry.m_eAlarmId == reAlarmId );

                if ( ! AlarmRecord.Encode( C7kStatus::messageTypeAlarm,
                                                C7kStatus::modeAlarmClear,
                                                    reAlarmId,
                                                        sAlarmEntry.m_szMessage,
                                                            false ) )
                {
                    ThrowMessage_m( "Can't encode active alarm record" );
                }

                bEncode = true;
            }
        }
        else
        {
            if ( ! AlarmRecord.Encode(  C7kStatus::messageTypeAlarm,
                                            C7kStatus::modeAlarmClear,
                                                reAlarmId,
                                                    NULL,
                                                        false ) )
            {
                ThrowMessage_m( "Can't encode inactive alarm record" );
            }

            bEncode = true;
        }

        if ( bEncode )
        {
            const unsigned long ulRecordSize = static_cast<unsigned long>( AlarmRecord );

            if ( ulRecordSize > 0UL )
            {
                bSuccess = rAlarmRecord.Add( static_cast<BYTE *>( AlarmRecord ), sizeof( BYTE ) * ulRecordSize );
            }
        }
        else
        {
            bSuccess = true;
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CAlarm::Retrieve7kAlarmRecord(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CAlarm::Retrieve7kAlarmRecord(), unspecified exception caught, level 1\n" ) );
    }

    return bSuccess;
}

bool CAlarm::IsAlarmCleared(    const EALARMID &reAlarmId,
                                const int      &riSecondaryIndex )
{
    bool bCleared = false;

    m_Critical.Enter();

    try
    {
        if ( m_ClearedAlarmList.empty() )
        {
            bCleared = false;
        }
        else if ( riSecondaryIndex == iUnspecified_c )
        {
            bCleared = ( find( m_ClearedAlarmList.begin(), m_ClearedAlarmList.end(), reAlarmId ) != m_ClearedAlarmList.end() );
        }
        else
        {
            for ( AlarmListIterator_t pAlarmItem = m_ClearedAlarmList.begin();
                    pAlarmItem != m_ClearedAlarmList.end();
                        pAlarmItem++ )
            {
                if ( IsAlarmMatch( reAlarmId, riSecondaryIndex, *pAlarmItem ) )
                {
                    bCleared = true;
                    break;
                }
            }
        }
    }
    catch ( ... )
    {
        bCleared = false;
        TRACE( _T( "CAlarm::IsAlarmCleared(), unspecified exception caught\n" ) );
    }

    m_Critical.Leave();

    return bCleared;
}

bool CAlarm::RetrieveClearedAlarm(  const EALARMID      &reAlarmId,
                                    const int           &riSecondaryIndex,
                                    ALARMENTRY * const   psAlarmEntry,
                                    const bool          &rbRemoveAlarm )
{
    bool bRetrieved = false;

    ASSERT( psAlarmEntry != NULL );

    if ( psAlarmEntry != NULL )
    {
        AlarmListIterator_t pAlarmItem;

        ::memset( psAlarmEntry, 0x00, sizeof( ALARMENTRY ) );

        m_Critical.Enter();

        if ( m_ClearedAlarmList.empty() )
        {
            // Here we ignore the reAlarmId field and reply with an "all clear" alarm message.

            psAlarmEntry->m_dwTimestamp    = CSystemTime::GetTickCount();
            psAlarmEntry->m_eAlarmId       = alarmIdAllClear;
            psAlarmEntry->m_szMessage[ 0 ] = static_cast<TCHAR>( '\0' );
        }
        else if ( riSecondaryIndex == iUnspecified_c )
        {
            pAlarmItem = find( m_ClearedAlarmList.begin(), m_ClearedAlarmList.end(), reAlarmId );

            if ( pAlarmItem != m_ClearedAlarmList.end() )
            {
                ::memcpy( psAlarmEntry, static_cast<ALARMENTRY *>( *pAlarmItem ), sizeof( ALARMENTRY ) );
                bRetrieved = true;
            }

            if ( bRetrieved && rbRemoveAlarm )
            {
                m_ClearedAlarmList.erase( pAlarmItem );
            }
        }
        else
        {
            for ( pAlarmItem = m_ClearedAlarmList.begin(); pAlarmItem != m_ClearedAlarmList.end(); pAlarmItem++ )
            {
                if ( IsAlarmMatch( reAlarmId, riSecondaryIndex, *pAlarmItem ) )
                {
                    ::memcpy( psAlarmEntry, static_cast<ALARMENTRY *>( *pAlarmItem ), sizeof( ALARMENTRY ) );
                    bRetrieved = true;
                    break;
                }
            }

            if ( bRetrieved && rbRemoveAlarm )
            {
                m_ClearedAlarmList.erase( pAlarmItem );
            }
        }

        m_Critical.Leave();
    }

    return bRetrieved;
}

bool CAlarm::RetrieveAlarms( std::vector<ALARMENTRY> &rAlarmList )
{
    bool bSuccess = false;

    try
    {
        rAlarmList.clear();

        ALARMENTRY sAlarm;

        m_Critical.Enter();

        if ( m_ActiveAlarmList.empty() )
        {
            // Here we reply with the "all clear" alarm message.

            sAlarm.m_dwTimestamp    = CSystemTime::GetTickCount();
            sAlarm.m_eAlarmId       = alarmIdAllClear;
            sAlarm.m_szMessage[ 0 ] = static_cast<TCHAR>( '\0' );

            rAlarmList.push_back( sAlarm );
        }
        else
        {
            for ( AlarmListIterator_t pAlarmItem = m_ActiveAlarmList.begin();
                    pAlarmItem != m_ActiveAlarmList.end();
                        pAlarmItem++ )
            {
                ::memcpy( &sAlarm, static_cast<ALARMENTRY *>( *pAlarmItem ), sizeof( ALARMENTRY ) );

                rAlarmList.push_back( sAlarm );
            }
        }

        m_Critical.Leave();

        bSuccess = true;

    }
    catch ( ... )
    {
        bSuccess = false;
    }

    return bSuccess;
}

bool CAlarm::Set7kAlarmRecord(  ALARMENTRY * const      psAlarm,
                                CDynamicBuffer<BYTE>   &rAlarmRecord )
{
    bool bSuccess = false;      // Assume failure for now.

    try
    {
        rAlarmRecord.Reset();

        ASSERT( psAlarm != NULL );

        if ( psAlarm == NULL )
        {
            ThrowMessage_m( "psAlarm is NULL" );
        }

        C7kStatus AlarmRecord;

        if ( IsAlarmActive( psAlarm->m_eAlarmId, psAlarm->m_iSecondaryIndex ) )
        {
            if ( ! AlarmRecord.Encode( C7kStatus::messageTypeAlarm,
                                            C7kStatus::modeAlarmActive,
                                                psAlarm->m_eAlarmId,
                                                    psAlarm->m_szMessage,
                                                        false ) )
            {
                ThrowMessage_m( "Can't encode active alarm record" );
            }
        }
        else if ( ! AlarmRecord.Encode(  C7kStatus::messageTypeAlarm,
                                            C7kStatus::modeAlarmClear,
                                                alarmIdAllClear,
                                                    NULL,
                                                        false ) )
        {
            ThrowMessage_m( "Can't encode inactive alarm record" );
        }

        const unsigned long ulRecordSize = static_cast<unsigned long>( AlarmRecord );

        if ( ulRecordSize > 0UL )
        {
            bSuccess = rAlarmRecord.Add( static_cast<BYTE *>( AlarmRecord ), sizeof( BYTE ) * ulRecordSize );
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CAlarm::Set7kAlarmRecord(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CAlarm::Set7kAlarmRecord(), unspecified exception caught.\n" ) );
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// CAlarm's private members.

inline
void CAlarm::AddAlarm(  const EALARMID     &reAlarmId,
                        const DWORD        &rdwTimestamp,
                        LPCTSTR             lpszMessage,
                        const int          &riSecondaryIndex )
{
    CEntry sAlarm;
    sAlarm.Set( reAlarmId, rdwTimestamp, lpszMessage, riSecondaryIndex );
    m_ActiveAlarmList.push_back( sAlarm );

    {
        ALARMENTRY *psAlarm = static_cast<ALARMENTRY *>( &sAlarm );

        TRACE( _T( "Adding alarm: %d, %d, \"%s\"\n" ),  psAlarm->m_eAlarmId,
                                                        psAlarm->m_iSecondaryIndex,
                                                        psAlarm->m_szMessage );
    }
}

inline
bool CAlarm::NotifyClient(  const EALARMID  &reAlarmId,
                            const int       &riSecondaryIndex )
{
    bool bSuccess = true;

    if ( m_bNotification )
    {
        if ( ( m_ulThreadToNotify == m_ulInvalidThreadId ) || ( m_uiThreadMessage == m_uiInvalidThreadMessage ) )
        {
            bSuccess = false;
        }
        else
        {
            bSuccess = ( ::PostThreadMessage(   m_ulThreadToNotify, 
                                                    m_uiThreadMessage, 
                                                        static_cast<WPARAM>( reAlarmId ),
                                                            static_cast<LPARAM>( riSecondaryIndex ) ) != FALSE );
        }
    }

    return bSuccess;
}

inline
int CAlarm::SecondaryIndex( const EALARMID &reAlarmId,
                            LPCTSTR         lpszMessage,
                            bool           &rbSecondaryAlarm )
{
    int iSecondaryIndex = iUnspecified_c;
    
    rbSecondaryAlarm = false;

    if ( lpszMessage != NULL )
    {
        switch ( reAlarmId )
        {
            case alarmIdSensorNotResponding:                     // Sensor with given index is not responding.
            case alarmIdPingDropped:                             // Pings have been dropped by subsystem.
            case alarmIdSensorReportedFailure:                   // Sensor reported failure.
            case alarmIdSensorUnhealthy:                         // Sensor has reported itself as being unhealthy.
            case alarmIdSensorFailedToStart:                     // Sensor failed to start.
            case alarmIdSensorFailedToShutdown:                  // Sensor failed to shutdown.
            case alarmIdSensorFailedToLoadData:                  // Sensor failed to load its data.
            case alarmIdSensorFailedToSetTime:                   // Failed to set sensor's system time.
            case alarmIdSensorFailedToRoute7kMessage:            // Failed to route 7k message to sensor.
            case alarmIdSensorCommandFailed:                     // Sensor failed to handle remote command.
            case alarmIdSensorStatusRetrievalFailed:             // Failed to retrieve status information for given sensor.
            case alarmIdResetFailed:                             // Sensor reset failed.

                {
                    int iTempIndex = iUnspecified_c;

                    if ( ::_stscanf( lpszMessage, "%d", &iTempIndex ) == 1 )
                    {
                        iSecondaryIndex = iTempIndex;
                    }

                    rbSecondaryAlarm = true;
                }

                break;
        }
    }

    return iSecondaryIndex;
}

inline
bool CAlarm::IsAlarmMatch(  const EALARMID     &reAlarmId,
                            const int          &riSecondaryIndex,
                            ALARMENTRY * const  psAlarmEntry )
{
    bool bMatch = false;

    if ( psAlarmEntry != NULL )
    {
        if ( reAlarmId == psAlarmEntry->m_eAlarmId )
        {
            bool bSecondaryAlarm = false;
            int iSecondaryIndex  = SecondaryIndex( reAlarmId, psAlarmEntry->m_szMessage, bSecondaryAlarm );

            bMatch = ( ( ! bSecondaryAlarm ) || ( bSecondaryAlarm && ( iSecondaryIndex == riSecondaryIndex ) ) );
        }
    }

    return bMatch;
}

///////////////////////////////////////////////////////////////////////////////
// CAlarm::CEntry helper class implementation.

CAlarm::CEntry::CEntry( void )
               :tagALARMENTRY()
{
}

CAlarm::CEntry::~CEntry( void )
{
}

void CAlarm::CEntry::Set(   const EALARMID         &reAlarmId,
                            const DWORD            &rdwTimestamp,
                            LPCTSTR                 lpszMessage,
                            const int              &riSecondaryIndex )
{
    m_eAlarmId          = reAlarmId;
    m_dwTimestamp       = rdwTimestamp;
    m_iSecondaryIndex   = riSecondaryIndex;

    if ( ( lpszMessage == NULL ) || ( ::lstrlen( lpszMessage ) <= 0 ) )
    {
        ::memset( &m_szMessage[ 0 ], 0x00, ( sizeof( TCHAR ) * g_dwMaxAlarmMessageLength ) + 1 );
    }
    else
    {
        ::lstrcpyn( m_szMessage, lpszMessage, g_dwMaxAlarmMessageLength );
    }
}

CAlarm::CEntry::operator ALARMENTRY * ( void ) const
{
    return static_cast<::ALARMENTRY *>( const_cast<CAlarm::CEntry *>( this ) );
}

bool CAlarm::CEntry::operator == ( const EALARMID &reAlarmId ) const
{
    return ( m_eAlarmId == reAlarmId );
}

bool CAlarm::CEntry::operator < ( const CEntry &rRhs ) const
{
    return ( m_eAlarmId < rRhs.m_eAlarmId );
}

