#include "StdAfx.h"
#include "PLCAlarmSim.h"
#include "PLCAlarmSimDlg.h"
#include "IPSetup.h"

#include "..\..\Include\SurveyEventTypes.h"

#include <deque>

using std::auto_ptr;

namespace                           // Begin unnamed namespace.
{

    const unsigned long             g_ulMaxFormatSize = 100UL;
    const unsigned long             g_ulMaxLabelSize  = 100UL;

    typedef std::deque<CString>     StatusMessageQueue_t;
    typedef StatusMessageQueue_t    StatusMessageQueueIterator_t;

    typedef struct tagALARMFORMAT
    {
        EALARMID    m_eAlarmId;
        char        m_szLabel       [ g_ulMaxLabelSize  ];
        char        m_szFormat      [ g_ulMaxFormatSize ];
    }
    ALARMFORMAT;

    static const ALARMFORMAT g_asAlarmFormat[] = 
    {
        { alarmIdDiskSpaceCritical,             "Disk Space Critical",                  "%s"                }, // <description>                                     - system's disk space <= 10% of capacity.
        { alarmIdSensorNotResponding,           "Sensor Not Responding",                "%d, %s"            }, // <sensor id, description>                          - a given sensor has not responded for a predefined period.
        { alarmIdPingDropped,                   "Ping Dropped",                         "%d, %ld, %ld"      }, // <sensor id, start ping number, stop ping number>  - specified ping number or range of pings have been dropped by the subsystem. Note: -1 if field is invalid.
        { alarmIdSensorReportedFailure,         "Sensor Reported Failure",              "%d, %d, %d, %s"    }, // <sensor id, cause, internal code, description>    - an internal sensor error has been detected. An internal code of -1 means that the sensor code is unknown.
        { alarmIdSensorUnhealthy,               "Sensor Unhealthy",                     "%d, %s"            }, // <sensor id, description>                          -
        { alarmIdSensorFailedToStart,           "Sensor Failed To Start",               "%d, %s"            },
        { alarmIdSensorFailedToShutdown,        "Sensor Failed To Shutdown",            "%d, %s"            },
        { alarmIdSensorFailedToLoadData,        "Sensor Failed To LoadData",            "%d, %s"            },
        { alarmIdSensorFailedToSetTime,         "Sensor Failed To SetTime",             "%d, %s"            },
        { alarmIdSensorFailedToRoute7kMessage,  "Sensor Failed To Route 7k Message",    "%d, %d, %s"        },
        { alarmIdSensorCommandFailed,           "Sensor Command Failed",                "%d, %d, %s"        },
        { alarmIdSensorStatusRetrievalFailed,   "Sensor Status Retrieval Failed",       "%d, %s"            },
        { alarmIdResetFailed,                   "Reset Failed",                         "%d, %s"            },
        { alarmIdInvalidCommand,                "Invalid Command",                      "%d, %d, %s"        }
    };

    StatusMessageQueue_t            g_StatusMessageQueue;

}                                   // end unnamed namespace.

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    //{{AFX_MSG(CAboutDlg)
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)
        // No message handlers
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPLCAlarmSimDlg dialog

CPLCAlarmSimDlg::CPLCAlarmSimDlg(CWnd* pParent /*=NULL*/)
                :CDialog(CPLCAlarmSimDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CPLCAlarmSimDlg)
    m_Address = _T("");
    m_Port = _T("");
    m_AlarmData = _T("");
    m_ReadbackText = _T("");
	m_AlarmFormat = _T("");
	//}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

    m_pRemote       = auto_ptr<C6046RemoteClient>( NULL );
    m_pSystemTime   = auto_ptr<CSystemTime>( NULL );
    m_bConnected    = false;
    m_ulPort        = 6046UL;
}

CPLCAlarmSimDlg::~CPLCAlarmSimDlg( void )
{
    if ( m_bConnected )
    {
        ASSERT( m_pRemote.get() != NULL );

        if ( ! m_pRemote->Disconnect() )
        {
            TRACE( _T( "CPLCAlarmSimDlg::~CPLCAlarmSimDlg(), m_pRemote->Disconnect() failed\n" ) );
        }

        m_bConnected = false;
    }

    if ( m_pRemote.get() != NULL )
    {
        delete ( m_pRemote.release() );
        m_pRemote = auto_ptr<C6046RemoteClient>( NULL );
    }

    if ( m_pSystemTime.get() != NULL )
    {
        delete ( m_pSystemTime.release() );
        m_pSystemTime = auto_ptr<CSystemTime>( NULL );
    }
}

void CPLCAlarmSimDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CPLCAlarmSimDlg)
	DDX_Control(pDX, IDC_COMBO_ALARMS, m_AlarmSelectCombo);
    DDX_Text(pDX, IDC_STATIC_ADDRESS, m_Address);
    DDX_Text(pDX, IDC_STATIC_PORT, m_Port);
    DDX_Text(pDX, IDC_EDIT_ALARM_DATA, m_AlarmData);
    DDX_Text(pDX, IDC_STATIC_STATUS_READBACK, m_ReadbackText);
	DDX_Text(pDX, IDC_STATIC_ALARM_FORMAT, m_AlarmFormat);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CPLCAlarmSimDlg, CDialog)
    //{{AFX_MSG_MAP(CPLCAlarmSimDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BUTTON_CHANGE_REMOTE_SETUP, OnButtonChangeRemoteSetup)
    ON_BN_CLICKED(IDC_BUTTON_CLEAR_ALARM, OnButtonClearAlarm)
    ON_BN_CLICKED(IDC_BUTTON_CONNECT_DISCONNECT, OnButtonConnectDisconnect)
    ON_BN_CLICKED(IDC_BUTTON_SET_ALARM, OnButtonSetAlarm)
    ON_BN_CLICKED(IDC_BUTTON_STATUS_UPDATE, OnButtonStatusUpdate)
    ON_CBN_SELCHANGE(IDC_COMBO_ALARMS, OnSelchangeComboAlarms)
	//}}AFX_MSG_MAP

    ON_MESSAGE( userMessageUpdateStatusDisplay, UpdateStatusDisplay )

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPLCAlarmSimDlg message handlers

BOOL CPLCAlarmSimDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog

    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // General GUI control initialization etc.

    Startup();

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CPLCAlarmSimDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CPLCAlarmSimDlg::OnPaint() 
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

HCURSOR CPLCAlarmSimDlg::OnQueryDragIcon( void )
{
    // The system calls this to obtain the cursor to display while the user drags
    // the minimized window.

    return (HCURSOR) m_hIcon;
}

void CPLCAlarmSimDlg::OnButtonChangeRemoteSetup( void ) 
{
    CIPSetup IPSetupDlg;

    UpdateData( TRUE );

    m_ulPort = atol( static_cast<LPCTSTR>( m_Port ) );

    IPSetupDlg.m_dwPort = m_ulPort;
    IPSetupDlg.m_Address = m_Address;

    if ( IPSetupDlg.DoModal() == IDOK )
    {
        m_ulPort = IPSetupDlg.m_dwPort;
        m_Port.Format( "%lu", m_ulPort );

        m_Address = IPSetupDlg.m_Address;
            
        UpdateData( FALSE );
    }
}

void CPLCAlarmSimDlg::OnButtonConnectDisconnect( void ) 
{
    CString Message;

    ASSERT( m_pRemote.get() != NULL );

    if ( m_bConnected )
    {
        if ( m_pRemote->Disconnect() )
        {
            m_bConnected = false;

            OnDisconnected();
        }
        else
        {
            m_bConnected = true;
            Message.Format( "Failed to disconnect from the PLC, error code: %d", m_pRemote->GetLastError() );
            AfxMessageBox( Message );
        }
    }
    else
    {
        if ( m_pRemote->Connect( StatusCallback, this, DataCallback, this, m_Address, m_ulPort ) )
        {
            m_bConnected = true;

            OnConnected();
        }
        else
        {
            m_bConnected = false;

            Message.Format( "Failed to connect to the PLC, error code: %d", m_pRemote->GetLastError() );
            AfxMessageBox( Message );
        }
    }
}

void CPLCAlarmSimDlg::OnButtonClearAlarm( void )
{
    SetOrClearAlarm( false );
}

void CPLCAlarmSimDlg::OnButtonSetAlarm() 
{
    SetOrClearAlarm( true );
}

void CPLCAlarmSimDlg::OnButtonStatusUpdate( void )
{
    if ( m_bConnected && ( m_pRemote.get() != NULL ) )
    {
        m_ReadbackText.Empty();
        UpdateData( FALSE );

        if ( ! m_pRemote->ReportAlarms() )
        {
            AfxMessageBox( _T( "Report alarms failed" ) );
        }
    }
}

void CPLCAlarmSimDlg::OnSelchangeComboAlarms( void )
{
    int iIndex = m_AlarmSelectCombo.GetCurSel();
    int iCount = m_AlarmSelectCombo.GetCount();

    if ( ( iIndex != LB_ERR ) && ( iCount > 1 ) )
    {
        m_AlarmFormat = &(g_asAlarmFormat[ iIndex ].m_szFormat[ 0 ]);
        UpdateData( FALSE );
    }
}

