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

  Builds a SysLog message (RFC 3164 or RFC 5424) from the fields on the form and sends it with
  TSysLogClient over UDP. The window position and the field values are kept in the ICS ini file.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The TSysLogClient is a std::unique_ptr member created with no Owner (Delphi: Create(Self)).
  - Send refuses a facility or severity that was typed into the combo box instead of picked from
    its list (the Delphi builds a message with an out-of-range priority).
  - A date or time field that is not a number, or does not fit a Word, gets the same "check the
    date/time" message as an impossible date (the Delphi raises a raw conversion error).
  - The process name comes from ParamStr(0), not from a 256 character GetModuleFileName buffer.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsSysLogClientDemo1.h"

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

TSysLogClientForm *SysLogClientForm;

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";
const String SectionData = "Data";
const String KeyText = "Text";
const String KeyFacility = "Facility";
const String KeySeverity = "Severity";
const String KeyHostName = "HostName";
const String KeyProcess = "ProcessName";
const String KeyPID = "ProcessID";
const String KeyMySelf = "ProcessMySelf";
const String KeyServer = "Server";
const String KeyDftPri = "DefaultPRI";
const String KeyYear = "Year";
const String KeyMonth = "Month";
const String KeyDay = "Day";
const String KeyHour = "Hour";
const String KeyMin = "Minute";
const String KeySec = "Second";
const String KeyNow = "Now";
const String KeyMsgID = "MsgID";
const String KeyStructData = "StructuredData";
const String KeyRFC5424 = "RFC5424";

const String DftServer = "127.0.0.1";
const int DftFacility = SYSLOG_FACILITY_USER;
const int DftSeverity = SYSLOG_SEVERITY_NOTICE;
const String DftHostName = "MyHostName";
const String DftProcess = "MyProcessName";
const String DftPID = "1234";
const bool DftMySelf = true;
const bool DftNow = true;
const bool DftPRI = true;
const String DftYear = "2009";
const String DftMonth = "9";
const String DftDay = "13";
const String DftHour = "14";
const String DftMin = "34";
const String DftSec = "48";
const String DftText = "Hello http://www.OverByte.be";
const String DftMsgID = "ID47";
const bool DftRFC5424 = true;
const String DftStructData = "[exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"]";

// 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;
};

// A date or time field must be a whole number that fits a Word.
bool TryEditToWord(TEdit *Edit, Word &Value)
{
    int Number = 0;
    if (!TryStrToInt(Edit->Text, Number) || Number < 0 || Number > 65535)
        return false;
    Value = static_cast<Word>(Number);
    return true;
}

}  // namespace

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

void __fastcall TSysLogClientForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FSysLogClient.reset(new TSysLogClient(nullptr));
    FIniFileName = GetIcsIniFileName();
    DisplayMemo->Clear();

    FacilityComboBox->Clear();
    for (int Facility = SYSLOG_FACILITY_KERNEL; Facility <= SYSLOG_FACILITY_LOCAL7; Facility++)
        FacilityComboBox->Items->Add(IntToStr(Facility) + ") " + SysLogFacilityString[Facility]);

    SeverityComboBox->Clear();
    for (int Severity = SYSLOG_SEVERITY_EMERGENCY; Severity <= SYSLOG_SEVERITY_DEBUG; Severity++)
        SeverityComboBox->Items->Add(IntToStr(Severity) + ") " + SysLogSeverityString[Severity]);
}

void __fastcall TSysLogClientForm::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);
        TextEdit->Text = IniFile->ReadString(SectionData, KeyText, DftText);
        FacilityComboBox->ItemIndex = IniFile->ReadInteger(SectionData, KeyFacility, DftFacility);
        SeverityComboBox->ItemIndex = IniFile->ReadInteger(SectionData, KeySeverity, DftSeverity);
        HostnameEdit->Text = IniFile->ReadString(SectionData, KeyHostName, DftHostName);
        ProcessNameEdit->Text = IniFile->ReadString(SectionData, KeyProcess, DftProcess);
        ProcessIDEdit->Text = IniFile->ReadString(SectionData, KeyPID, DftPID);
        ProcessMySelfCheckBox->Checked = IniFile->ReadBool(SectionData, KeyMySelf, DftMySelf);
        ServerEdit->Text = IniFile->ReadString(SectionData, KeyServer, DftServer);
        DefaultPRICheckBox->Checked = IniFile->ReadBool(SectionData, KeyDftPri, DftPRI);
        NowCheckBox->Checked = IniFile->ReadBool(SectionData, KeyNow, DftNow);
        YearEdit->Text = IniFile->ReadString(SectionData, KeyYear, DftYear);
        MonthEdit->Text = IniFile->ReadString(SectionData, KeyMonth, DftMonth);
        DayEdit->Text = IniFile->ReadString(SectionData, KeyDay, DftDay);
        HourEdit->Text = IniFile->ReadString(SectionData, KeyHour, DftHour);
        MinEdit->Text = IniFile->ReadString(SectionData, KeyMin, DftMin);
        SecEdit->Text = IniFile->ReadString(SectionData, KeySec, DftSec);
        MsgIDEdit->Text = IniFile->ReadString(SectionData, KeyMsgID, DftMsgID);
        StructDataEdit->Text = IniFile->ReadString(SectionData, KeyStructData, DftStructData);
        RFC5424RadioButton->Checked = IniFile->ReadBool(SectionData, KeyRFC5424, DftRFC5424);
        RFC3164RadioButton->Checked = !RFC5424RadioButton->Checked;
        UpdateControlStates();
    }
}

