/*
  ICS SNMP Client demo - C++Builder translation of the Delphi demo OverbyteIcsSnmpCliTst1.pas
  (demos-delphi-vcl), ICS V9.8.

  Sends SNMP v1 Get and Get Next (table walk) requests with TSnmpCli and shows each reply.
  Window position and the entry fields are kept in the common ICS ini file.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The OID is saved on close and restored on show; the Delphi demo reads it but never writes it.
  - Get and Get Table are both disabled while a request runs and enabled again when it
    finishes; the Delphi demo leaves Get Table enabled and TSnmpCli raises "busy" if it is clicked.
  - The common request settings are in SetupRequest instead of being repeated in both buttons.
  - The Max Retries and Port range fields are loaded and saved as in the Delphi demo,
    which never uses them.
*/

#include <vcl.h>
#pragma hdrstop

#include <memory>
#include <System.IniFiles.hpp>

#include "OverbyteIcsSnmpCliTst1.h"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsSnmpMsgs.hpp"

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

TSnmpClientTestForm *SnmpClientTestForm;

namespace {

const String SectionWindow = "WindowMain";
const String KeyTop = "Top";
const String KeyLeft = "Left";
const String KeyWidth = "Width";
const String KeyHeight = "Height";
const String SectionData = "Data";
const String KeySnmpServer = "SnmpServer";
const String KeyOID = "OID";
const String KeyCommunity = "Community";
const String KeyTimeout = "Timeout";
const String KeyMaxRetries = "MaxRetries";
const String KeyPort = "Port";
const String KeyPortRange = "PortRange";

// Holds a memo's Lines in BeginUpdate until the scope ends, as Delphi's try/finally does.
class TMemoUpdate
{
public:
    explicit TMemoUpdate(TMemo *Memo) : FMemo(Memo) { FMemo->Lines->BeginUpdate(); }
    ~TMemoUpdate() { FMemo->Lines->EndUpdate(); }
    TMemoUpdate(const TMemoUpdate &) = delete;
    TMemoUpdate &operator=(const TMemoUpdate &) = delete;
private:
    TMemo *FMemo;
};

} // namespace

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

void __fastcall TSnmpClientTestForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FIniFileName = GetIcsIniFileName();
    DisplayMemo->Clear();
    TimeoutLabel->Caption = "";
    FSnmpCli = new TSnmpCli(this);   // the form is the Owner, so the form frees it
    FSnmpCli->OnRequestDone = SnmpCliRequestDone;
}

void __fastcall TSnmpClientTestForm::FormShow(TObject *Sender)
{
    if (FInitialized)
        return;
    FInitialized = true;

    std::unique_ptr<TIniFile> ini(new TIniFile(FIniFileName));
    Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
    Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
    Top = ini->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = ini->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
    HostEdit->Text = ini->ReadString(SectionData, KeySnmpServer, "192.168.1.160");
    CommunityEdit->Text = ini->ReadString(SectionData, KeyCommunity, "public");
    TimeoutEdit->Text = ini->ReadString(SectionData, KeyTimeout, "10");
    MaxRetriesEdit->Text = ini->ReadString(SectionData, KeyMaxRetries, "5");
    PortEdit->Text = ini->ReadString(SectionData, KeyPort, "5000");
    PortRangeEdit->Text = ini->ReadString(SectionData, KeyPortRange, "50");

    // Select the saved OID by index: assigning Text and then ItemIndex would overwrite the text.
    const String oid = ini->ReadString(SectionData, KeyOID, "1.3.6.1.2.1.1.1.0");
    if (OidComboBox->Items->IndexOf(oid) < 0)
        OidComboBox->Items->Add(oid);
    OidComboBox->ItemIndex = OidComboBox->Items->IndexOf(oid);

    DisplayMemo->Clear();
}