void CPLCAlarmSimDlg::PLCClientInitialization( void )
{
    m_bConnected = false;

    ASSERT( m_pRemote.get()     == NULL );
    ASSERT( m_pSystemTime.get() == NULL );

    m_pRemote = auto_ptr<C6046RemoteClient>( new C6046RemoteClient() );

    if ( m_pRemote.get() == NULL )
    {
        ::AfxMessageBox( _T( "Remote client failed to be constructed"), MB_OK );
    }

    m_pSystemTime = auto_ptr<CSystemTime>( new CSystemTime() );

    if ( m_pSystemTime.get() == NULL )
    {
        ::AfxMessageBox( _T( "SystemTime object failed to be constructed"), MB_OK );
    }

    ASSERT( m_pRemote.get()     != NULL );
    ASSERT( m_pSystemTime.get() != NULL );
}

void CPLCAlarmSimDlg::StatusCallback( void *pvParam, BYTE *pbyData, unsigned long ulBytes )
{
    try
    {
        ASSERT( ulBytes >  0UL  );
        ASSERT( pbyData != NULL );
        ASSERT( pvParam != NULL );

        CPLCAlarmSimDlg *pthis = static_cast<CPLCAlarmSimDlg *>( pvParam );

        if ( pthis != NULL )
        {
            pthis->UpdateStatusDisplay( pbyData, ulBytes );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCAlarmSimDlg::StatusCallback(), Unspecified exception caught\n" ) );
    }
}

void CPLCAlarmSimDlg::DataCallback( void *pvParam, BYTE *pbyData, unsigned long ulBytes )
{
    UNREFERENCED_PARAMETER( pvParam );
    UNREFERENCED_PARAMETER( pbyData );
    UNREFERENCED_PARAMETER( ulBytes );

    TRACE( _T( "CPLCAlarmSimDlg::DataCallback(), Data message ignored\n" ) );
}

bool CPLCAlarmSimDlg::SetOrClearAlarm( const bool &rbSet )
{
    bool bSuccess = false;

    if ( ( m_pRemote.get() != NULL ) && m_bConnected )
    {
        UpdateData( TRUE );

        int iIndex = m_AlarmSelectCombo.GetCurSel();
        int iCount = m_AlarmSelectCombo.GetCount();

        if ( ( iIndex != LB_ERR ) && ( iCount > 1 ) )
        {
            int     iAlarmId  = static_cast<int>( g_asAlarmFormat[ iIndex ].m_eAlarmId );
            int     iActivate = ( ( rbSet ) ? 1 : 0 );

            bSuccess = m_pRemote->SimulateCommand(  0UL,                                                                            // Simulate command -- set/clear alarms.
                                                        "%d, %d, %s",                                                               // printf style format description of params to follow.
                                                            iAlarmId,                                                               // Alarm id.
                                                                iActivate,                                                          // Set or clear.
                                                                    const_cast<char *>( static_cast<LPCTSTR>( m_AlarmData ) ) );    // Options string; see table 2, columns 3 & 4 of the PLC Spec.
        }
    }

    return bSuccess;
}

void CPLCAlarmSimDlg::EnableDisableControls( const bool &rbEnable )
{
    static int aiControlIds[ ] =    {
                                        IDC_COMBO_ALARMS,
                                        IDC_STATIC_STATUS_READBACK,
                                        IDC_STATIC_ALARM_FORMAT,
                                        IDC_EDIT_ALARM_DATA,
                                        IDC_BUTTON_CLEAR_ALARM,
                                        IDC_BUTTON_SET_ALARM,
                                        IDC_BUTTON_CLEAR_ALARM,
                                        IDC_BUTTON_STATUS_UPDATE
                                    };

    try
    {
        GetDlgItem( IDC_BUTTON_CHANGE_REMOTE_SETUP )->EnableWindow( ! rbEnable );

        for ( int iControl = 0; iControl < DimOf_m( aiControlIds ); iControl++ )
        {
            GetDlgItem( aiControlIds[ iControl ] )->EnableWindow( rbEnable );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCAlarmSimDlg::EnableDisableControls(), Unspecified exception\n" ) );
    }
}

void CPLCAlarmSimDlg::UpdateStatusDisplay(  const BYTE          *pbyData,
                                            const unsigned long &rulBytes )
{
    if ( ( rulBytes > 0UL ) && ( pbyData != NULL ) )
    {
        C7kStatus::EMESSAGETYPE e7kStatusType;
        C7kStatus::EMODE        e7kStatusMode;

        int                     iMessageId;

        C7kStatus               StatusOrAlarm;
        CString                 StatusLine;

        StatusOrAlarm.Decode( const_cast<BYTE *>( pbyData ), rulBytes, false, e7kStatusType, e7kStatusMode, iMessageId );

        switch ( e7kStatusType )
        {
            case C7kStatus::messageTypeStatus:

                StatusLine.Format( "Status, Bytes: %lu, Mode: %d, Id: %d, \"%s\"\n", rulBytes, e7kStatusMode, iMessageId, StatusOrAlarm.Message() );
                break;

            case C7kStatus::messageTypeAlarm:

                StatusLine.Format( "Alarm,  Bytes: %lu, Mode: %d, Id: %d, \"%s\"\n", rulBytes, e7kStatusMode, iMessageId, StatusOrAlarm.Message() );
                break;

            default:
                break;
        }

        if ( ! StatusLine.IsEmpty() )
        {
            // Here we queue the string ready for later display.

            m_Critical.Enter();
            g_StatusMessageQueue.push_back( StatusLine );
            m_Critical.Leave();

            PostMessage( userMessageUpdateStatusDisplay );
        }
    }
}

afx_msg LONG CPLCAlarmSimDlg::UpdateStatusDisplay( WPARAM wParam, LPARAM lParam )
{
    UNREFERENCED_PARAMETER( wParam );
    UNREFERENCED_PARAMETER( lParam );

    m_Critical.Enter();
    unsigned int uiSize = g_StatusMessageQueue.size();
    m_Critical.Leave();

    if ( uiSize == 0U )
    {
        m_ReadbackText.Empty();
        UpdateData( FALSE );
    }
    else
    {
        CString StatusLine;
        //CString NewLine( _T( "\n" ) );

        for ( unsigned int uiMsg = 0U; uiMsg < uiSize; uiMsg++ )
        {
            StatusLine.Empty();

            m_Critical.Enter();
            StatusLine = g_StatusMessageQueue.front();
            g_StatusMessageQueue.pop_front();
            m_Critical.Leave();

            if ( ! StatusLine.IsEmpty() )
            {
                m_ReadbackText += ( StatusLine /*+ NewLine*/ );
                UpdateData( FALSE );
            }
        }
    }

    return 1L;
}

void CPLCAlarmSimDlg::Startup( void )
{
    // General GUI control initialization etc.

    m_Port.Format( "%lu", m_ulPort );
    m_Address = _T( "127.0.0.1" );

    for ( int iComboItem = 0; iComboItem < DimOf_m( g_asAlarmFormat ); iComboItem++ )
    {
        m_AlarmSelectCombo.AddString( &(g_asAlarmFormat[ iComboItem ].m_szLabel[ 0 ]) );
    }

    UpdateData( FALSE );

    PLCClientInitialization();

    EnableDisableControls( false );
}

void CPLCAlarmSimDlg::OnConnected( void )
{
    try
    {
        GetDlgItem( IDC_BUTTON_CONNECT_DISCONNECT )->SetWindowText( _T( "Disconnect" ) );

        EnableDisableControls( true );

        if ( m_AlarmSelectCombo.GetCount() )
        {
            m_AlarmSelectCombo.SetCurSel( 0 );
            m_AlarmFormat = &(g_asAlarmFormat[ 0 ].m_szFormat[ 0 ]);
            UpdateData( FALSE );
        }

        OnButtonStatusUpdate();
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCAlarmSimDlg::OnConnected(), Unspecified exception caught\n" ) );
    }
}

void CPLCAlarmSimDlg::OnDisconnected( void )
{
    try
    {
        GetDlgItem( IDC_BUTTON_CONNECT_DISCONNECT )->SetWindowText( _T( "Connect" ) );

        EnableDisableControls( false );
    }
    catch ( ... )
    {
        TRACE( _T( "CPLCAlarmSimDlg::OnDisconnected(), Unspecified exception caught\n" ) );
    }
}

void CPLCAlarmSimDlg::OnOK( void )
{
    if ( DoesUserWishToExit() )
    {
        CDialog::OnOK();
    }
}

void CPLCAlarmSimDlg::OnCancel( void ) 
{
    if ( DoesUserWishToExit() )
    {
        CDialog::OnCancel();
    }
}

inline
bool CPLCAlarmSimDlg::DoesUserWishToExit( void ) const
{
    return ( AfxMessageBox( _T( "Are you sure you wish to exit?" ), MB_YESNO ) == IDYES );
}