void __fastcall TSysLogClientForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    try {
        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->WriteString(SectionData, KeyText, TextEdit->Text);
        IniFile->WriteInteger(SectionData, KeyFacility, FacilityComboBox->ItemIndex);
        IniFile->WriteInteger(SectionData, KeySeverity, SeverityComboBox->ItemIndex);
        IniFile->WriteString(SectionData, KeyHostName, HostnameEdit->Text);
        IniFile->WriteString(SectionData, KeyProcess, ProcessNameEdit->Text);
        IniFile->WriteString(SectionData, KeyPID, ProcessIDEdit->Text);
        IniFile->WriteBool(SectionData, KeyMySelf, ProcessMySelfCheckBox->Checked);
        IniFile->WriteString(SectionData, KeyServer, ServerEdit->Text);
        IniFile->WriteBool(SectionData, KeyDftPri, DefaultPRICheckBox->Checked);
        IniFile->WriteBool(SectionData, KeyNow, NowCheckBox->Checked);
        IniFile->WriteString(SectionData, KeyYear, YearEdit->Text);
        IniFile->WriteString(SectionData, KeyMonth, MonthEdit->Text);
        IniFile->WriteString(SectionData, KeyDay, DayEdit->Text);
        IniFile->WriteString(SectionData, KeyHour, HourEdit->Text);
        IniFile->WriteString(SectionData, KeyMin, MinEdit->Text);
        IniFile->WriteString(SectionData, KeySec, SecEdit->Text);
        IniFile->WriteString(SectionData, KeyMsgID, MsgIDEdit->Text);
        IniFile->WriteString(SectionData, KeyStructData, StructDataEdit->Text);
        IniFile->WriteBool(SectionData, KeyRFC5424, RFC5424RadioButton->Checked);
        IniFile->UpdateFile();
    }
    catch (Exception &) {
        // As in the Delphi: a failure writing the ini file (usually a permission problem) is ignored.
    }
}

void TSysLogClientForm::Display(const String &Msg)
{
    TMemoUpdate update(DisplayMemo);
    if (DisplayMemo->Lines->Count > 200) {
        for (int i = 0; i < 50; i++)
            DisplayMemo->Lines->Delete(0);
    }
    DisplayMemo->Lines->Add(Msg);
}

void TSysLogClientForm::UpdateControlStates()
{
    ProcessMySelfCheckBoxClick(nullptr);
    DefaultPRICheckBoxClick(nullptr);
    NowCheckBoxClick(nullptr);
    RFC5424RadioButtonClick(nullptr);
}

void __fastcall TSysLogClientForm::ProcessMySelfCheckBoxClick(TObject *Sender)
{
    const bool MySelf = ProcessMySelfCheckBox->Checked;
    if (MySelf) {
        ProcessIDEdit->Text = IntToStr(static_cast<int>(GetCurrentProcessId()));
        ProcessNameEdit->Text = ChangeFileExt(ExtractFileName(ParamStr(0)), "");
        HostnameEdit->Text = String(LocalHostName());
    }
    HostnameEdit->Enabled = !MySelf;
    ProcessNameEdit->Enabled = !MySelf;
    ProcessIDEdit->Enabled = !MySelf;
}

void __fastcall TSysLogClientForm::RFC3164RadioButtonClick(TObject *Sender)
{
    RFC5424RadioButtonClick(nullptr);
}

void __fastcall TSysLogClientForm::RFC5424RadioButtonClick(TObject *Sender)
{
    MsgIDEdit->Enabled = RFC5424RadioButton->Checked;
    StructDataEdit->Enabled = RFC5424RadioButton->Checked;
}

void __fastcall TSysLogClientForm::DefaultPRICheckBoxClick(TObject *Sender)
{
    if (DefaultPRICheckBox->Checked) {
        FacilityComboBox->ItemIndex = SYSLOG_FACILITY_USER;
        SeverityComboBox->ItemIndex = SYSLOG_SEVERITY_NOTICE;
    }
    FacilityComboBox->Enabled = !DefaultPRICheckBox->Checked;
    SeverityComboBox->Enabled = !DefaultPRICheckBox->Checked;
}