void __fastcall TSnmpClientTestForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    try {
        std::unique_ptr<TIniFile> ini(new TIniFile(FIniFileName));
        ini->WriteInteger(SectionWindow, KeyTop, Top);
        ini->WriteInteger(SectionWindow, KeyLeft, Left);
        ini->WriteInteger(SectionWindow, KeyWidth, Width);
        ini->WriteInteger(SectionWindow, KeyHeight, Height);
        ini->WriteString(SectionData, KeySnmpServer, HostEdit->Text);
        ini->WriteString(SectionData, KeyCommunity, CommunityEdit->Text);
        ini->WriteString(SectionData, KeyTimeout, TimeoutEdit->Text);
        ini->WriteString(SectionData, KeyMaxRetries, MaxRetriesEdit->Text);
        ini->WriteString(SectionData, KeyPort, PortEdit->Text);
        ini->WriteString(SectionData, KeyPortRange, PortRangeEdit->Text);
        ini->WriteString(SectionData, KeyOID, Trim(OidComboBox->Text));   // not saved by the Delphi demo
    }
    catch (Exception &) {
        ShowMessage("Can't save configuration, check permissions");
    }
}

void __fastcall TSnmpClientTestForm::ClearButtonClick(TObject *Sender)
{
    TimeoutLabel->Caption = "";
    DisplayMemo->Clear();
}

// Appends a line, keeping at most 2000 lines, and scrolls the memo to the end.
void TSnmpClientTestForm::Display(const String &Msg)
{
    {
        TMemoUpdate update(DisplayMemo);
        while (DisplayMemo->Lines->Count > 2000)
            DisplayMemo->Lines->Delete(0);
        DisplayMemo->Lines->Add(Msg);
    }
    SendMessage(DisplayMemo->Handle, EM_SCROLLCARET, 0, 0);
}

void TSnmpClientTestForm::SetupRequest()
{
    TimeoutLabel->Caption = "";
    FSnmpCli->Port = Trim(PortEdit->Text);
    FSnmpCli->Addr = "0.0.0.0";
    FSnmpCli->HostIP = HostEdit->Text;
    FSnmpCli->OID = Trim(OidComboBox->Text);
    FSnmpCli->Version = SNMP_V1;
    FSnmpCli->Community = AnsiString(Trim(CommunityEdit->Text));
    FSnmpCli->Timeout = static_cast<int>(StrToFloatDef(TimeoutEdit->Text, 2.0) * 1000);
    FStartTick = GetTickCount();
}

void __fastcall TSnmpClientTestForm::GetButtonClick(TObject *Sender)
{
    SetupRequest();
    if (FSnmpCli->Get()) {
        GetButton->Enabled = false;
        GetTableButton->Enabled = false;
    }
    else
        Display("Failed");
}

void __fastcall TSnmpClientTestForm::GetTableButtonClick(TObject *Sender)
{
    SetupRequest();
    if (FSnmpCli->GetNext()) {
        GetButton->Enabled = false;
        GetTableButton->Enabled = false;
    }
    else
        Display("Failed");
}

/* Called once per reply. A table walk calls it for every row with Action sacContinue and
   TSnmpCli then asks for the next row; any other Action means the request has finished. */
void __fastcall TSnmpClientTestForm::SnmpCliRequestDone(TObject *Sender, TSnmpCliReq ReqType,
                                                        TSnmpCliAct &Action, Word ErrCode)
{
    FEndTick = GetTickCount();
    const unsigned elapsed = IcsCalcTickDiff(FStartTick, FEndTick);
    if (Action != sacContinue) {   // the Delphi demo enables Get on every reply, even mid-table
        GetButton->Enabled = true;
        GetTableButton->Enabled = true;
    }

    String msg;
    if (ErrCode != 0) {
        if (ErrCode == SNMP_TIMEOUT)
            msg = "Timeout error: " + HostEdit->Text + " do not answer to SNMP request";
        else
            msg = "Error #" + IntToStr(ErrCode);
    }
    else if (FSnmpCli->Reply->ErrorStatus != 0) {
        msg = "<ERROR STATUS=" + IntToStr(FSnmpCli->Reply->ErrorStatus) + " \"" +
              SnmpErrorToString(FSnmpCli->Reply->ErrorStatus) + "\">";
    }
    else if (ReqType == srqGet || (ReqType == srqGetNext && Action == sacContinue))
        msg = FSnmpCli->ResponseValue;
    else if (ReqType == srqGetNext && Action == sacNone)
        msg = "<END OF TABLE>";

    if (!msg.IsEmpty()) {
        Display(FormatDateTime("YYYYMMDD|HHNNSS.ZZZ|", Now()) + IntToStr(static_cast<__int64>(elapsed)) + "|" +
                FSnmpCli->ResponseOID + "|" + FSnmpCli->HostIP + "|" + msg);
    }

    if (Action == sacNone)
        FSnmpCli->Abort();
}
