/*
  ICS SysLog Server demo - C++Builder translation of the Delphi demo OverbyteIcsSysLogServerDemo1.pas
  (demos-delphi-vcl), ICS V9.8.

  Listens for SysLog messages with TSysLogServer, decodes each one and shows the fields
  in a memo that keeps the last 200 lines. The window position is kept in the ICS ini file.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The TSysLogServer is a std::unique_ptr member created with no Owner (Delphi: Create(Self)).
  - The Stop button starts disabled, as no server is listening yet.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsSysLogServerDemo1.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

TSysLogServerForm *SysLogServerForm;

namespace {

const String SectionWindow = "Window";   // must be unique for each window
const String KeyTop = "Top";
const String KeyLeft = "Left";
const String KeyWidth = "Width";
const String KeyHeight = "Height";

// Holds the memo's updates for the guard's lifetime. On every exit path it ends the update
// and scrolls the caret into view, as the Delphi finally block does.
class TMemoUpdate
{
public:
    explicit TMemoUpdate(TMemo *Memo) : FMemo(Memo) { FMemo->Lines->BeginUpdate(); }
    ~TMemoUpdate()
    {
        FMemo->Lines->EndUpdate();
        SendMessage(FMemo->Handle, EM_SCROLLCARET, 0, 0);
    }
    TMemoUpdate(const TMemoUpdate &) = delete;
    TMemoUpdate &operator=(const TMemoUpdate &) = delete;
private:
    TMemo *FMemo;
};

} // namespace

__fastcall TSysLogServerForm::TSysLogServerForm(TComponent *Owner)
    : TForm(Owner)
{
}

void __fastcall TSysLogServerForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FSysLogServer.reset(new TSysLogServer(nullptr));
    FIniFileName = GetIcsIniFileName();
    DisplayMemo->Clear();
    FSysLogServer->OnDataAvailable = SysLogServer1DataAvailable;
    SetButtons(false);   // Delphi leaves Stop enabled before anything is listening
}

void __fastcall TSysLogServerForm::FormShow(TObject *Sender)
{
    if (!FInitialized) {
        FInitialized = true;

        // The second constructor argument (PreserveAnsi) has a Delphi default that the header drops.
        std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
        Width = IniFile->ReadInteger(SectionWindow, KeyWidth, Width);
        Height = IniFile->ReadInteger(SectionWindow, KeyHeight, Height);
        Top = IniFile->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
        Left = IniFile->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
        DisplayMemo->Clear();
    }
}

void __fastcall TSysLogServerForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    IniFile->WriteInteger(SectionWindow, KeyTop, Top);
    IniFile->WriteInteger(SectionWindow, KeyLeft, Left);
    IniFile->WriteInteger(SectionWindow, KeyWidth, Width);
    IniFile->WriteInteger(SectionWindow, KeyHeight, Height);
    IniFile->UpdateFile();
}

void TSysLogServerForm::Display(const String &Msg)
{
    TMemoUpdate update(DisplayMemo);
    while (DisplayMemo->Lines->Count > 200)
        DisplayMemo->Lines->Delete(0);
    DisplayMemo->Lines->Add(Msg);
}

void TSysLogServerForm::SetButtons(bool Listening)
{
    StartButton->Enabled = !Listening;
    StopButton->Enabled = Listening;
}

void __fastcall TSysLogServerForm::StartButtonClick(TObject *Sender)
{
    FSysLogServer->Listen();
    SetButtons(true);
}

void __fastcall TSysLogServerForm::StopButtonClick(TObject *Sender)
{
    FSysLogServer->Close();
    SetButtons(false);
}

void __fastcall TSysLogServerForm::SysLogServer1DataAvailable(TObject *Sender, const AnsiString SrcIP,
                                                              const AnsiString SrcPort, const AnsiString RawMessage)
{
    TSysLogData Decoded;
    try {
        FSysLogServer->ParseRawMessage(RawMessage, Decoded);
    }
    catch (Exception &E) {
        Display(E.Message);
        return;
    }
    Display("Received from " + String(SrcIP) + ":" + String(SrcPort));
    Display("   RawMessage       = " + String(RawMessage));
    Display("   PRI              = " + IntToStr(Decoded.PRI));
    Display("   TimeStamp        = " + Decoded.TimeString);
    // String() makes the format UnicodeString (a narrow literal also matches AnsiStrings::Format); the Word fields are cast to int for TVarRec.
    Display("   DateTime         = " + Format(String("%04.4d-%02.2d-%02.2d %02.2d:%02.2d:%02.2d.%03.3d"),
                                              ARRAYOFCONST((static_cast<int>(Decoded.Year),
                                                            static_cast<int>(Decoded.Month),
                                                            static_cast<int>(Decoded.Day),
                                                            static_cast<int>(Decoded.Hour),
                                                            static_cast<int>(Decoded.Minute),
                                                            static_cast<int>(Decoded.Second),
                                                            static_cast<int>(Decoded.MilliSec)))));
    Display("   Facility         = " + IntToStr(static_cast<int>(Decoded.Facility)));
    Display("   Severity         = " + IntToStr(static_cast<int>(Decoded.Severity)));
    Display("   HostName         = " + Decoded.HostName);
    Display("   Process          = " + Decoded.Process);
    Display("   PID              = " + IntToStr(Decoded.PID));
    Display("   Text             = " + String(Decoded.Text));
    Display("   RFC5424          = " + IntToStr(Decoded.RFC5424 ? 1 : 0));
    if (Decoded.RFC5424) {
        Display("   MsgVer           = " + IntToStr(Decoded.MsgVer));
        Display("   MsgID            = " + Decoded.MsgID);
        Display("   StructData       = " + Decoded.StructData);
    }
}