void __fastcall TSysLogClientForm::NowCheckBoxClick(TObject *Sender)
{
    if (NowCheckBox->Checked) {
        const TDateTime TS = Now();
        Word Year, Month, Day, Hour, Minute, Second, MilliSec;
        DecodeDate(TS, Year, Month, Day);
        DecodeTime(TS, Hour, Minute, Second, MilliSec);
        YearEdit->Text = IntToStr(static_cast<int>(Year));
        MonthEdit->Text = IntToStr(static_cast<int>(Month));
        DayEdit->Text = IntToStr(static_cast<int>(Day));
        HourEdit->Text = IntToStr(static_cast<int>(Hour));
        MinEdit->Text = IntToStr(static_cast<int>(Minute));
        SecEdit->Text = IntToStr(static_cast<int>(Second));
    }
    const bool Editable = !NowCheckBox->Checked;
    YearEdit->Enabled = Editable;
    MonthEdit->Enabled = Editable;
    DayEdit->Enabled = Editable;
    HourEdit->Enabled = Editable;
    MinEdit->Enabled = Editable;
    SecEdit->Enabled = Editable;
}

void __fastcall TSysLogClientForm::SendButtonClick(TObject *Sender)
{
    NowCheckBoxClick(nullptr);

    Word Year = 0, Month = 0, Day = 0, Hour = 0, Minute = 0, Second = 0;
    TDateTime TimeStamp;
    bool Valid = TryEditToWord(YearEdit, Year) && TryEditToWord(MonthEdit, Month) &&
                 TryEditToWord(DayEdit, Day) && TryEditToWord(HourEdit, Hour) &&
                 TryEditToWord(MinEdit, Minute) && TryEditToWord(SecEdit, Second);
    if (Valid) {
        try {
            TimeStamp = EncodeDate(Year, Month, Day) + EncodeTime(Hour, Minute, Second, 0);
        }
        catch (Exception &) {
            Valid = false;   // an impossible date or time, such as month 13
        }
    }
    if (!Valid) {
        Beep();
        ShowMessage("Please check the date/time you entered");
        return;
    }
    if (FacilityComboBox->ItemIndex < 0 || SeverityComboBox->ItemIndex < 0) {
        Beep();
        ShowMessage("Please pick the facility and severity from their lists");
        return;
    }

    FSysLogClient->TimeStamp = TimeStamp;
    FSysLogClient->Server = Trim(ServerEdit->Text);
    FSysLogClient->Text = Trim(TextEdit->Text);
    FSysLogClient->Facility = static_cast<TSysLogFacility>(FacilityComboBox->ItemIndex);
    FSysLogClient->Severity = static_cast<TSysLogSeverity>(SeverityComboBox->ItemIndex);
    FSysLogClient->HostName = Trim(HostnameEdit->Text);
    FSysLogClient->Process = Trim(ProcessNameEdit->Text);
    FSysLogClient->PID = StrToIntDef(ProcessIDEdit->Text, 0);
    FSysLogClient->MsgID = Trim(MsgIDEdit->Text);
    FSysLogClient->StructData = Trim(StructDataEdit->Text);
    FSysLogClient->RFC5424 = RFC5424RadioButton->Checked;
    FSysLogClient->Send();
    FSysLogClient->Close();
    Display(FormatDateTime(String("yyyymmdd hhnnss"), Now()) + "|" + FSysLogClient->RawMessage);
}

void __fastcall TSysLogClientForm::SetDefaultButtonClick(TObject *Sender)
{
    TextEdit->Text = DftText;
    FacilityComboBox->ItemIndex = DftFacility;
    SeverityComboBox->ItemIndex = DftSeverity;
    HostnameEdit->Text = DftHostName;
    ProcessNameEdit->Text = DftProcess;
    ProcessIDEdit->Text = DftPID;
    ProcessMySelfCheckBox->Checked = DftMySelf;
    ServerEdit->Text = DftServer;
    DefaultPRICheckBox->Checked = DftPRI;
    NowCheckBox->Checked = DftNow;
    YearEdit->Text = DftYear;
    MonthEdit->Text = DftMonth;
    DayEdit->Text = DftDay;
    HourEdit->Text = DftHour;
    MinEdit->Text = DftMin;
    SecEdit->Text = DftSec;
    MsgIDEdit->Text = DftMsgID;
    StructDataEdit->Text = DftStructData;
    RFC5424RadioButton->Checked = DftRFC5424;
    RFC3164RadioButton->Checked = !RFC5424RadioButton->Checked;
    UpdateControlStates();
}
