/*
  ICS Network Monitor demo - C++Builder translation of the Delphi demo OverbyteIcsNetMon1.pas
  (demos-delphi-vcl), ICS V9.8.

  Captures IP packets with raw sockets (TIcsMonSocket, needs administrator rights) or with the
  Npcap driver (TIcsMonPcap, needs packet.dll from https://npcap.com/), filters them by protocol,
  address and port, and shows them as log lines, in a virtual grid (one packet in detail in the
  second form), or as traffic totals per remote address and service.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The monitor, filter and traffic objects and the adapter lists are form members owned by
    std::unique_ptr (Delphi: unit-level variables, created with Create(Self) and freed in OnDestroy).
    The adapter lists always exist (Delphi creates them only when Npcap lists an adapter).
  - The packet grid stores its packets in a std::vector, which grows as needed.
  - Without administrator rights Npcap is selected after the ini settings are read (Delphi selects it
    before, so a saved setting turns it off again).
  - A capture with "Use Npcap" ticked but no packet.dll loaded is refused with a log line
    (Delphi raises an access violation, caught and logged).
  - Removing an address or port from the filter memos takes effect: the filter lists are rebuilt
    from the memo (Delphi only adds, so every partly typed port number stays in the filter).
  - The "Log DNS Cache" box works: the DNS cache log event is assigned in FormCreate (Delphi declares
    the handler but never connects it).
  - Selecting a row in the traffic list does not open the packet window (Delphi shares the grid's
    handler, which shows the grid packet with the same row number).
  - The packet window shows the whole packet in hex and no longer adds an empty row per selection.
  - "Log Totals" writes the service header after the traffic lines (Delphi writes it to the memo
    directly, before the buffered traffic lines appear).
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsNetMon1.h"
#include "OverbyteIcsNetMon2.h"

#pragma package(smart_init)
#pragma link "OverbyteIcsDnsQuery"
#pragma link "OverbyteIcsTypes"
#pragma link "OverbyteIcsWndControl"
#pragma resource "*.dfm"
#pragma resource "nmap-mac-prefixes.RES"   // MAC vendor names, so nmap-mac-prefixes.txt need not be shipped
#pragma resource "icsportlist.RES"         // port and service names, so icsportlist.txt need not be shipped

TMonForm *MonForm;

using namespace Netmonini;

namespace {

const String PacketLineMask = "%-12s %-6s %4d  %-35s %6d  %-35s %6d  %-12s %4d  %s";
const String HeaderLine = "Time         Prot   Plen  Source IP                      Source Port  "
                          "Destination IP                   Dest Port  Service      Dlen  Packet Data";
const int MaxDataShown = 128;   // bytes of packet data shown unless Show Full Data is ticked

}  // namespace

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

// Freed here rather than in OnDestroy, which in C++ runs after the members are gone. The monitors
// go first, so no packet event can reach the filter or traffic objects after they are freed.
__fastcall TMonForm::~TMonForm()
{
    FMonitorSocket.reset();
    FMonitorPcap.reset();
    FTrafficClass.reset();
    FMonitorFilter.reset();
}

void __fastcall TMonForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FMonitorFilter.reset(new TIcsMonFilterClass());
    FTrafficClass.reset(new TIcsTrafficClass(nullptr));
    FTrafficClass->IcsDNCache = IcsDomainNameCache1;
    IcsDomainNameCache1->OnAppLogEvent = IcsDomainNameCache1DNLogEvent;   // Delphi never connects it
    FAdapterIPList.reset(new TStringList());
    FAdapterMaskList.reset(new TStringList());
    FAdapterBcastList.reset(new TStringList());

    // raw sockets monitoring
    FMonitorSocket.reset(new TIcsMonSocket(nullptr));
    FMonitorSocket->OnPacketEvent = PacketEvent;
    FMonitorSocket->OnLogEvent = LogEvent;
    FMonitorSocket->IcsDNCache = IcsDomainNameCache1;
    MonIpList->Items = LocalIPList(sfAny);
    if (MonIpList->Items->Count > 0)
        MonIpList->ItemIndex = 0;

    // Npcap monitoring, needs packet.dll and drivers installed
    if (LoadPacketDll()) {
        FMonitorPcap.reset(new TIcsMonPcap(nullptr));
        FMonitorPcap->OnPacketEvent = PacketEvent;
        FMonitorPcap->OnLogEvent = LogEvent;
        FMonitorPcap->IcsDNCache = IcsDomainNameCache1;
        LogLine("Npcap version: " + Pcap_GetPacketVersion());
        MonAdptList->Items->Assign(FMonitorPcap->AdapterDescList);
        if (MonAdptList->Items->Count != 0) {
            for (int i = 0; i < MonAdptList->Items->Count; i++) {
                if (MonAdptList->Items->Strings[i] == "")
                    MonAdptList->Items->Strings[i] = FMonitorPcap->AdapterNameList->Strings[i];   // device name
            }
            MonAdptList->ItemIndex = 0;
            MonAdptList->Enabled = true;
            UseWinNCap->Enabled = true;
            MonPromiscuous->Enabled = true;
        }
    }
}

void __fastcall TMonForm::FormShow(TObject *Sender)
{
    // socket monitoring needs admin rights
    const bool IsAdmin = IcsIsProgAdmin();
    if (IsAdmin)
        LabelAdmin->Caption = "Program has Administrator Rights";
    else
        LabelAdmin->Caption = "Program does not have Administrator Rights, no socket monitoring";

    FCaptureMode = TCaptureMode::None;
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(GetIcsIniFileName(), true));   // PreserveAnsi: Delphi default
    // form positions
    Width = IniFile->ReadInteger(SectionMainWindow, KeyWidth, Width);
    Height = IniFile->ReadInteger(SectionMainWindow, KeyHeight, Height);
    Top = IniFile->ReadInteger(SectionMainWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = IniFile->ReadInteger(SectionMainWindow, KeyLeft, (Screen->Width - Width) / 2);

    // first, to fill IP address list
    UseWinNCap->Checked = IniFile->ReadString(SectionData, "UseWinNCap_Checked", "False") == "True";
    if (!IsAdmin)
        UseWinNCap->Checked = true;   // Npcap only; Delphi forces this before the ini load, which then undoes it
    MonAdptList->ItemIndex = IniFile->ReadInteger(SectionData, "MonAdptList_ItemIndex", MonAdptList->ItemIndex);
    UseWinNCapClick(this);
    MonIpList->ItemIndex = IniFile->ReadInteger(SectionData, "MonIpList_ItemIndex", MonIpList->ItemIndex);
    FilterIpAddr->ItemIndex = IniFile->ReadInteger(SectionData, "FilterIpAddr_ItemIndex", FilterIpAddr->ItemIndex);
    FilterProtocol->ItemIndex = IniFile->ReadInteger(SectionData, "FilterProtocol_ItemIndex", FilterProtocol->ItemIndex);
    IpAddrList->Lines->CommaText = IniFile->ReadString(SectionData, "IpAddrList_Lines", IpAddrList->Lines->CommaText);
    PortsList->Lines->CommaText = IniFile->ReadString(SectionData, "PortsList_Lines", PortsList->Lines->CommaText);

    // The ini key of each box is its name + "_Checked", as in the Delphi; setting Checked runs its OnClick.
    for (TCheckBox *Box : {IpAddrLocal, MonPromiscuous, ProtocolIRC, ProtocolARP, ProtocolBroadcast, ProtocolDns,
                           ProtocolHttp, ProtocolICMP, ProtocolIPv4, ProtocolIPv6, ProtocolNonIp, ProtocolSNMP,
                           ProtocolSyslog, ProtocolTCP, ProtocolUPnP, ProtocolUdp, ShowFullData, ShowIgnoreData})
        Box->Checked = IniFile->ReadString(SectionData, Box->Name + "_Checked", "False") == "True";
    CapButtons(false);
}

void __fastcall TMonForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    if (FCaptureMode != TCaptureMode::None)
        CaptureStop();
    if (FormOneRow->Visible)
        FormOneRow->FormClose(this, Action);   // saves the packet window position
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(GetIcsIniFileName(), true));
    // form positions
    IniFile->WriteInteger(SectionMainWindow, KeyTop, Top);
    IniFile->WriteInteger(SectionMainWindow, KeyLeft, Left);
    IniFile->WriteInteger(SectionMainWindow, KeyWidth, Width);
    IniFile->WriteInteger(SectionMainWindow, KeyHeight, Height);
    IniFile->WriteInteger(SectionData, "FilterIpAddr_ItemIndex", FilterIpAddr->ItemIndex);
    IniFile->WriteInteger(SectionData, "FilterProtocol_ItemIndex", FilterProtocol->ItemIndex);
    IniFile->WriteString(SectionData, "IpAddrList_Lines", IpAddrList->Lines->CommaText);
    IniFile->WriteInteger(SectionData, "MonAdptList_ItemIndex", MonAdptList->ItemIndex);
    IniFile->WriteInteger(SectionData, "MonIpList_ItemIndex", MonIpList->ItemIndex);
    IniFile->WriteString(SectionData, "PortsList_Lines", PortsList->Lines->CommaText);
    for (TCheckBox *Box : {IpAddrLocal, MonPromiscuous, ProtocolIRC, ProtocolARP, ProtocolBroadcast, ProtocolDns,
                           ProtocolHttp, ProtocolICMP, ProtocolIPv4, ProtocolIPv6, ProtocolNonIp, ProtocolSNMP,
                           ProtocolSyslog, ProtocolTCP, ProtocolUPnP, ProtocolUdp, ShowFullData, ShowIgnoreData,
                           UseWinNCap})
        IniFile->WriteString(SectionData, Box->Name + "_Checked", Box->Checked ? "True" : "False");
    IniFile->UpdateFile();
}

// The packet data as display text: TCP flags for an empty TCP packet, otherwise the data with
// control characters removed and CR/LF escaped, cut to MaxLen bytes (0 = all).
String TMonForm::PacketText(const TPacketInfo &Packet, int MaxLen)
{
    if (Packet.DataLen == 0 && Packet.ProtoType == IPPROTO_TCP)
        return IcsTCPFlags(Packet.TcpFlags);
    AnsiString Data = Packet.DataBuf;
    if (MaxLen > 0 && Data.Length() > MaxLen)
        Data.SetLength(MaxLen);
    return IcsEscapeCRLF(IcsStrRemCntls(String(Data)));
}

void __fastcall TMonForm::PacketEvent(TObject *Sender, const TPacketInfo &PacketInfo)
{
    if (FCaptureMode == TCaptureMode::None)
        return;
    if (!FMonitorFilter->AllowPacket(PacketInfo))
        return;
    if (FCaptureMode == TCaptureMode::Log) {
        String MyData = PacketText(PacketInfo, ShowFullData->Checked ? 0 : MaxDataShown);
        if (!(PacketInfo.DataLen == 0 && PacketInfo.ProtoType == IPPROTO_TCP))
            MyData = "[" + MyData + "]";
        LogLine(Format(PacketLineMask, ARRAYOFCONST((IcsTimeToZStr(PacketInfo.PacketDT), PacketInfo.DispProto,
                                                     PacketInfo.PacketLen, PacketInfo.DispAddrSrc, PacketInfo.PortSrc,
                                                     PacketInfo.DispAddrDest, PacketInfo.PortDest, PacketInfo.DispServ,
                                                     PacketInfo.DataLen, MyData))));
    }
    else if (FCaptureMode == TCaptureMode::Grid) {
        FGridBuffer.push_back(PacketInfo);
        const int Row = static_cast<int>(FGridBuffer.size()) - 1;
        MonGridWin->Items->Count = Row + 1;   // virtual TListView, gets data from OnData
        if (GridAutoScroll->Checked)
            MonGridWin->Items->Item[Row]->MakeVisible(false);   // scroll
        MonGridWin->UpdateItems(Row, Row);
    }
    else if (FCaptureMode == TCaptureMode::Traffic) {
        FTrafficClass->Add(PacketInfo);
    }
}

// Virtual TListView: OnData is called for each row the list view needs. Item is a dummy whose
// only valid data is its Index; in report view the sub-items must be added too.
void __fastcall TMonForm::MonGridWinData(TObject *Sender, TListItem *Item)
{
    const int ItemNr = Item->Index;   // line for which data is required
    if (ItemNr < 0 || ItemNr >= static_cast<int>(FGridBuffer.size()))
        return;
    const TPacketInfo &Packet = FGridBuffer[ItemNr];
    String DispSrc = Packet.DispAddrSrc;
    if (Packet.DispAddrSrc != Packet.HostNameSrc)
        DispSrc = DispSrc + IcsSPACE + Packet.HostNameSrc;
    String DispDest = Packet.DispAddrDest;
    if (Packet.DispAddrDest != Packet.HostNameDest)
        DispDest = DispDest + IcsSPACE + Packet.HostNameDest;
    Item->Caption = IcsTimeToZStr(Packet.PacketDT);
    Item->SubItems->Add(Packet.DispProto);
    Item->SubItems->Add(IntToStr(Packet.PacketLen));
    Item->SubItems->Add(DispSrc);
    Item->SubItems->Add(IntToStr(Packet.PortSrc));
    Item->SubItems->Add(DispDest);
    Item->SubItems->Add(IntToStr(Packet.PortDest));
    Item->SubItems->Add(Packet.DispServ);
    Item->SubItems->Add(IntToStr(Packet.DataLen));
    Item->SubItems->Add(PacketText(Packet, ShowFullData->Checked ? 0 : MaxDataShown));
}

void __fastcall TMonForm::MonGridWinSelectItem(TObject *Sender, TListItem *Item, bool Selected)
{
    const int DataPerRow = 40;   // ASCII bytes per row; hex rows hold half that

    if (Sender != MonGridWin)
        return;   // the .dfm also binds the traffic list here; its rows are not grid packets
    if (!Selected || !Item)
        return;
    const int ItemNr = Item->Index;   // line selected, equals the grid buffer index
    if (ItemNr < 0 || ItemNr >= static_cast<int>(FGridBuffer.size()))
        return;
    if (!FormOneRow->Visible)
        FormOneRow->Show();

    TListItems *Rows = FormOneRow->ListOneRow->Items;
    auto addRow = [Rows]() {
        TListItem *Row = Rows->Add();
        Row->Caption = "";
        Row->SubItems->Add("");
    };
    auto setRow = [Rows](int Row, const String &Value) { Rows->Item[Row]->SubItems->Strings[0] = Value; };

    if (Rows->Count < ColCapDataLast) {
        Rows->Clear();
        for (int i = 0; i <= ColCapDataLast; i++) {
            addRow();
            Rows->Item[i]->Caption = HdrsCapData[i];
        }
    }
    // clear old data
    for (int i = 0; i < Rows->Count; i++)
        setRow(i, "");

    const TPacketInfo &Packet = FGridBuffer[ItemNr];
    setRow(ColCapDataTime, IcsTimeToZStr(Packet.PacketDT));
    setRow(ColCapDataPlen, IntToStr(Packet.PacketLen));
    setRow(ColCapDataEProto, IcsEtherProtoName(Packet.EtherProto));
    setRow(ColCapDataMacAddSrc, IcsMacToStr(Packet.EtherSrc));
    setRow(ColCapDataMacVenSrc, Packet.VendorSrc);
    setRow(ColCapDataMacAddTar, IcsMacToStr(Packet.EtherDest));
    setRow(ColCapDataMacVenTar, Packet.VendorDest);
    if (Packet.EtherProto == ETHERNET_IP || Packet.EtherProto == ETHERNET_IPV6) {
        setRow(ColCapDataTrans, IcsIPProtoName(Packet.ProtoType));
        setRow(ColCapDataSocFam, String(SocketFamilyNames[Packet.SocketFamily]));
        setRow(ColCapDataAddrSrc, Packet.DispAddrSrc);
        setRow(ColCapDataHostSrc, Packet.HostNameSrc);
        setRow(ColCapDataAddrDes, Packet.DispAddrDest);
        setRow(ColCapDataHostDes, Packet.HostNameDest);
        setRow(ColCapDataIpTTL, IntToStr(Packet.IpTTL));
        if (Packet.ProtoType == IPPROTO_TCP || Packet.ProtoType == IPPROTO_UDP) {
            setRow(ColCapDataPortSrc, IntToStr(Packet.PortSrc));
            setRow(ColCapDataPortDes, IntToStr(Packet.PortDest));
            setRow(ColCapDataServ, Packet.DispServ);
        }
        if (Packet.ProtoType == IPPROTO_TCP) {
            setRow(ColCapDataTFlag, IcsTCPFlags(Packet.TcpFlags));
            setRow(ColCapDataTSeq, IntToStr(static_cast<__int64>(Packet.TCPCurSeq)));
            setRow(ColCapDataTASeq, IntToStr(static_cast<__int64>(Packet.TCPNextSeq)));
            setRow(ColCapDataTWin, IntToStr(static_cast<int>(Packet.TCPWinSize)));
        }
        if (Packet.ProtoType == IPPROTO_ICMP)
            setRow(ColCapDataIcmp, LowerCase(IcsICMPType(Packet.IcmpType)));
    }
    setRow(ColCapDataDLen, IntToStr(Packet.DataLen));
    if (Packet.DataLen <= 0)
        return;

    // ASCII representation of the data, 40 bytes per row (String and AnsiString are 1-based)
    const String DataText = String(Packet.DataBuf);
    const int AsciiRows = Packet.DataLen / DataPerRow + 1;
    int DOff = 1;
    for (int Row = ColCapDataData; Row < ColCapDataData + AsciiRows; Row++) {
        if (Rows->Count <= Row)
            addRow();
        setRow(Row, IcsEscapeCRLF(IcsStrRemCntls(DataText.SubString(DOff, DataPerRow))));
        DOff += DataPerRow;
    }
    // one empty row, then the hex representation, 20 bytes per row
    const int GapRow = ColCapDataData + AsciiRows;
    if (Rows->Count <= GapRow)
        addRow();   // Delphi adds this row on every selection, so the list keeps growing
    const int HexPerRow = DataPerRow / 2;
    const int HexRows = Packet.DataLen / HexPerRow + 1;   // Delphi: (DataLen div 40 * 2) + 1, which drops up to 19 bytes
    DOff = 1;
    for (int Row = GapRow + 1; Row < GapRow + 1 + HexRows; Row++) {
        if (Rows->Count <= Row)
            addRow();
        setRow(Row, IcsBufferToHex(Packet.DataBuf.SubString(DOff, HexPerRow)));
        DOff += HexPerRow;
    }
}

void __fastcall TMonForm::MonTrafficWinData(TObject *Sender, TListItem *Item)
{
    const int ItemNr = Item->Index;   // line for which data is required
    if (ItemNr >= FTrafficClass->TotTraffic)
        return;
    const PTrafficInfo TrafficRec = FTrafficClass->GetSortedTraf(ItemNr);
    if (!TrafficRec)
        return;   // sanity check
    String DispLoc = TrafficRec->SrcDispAddr;
    String DispRem = TrafficRec->DestDispAddr;
    if (TrafficHostNames->Checked) {
        if (TrafficRec->SrcHostName != "")
            DispLoc = TrafficRec->SrcHostName;
        if (TrafficRec->DestHostName != "")
            DispRem = TrafficRec->DestHostName;
    }
    Item->Caption = DispLoc;
    Item->SubItems->Add(DispRem);
    Item->SubItems->Add(TrafficRec->ServName);
    Item->SubItems->Add(IntToKbyte(TrafficRec->BytesSent));
    Item->SubItems->Add(IntToKbyte(TrafficRec->PacksSent));
    Item->SubItems->Add(IntToKbyte(TrafficRec->BytesRecv));
    Item->SubItems->Add(IntToKbyte(TrafficRec->PacksRecv));
    Item->SubItems->Add(TimeToStr(TrafficRec->FirstDT));
    Item->SubItems->Add(TimeToStr(TrafficRec->LastDT));
}

// The filter lists are rebuilt from the memo, so removed addresses are dropped (Delphi only adds).
void TMonForm::LoadIpAddrList()
{
    FMonitorFilter->ClearLists();   // clears the IPv4 and IPv6 lists only
    for (int i = 0; i < IpAddrList->Lines->Count; i++)
        FMonitorFilter->SetIpAddr(IpAddrList->Lines->Strings[i]);
}

void __fastcall TMonForm::IcsDomainNameCache1DNLogEvent(TObject *Sender, const String Msg)
{
    if (DnsCacheLog->Checked)
        FBuffLogLines += Msg + IcsCRLF;
}

void __fastcall TMonForm::IpAddrListExit(TObject *Sender)
{
    if (!FMonitorFilter)
        return;   // focus can move while the form is being destroyed
    LoadIpAddrList();
}

void __fastcall TMonForm::IpAddrLocalClick(TObject *Sender)
{
    FMonitorFilter->LocalIpAddr = IpAddrLocal->Checked;
}

void __fastcall TMonForm::FilterIpAddrClick(TObject *Sender)
{
    FMonitorFilter->FilterIpAddr = static_cast<TMonFilterMeth>(FilterIpAddr->ItemIndex);
}

void __fastcall TMonForm::FilterProtocolClick(TObject *Sender)
{
    FMonitorFilter->FilterProtocol = static_cast<TMonFilterMeth>(FilterProtocol->ItemIndex);
}

void __fastcall TMonForm::PortsListChange(TObject *Sender)
{
    // Rebuilt on every change: Delphi only adds, so typing 443 also leaves 4 and 44 in the filter.
    // SetPort is also a Windows macro (winspool.h, SetPortW); the form header #undefs it before
    // including OverbyteIcsMonCommon.hpp, or this call links against a SetPortW that ICS does not export.
    if (!FMonitorFilter)
        return;   // not created yet, or already freed
    FMonitorFilter->PortList->Clear();
    for (int i = 0; i < PortsList->Lines->Count; i++)
        FMonitorFilter->SetPort(PortsList->Lines->Strings[i]);
}

void __fastcall TMonForm::ProtocolARPClick(TObject *Sender)
{
    FMonitorFilter->ProtoARP = ProtocolARP->Checked;
}

void __fastcall TMonForm::ProtocolBroadcastClick(TObject *Sender)
{
    FMonitorFilter->ProtoBroadcast = ProtocolBroadcast->Checked;
}

void __fastcall TMonForm::ProtocolDnsClick(TObject *Sender)
{
    FMonitorFilter->ProtoDns = ProtocolDns->Checked;
}

void __fastcall TMonForm::ProtocolHttpClick(TObject *Sender)
{
    FMonitorFilter->ProtoHttp = ProtocolHttp->Checked;
}

void __fastcall TMonForm::ProtocolICMPClick(TObject *Sender)
{
    FMonitorFilter->ProtoICMP = ProtocolICMP->Checked;
}

void __fastcall TMonForm::ProtocolIPv4Click(TObject *Sender)
{
    FMonitorFilter->ProtoIPv4 = ProtocolIPv4->Checked;
}

void __fastcall TMonForm::ProtocolIPv6Click(TObject *Sender)
{
    FMonitorFilter->ProtoIPv6 = ProtocolIPv6->Checked;
}

void __fastcall TMonForm::ProtocolIRCClick(TObject *Sender)
{
    FMonitorFilter->ProtoIRC = ProtocolIRC->Checked;
}

void __fastcall TMonForm::ProtocolNonIpClick(TObject *Sender)
{
    FMonitorFilter->ProtoNonIp = ProtocolNonIp->Checked;
}

void __fastcall TMonForm::ProtocolSNMPClick(TObject *Sender)
{
    FMonitorFilter->ProtoSNMP = ProtocolSNMP->Checked;
}

void __fastcall TMonForm::ProtocolSyslogClick(TObject *Sender)
{
    FMonitorFilter->ProtoSyslog = ProtocolSyslog->Checked;
}

void __fastcall TMonForm::ProtocolTCPClick(TObject *Sender)
{
    FMonitorFilter->ProtoTCP = ProtocolTCP->Checked;
}

void __fastcall TMonForm::ProtocolUdpClick(TObject *Sender)
{
    FMonitorFilter->ProtoUdp = ProtocolUdp->Checked;
}

void __fastcall TMonForm::ProtocolUPnPClick(TObject *Sender)
{
    FMonitorFilter->ProtoUPnP = ProtocolUPnP->Checked;
}

// Log lines are buffered here and written to the memo by the timer.
void TMonForm::LogLine(const String &Msg)
{
    FBuffLogLines += Msg + IcsCRLF;
}

void __fastcall TMonForm::LogEvent(TObject *Sender, const String Msg)
{
    LogLine(Msg);
}

void __fastcall TMonForm::doClearLogClick(TObject *Sender)
{
    MonLogWin->Lines->Clear();
    FBuffLogLines = "";
}

void __fastcall TMonForm::doDnsCacheClearClick(TObject *Sender)
{
    IcsDomainNameCache1->MaintClearAll();
}

void __fastcall TMonForm::doDnsCacheListClick(TObject *Sender)
{
    FBuffLogLines += IcsDomainNameCache1->ListCache() + IcsCRLF;
}

void __fastcall TMonForm::doExitClick(TObject *Sender)
{
    if (FCaptureMode != TCaptureMode::None)
        CaptureStop();
    Close();
}

void __fastcall TMonForm::doPackGridClearClick(TObject *Sender)
{
    MonGridWin->Items->Clear();
    FGridBuffer.clear();
}

void __fastcall TMonForm::doPackGridStartClick(TObject *Sender)
{
    FGridBuffer.reserve(512);   // somewhere to store our data for grid
    FCaptureMode = TCaptureMode::Grid;
    CaptureStart();
}

void __fastcall TMonForm::doPackGridStopClick(TObject *Sender)
{
    CaptureStop();
}

void __fastcall TMonForm::doPackLogStartClick(TObject *Sender)
{
    FCaptureMode = TCaptureMode::Log;
    CaptureStart();
    MonLogWin->Lines->Add(String(IcsCRLF) + HeaderLine + IcsCRLF);
}

void __fastcall TMonForm::doPackLogStopClick(TObject *Sender)
{
    CaptureStop();
}

void __fastcall TMonForm::doTrafficClearClick(TObject *Sender)
{
    MonTrafficWin->Items->Clear();
    FTrafficClass->Clear();
}

void __fastcall TMonForm::doTrafficLogTotalsClick(TObject *Sender)
{
    doClearLogClick(this);
    PageControl1->ActivePage = TabPacketsLog;
    LogLine(sTrafficHdr);
    if (FTrafficClass->TotTraffic == 0)
        return;
    doTrafficLogTotals->Enabled = false;
    FTrafficClass->UpdateService();
    for (int i = 0; i < FTrafficClass->TotTraffic; i++) {
        const String S = FTrafficClass->FmtTrafStr(FTrafficClass->GetSortedTraf(i), TrafficHostNames->Checked);
        if (S != "")   // sanity check
            LogLine(S);
    }
    LogLine(String(IcsCRLF) + sServiceHdr);   // Delphi adds it to the memo directly, ahead of the buffered lines
    for (int i = 0; i < FTrafficClass->TotService; i++) {
        const String S = FTrafficClass->GetFmtServStr(i);
        if (S != "")   // sanity check
            LogLine(S);
    }
    doTrafficLogTotals->Enabled = true;
}

void __fastcall TMonForm::doTrafficStartClick(TObject *Sender)
{
    FCaptureMode = TCaptureMode::Traffic;
    MonTrafficWin->Items->Clear();
    FTrafficClass->Clear();
    CaptureStart();
}

void __fastcall TMonForm::doTrafficStopClick(TObject *Sender)
{
    CaptureStop();
}

void TMonForm::CapButtons(bool Run)
{
    doPackGridStart->Enabled = !Run;
    doPackLogStart->Enabled = !Run;
    doTrafficStart->Enabled = !Run;
    doPackGridStop->Enabled = Run;
    doPackLogStop->Enabled = Run;
    doTrafficStop->Enabled = Run;
}

void TMonForm::CaptureStart()
{
    if (MonIpList->ItemIndex < 0) {
        FCaptureMode = TCaptureMode::None;
        return;
    }
    if (UseWinNCap->Checked && !FMonitorPcap) {   // Delphi raises an access violation here
        LogLine("Failed to Start Monitor - Npcap packet.dll not loaded");
        FCaptureMode = TCaptureMode::None;
        return;
    }
    try {
        LoadIpAddrList();
        if (UseWinNCap->Checked) {
            FMonitorPcap->MonAdapter = FMonitorPcap->AdapterNameList->Strings[MonAdptList->ItemIndex];
            const int Found = FMonitorPcap->GetIPAddresses(FMonitorPcap->MonAdapter, FAdapterIPList.get(),
                                                           FAdapterMaskList.get(), FAdapterBcastList.get());
            if (Found > 0)
                FMonitorPcap->Addr = FAdapterIPList->Strings[0];
            else
                FMonitorPcap->Addr = MonIpList->Items->Strings[MonIpList->ItemIndex];
            FMonitorPcap->IgnoreData = ShowIgnoreData->Checked;
            FMonitorPcap->Promiscuous = MonPromiscuous->Checked;
            FMonitorPcap->StartMonitor();
            if (!FMonitorPcap->Connected) {
                LogLine(FMonitorPcap->LastError);
                FCaptureMode = TCaptureMode::None;   // Delphi leaves the capture mode set here
                return;
            }
            LogLine("Capture Started - " + FMonitorPcap->MonAdapter + " on " + FMonitorPcap->Addr);
        }
        else {
            FMonitorSocket->Addr = MonIpList->Items->Strings[MonIpList->ItemIndex];
            FMonitorSocket->IgnoreData = ShowIgnoreData->Checked;
            FMonitorSocket->StartMonitor();
            LogLine("Capture Started - Raw Sockets on " + FMonitorSocket->Addr);
        }
        CapButtons(true);
    }
    catch (Exception &E) {
        LogLine("Failed to Start Monitor - " + IcsGetExceptMess(&E));
        if (!UseWinNCap->Checked)
            FMonitorSocket->StopMonitor();
        FCaptureMode = TCaptureMode::None;
        CapButtons(false);
    }
}

void TMonForm::CaptureStop()
{
    if (FCaptureMode != TCaptureMode::None) {
        LogLine(String("Capture Stopped") + IcsCRLF);
        if (UseWinNCap->Checked) {
            if (FMonitorPcap)
                FMonitorPcap->StopMonitor();
        }
        else
            FMonitorSocket->StopMonitor();
    }
    FCaptureMode = TCaptureMode::None;
    CapButtons(false);
}

void __fastcall TMonForm::TimerTimer(TObject *Sender)
{
    // Delphi wraps the memo update in an empty except; a failure here is left to the application.
    const int DispLen = FBuffLogLines.Length();
    if (DispLen > 0) {
        FBuffLogLines.SetLength(DispLen - 2);   // remove CRLF
        MonLogWin->Lines->Add(FBuffLogLines);
        SendMessage(MonLogWin->Handle, EM_LINESCROLL, 0, 999999);
        FBuffLogLines = "";
    }

    // update traffic captions
    if (FCaptureMode == TCaptureMode::None)
        return;
    String Traffic;
    if (UseWinNCap->Checked && FMonitorPcap) {
        Traffic = "Traffic: Sent " + IntToKbyte(FMonitorPcap->TotSendBytes) + ", Received " +
                  IntToKbyte(FMonitorPcap->TotRecvBytes) + IcsCRLF + "Packets Sent " +
                  IntToStr(FMonitorPcap->TotSendPackets) + ", Received " + IntToStr(FMonitorPcap->TotRecvPackets);
    }
    else {
        Traffic = "Traffic: Sent " + IntToKbyte(FMonitorSocket->TotSendBytes) + ", Received " +
                  IntToKbyte(FMonitorSocket->TotRecvBytes) + IcsCRLF + "Packets Sent " +
                  IntToStr(FMonitorSocket->TotSendPackets) + ", Received " + IntToStr(FMonitorSocket->TotRecvPackets);
    }
    LabelTrafficLog->Caption = Traffic;
    LabelTrafficGrid->Caption = Traffic;
    LabelTrafficTotals->Caption = Traffic;

    // for traffic, update TListView lines which causes OnData event to get new details
    if (FCaptureMode == TCaptureMode::Traffic) {
        MonTrafficWin->Items->Count = FTrafficClass->TotTraffic;   // lines of traffic to display
        MonTrafficWin->Refresh();
    }
}

void __fastcall TMonForm::MonAdptListClick(TObject *Sender)
{
    if (MonAdptList->Items->Count == 0 || MonAdptList->ItemIndex < 0 || !FMonitorPcap)
        return;
    const int Found = FMonitorPcap->GetIPAddresses(FMonitorPcap->AdapterNameList->Strings[MonAdptList->ItemIndex],
                                                   FAdapterIPList.get(), FAdapterMaskList.get(),
                                                   FAdapterBcastList.get());
    if (Found == 0)
        return;
    MonIpList->Items->Assign(FAdapterIPList.get());
    if (MonIpList->Items->Count > 0)
        MonIpList->ItemIndex = 0;
}

void __fastcall TMonForm::UseWinNCapClick(TObject *Sender)
{
    if (UseWinNCap->Checked)
        MonAdptListClick(this);
    else {
        MonIpList->Items = LocalIPList(sfAny);
        if (MonIpList->Items->Count > 0)
            MonIpList->ItemIndex = 0;
    }
}
