/*
  ICS Domain Name Server Lookup demo - C++Builder translation of the Delphi demo OverbyteIcsNsLookup1.pas
  (demos-delphi-vcl), ICS V9.8.

  Sends DNS queries with TDnsQuery (one query type, or the seven most common at once), asynchronously
  or synchronously, to a single DNS server, a list of servers or the public servers, and shows the
  decoded answers and a dump of the raw response. The window position and the settings are kept in
  the ICS ini file.

  How this C++ version differs from the Delphi original, apart from syntax:
  - A DNS response code the DnsRCodeTable has no name for is shown as "unknown" (the Delphi indexes
    past the end of the table).
  - Lookup and Lookup All share one PrepareQuery method for the settings they both apply.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsNsLookup1.h"

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

TNsLookupForm *NsLookupForm;

namespace {

const String SectionWindow = "Window";
const String KeyTop = "Top";
const String KeyLeft = "Left";
const String KeyWidth = "Width";
const String KeyHeight = "Height";
const String SectionData = "Data";
const String KeyName = "Name";
const String KeyDns = "Dns";
const String KeySync = "Sync";
const String KeyStrategy = "Strategy";
const String KeyServerList = "ServerList";
const String KeyQueryType = "QueryType";

// Named AppCopyRight: every ICS unit exports its own CopyRight, so that name is ambiguous in C++.
const String AppCopyRight = String(" NsLookup") + IcsVerLitCopyright;

}  // namespace

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

void __fastcall TNsLookupForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FIniFileName = GetIcsIniFileName();

    DnsQueryType->Items->Clear();
    for (size_t i = 0; i < DnsReqTable.size(); i++)
        DnsQueryType->Items->Add(DnsReqTable[i].Asc + " [" + DnsReqTable[i].Desc + "]");
    DnsQueryType->ItemIndex = 0;

    DnsEdit->Items->Clear();
    for (size_t i = 0; i < DnsPublicServerTable.size(); i++)
        DnsEdit->Items->Add(DnsPublicServerTable[i]);
}

void __fastcall TNsLookupForm::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);
        NameEdit->Text = IniFile->ReadString(SectionData, KeyName, "embarcadero.com");
        DnsEdit->Text = IniFile->ReadString(SectionData, KeyDns, DnsPublicServerTable[0]);
        RequestSync->Checked = IniFile->ReadBool(SectionData, KeySync, false);
        ServerStratagy->ItemIndex = IniFile->ReadInteger(SectionData, KeyStrategy, 0);
        ServerList->Lines->CommaText = IniFile->ReadString(SectionData, KeyServerList, ServerList->Lines->CommaText);
        DnsQueryType->ItemIndex = IniFile->ReadInteger(SectionData, KeyQueryType, 0);
        DisplayMemo->Clear();
        Display(Trim(AppCopyRight));
    }
}

void __fastcall TNsLookupForm::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->WriteString(SectionData, KeyName, NameEdit->Text);
    IniFile->WriteString(SectionData, KeyDns, DnsEdit->Text);
    IniFile->WriteBool(SectionData, KeySync, RequestSync->Checked);
    IniFile->WriteInteger(SectionData, KeyStrategy, ServerStratagy->ItemIndex);
    IniFile->WriteString(SectionData, KeyServerList, ServerList->Lines->CommaText);
    IniFile->WriteInteger(SectionData, KeyQueryType, DnsQueryType->ItemIndex);
    IniFile->UpdateFile();
}

void TNsLookupForm::Display(const String &Msg)
{
    if (DisplayMemo->Lines->Count > 200)
        DisplayMemo->Lines->Delete(0);
    DisplayMemo->Lines->Add(Msg);
}

void TNsLookupForm::DumpDnsResponse()
{
    const int Len = DnsQuery1->ResponseLen;
    // ResponseBuf is a char *; unsigned char so bytes above 127 are shown as <n>, not as negative numbers.
    const unsigned char *P = reinterpret_cast<const unsigned char *>(DnsQuery1->ResponseBuf);
    Display("Response dump (" + IntToStr(Len) + " bytes):");

    String Buf;
    for (int i = 0; i < Len; i++) {
        if (P[i] >= ' ' && P[i] <= '~')
            Buf += WideChar(P[i]);
        else
            Buf += "<" + IntToStr(static_cast<int>(P[i])) + ">";
        if ((i + 1) % 16 == 0) {
            Display("  " + Buf);
            Buf = "";
        }
    }
    if (Buf.Length() > 0)
        Display("  " + Buf);
}

void __fastcall TNsLookupForm::DnsQuery1LogEvent(TObject *Sender, const System::UnicodeString Msg)
{
    Display(Msg);
}

void __fastcall TNsLookupForm::DnsQuery1RequestDone(TObject *Sender, System::Word Error)
{
    Timer1->Enabled = false;
    AllButton->Enabled = true;
    LookupButton->Enabled = true;
    if (Error != 0) {
        Display("Error #" + IntToStr(static_cast<int>(Error)));
        return;
    }

    const int RCode = DnsQuery1->ResponseCode;
    // The Delphi indexes DnsRCodeTable with the 4-bit response code, but the table only names codes 0 to 5.
    const String RCodeName = (RCode >= 0 && static_cast<size_t>(RCode) < DnsRCodeTable.size())
                                 ? DnsRCodeTable[RCode] : String("unknown");

    // only show question for a single query, not ALL
    Display("DNS Server         : " + DnsQuery1->ServerAddr);
    Display("ID                 : " + IntToStr(DnsQuery1->ResponseID));
    Display("ResponseCode       : " + IntToStr(RCode) + " " + RCodeName);
    Display("OpCode             : " + IntToStr(DnsQuery1->ResponseOpCode));
    Display("Authoritative      : " + IntToStr(DnsQuery1->ResponseAuthoritative ? 1 : 0));
    Display("Truncation         : " + IntToStr(DnsQuery1->ResponseTruncation ? 1 : 0));
    Display("RecursionAvailable : " + IntToStr(DnsQuery1->ResponseRecursionAvailable ? 1 : 0));
    Display("QDCount            : " + IntToStr(DnsQuery1->ResponseQDCount));
    Display("ANCount            : " + IntToStr(DnsQuery1->ResponseANCount));
    Display("NSCount            : " + IntToStr(DnsQuery1->ResponseNSCount));
    Display("ARCount            : " + IntToStr(DnsQuery1->ResponseARCount));
    Display("ResponseLen        : " + IntToStr(DnsQuery1->ResponseLen));
    Display("QuestionName       : " + DnsQuery1->QuestionName);
    Display("QuestionType       : " + IntToStr(DnsQuery1->QuestionType) + " " + FindDnsReqTypeName(DnsQuery1->QuestionType));
    Display("QuestionClass      : " + IntToStr(DnsQuery1->QuestionClass));

    for (int i = 0; i < DnsQuery1->AnswerTotal; i++) {
        const TRRRecord Rec = DnsQuery1->AnswerRecord[i];   // the property returns the record by value
        Display("Answer #" + IntToStr(i + 1));
        Display("  AnswerName       : " + Rec.AnswerName + "  (" + String(Rec.RRName) + ")");
        Display("  AnswerType       : " + IntToStr(static_cast<int>(Rec.RRType)) + " " + FindDnsReqTypeName(Rec.RRType));
        Display("  AnswerClass      : " + IntToStr(static_cast<int>(Rec.RRClass)));
        Display("  AnswerTTL        : " + IntToStr(static_cast<__int64>(Rec.TTL)));
        Display("  Result Length    : " + IntToStr(static_cast<int>(Rec.RDLength)));

        switch (Rec.RRType) {
        case DnsQueryMX:
            Display("  MXPreference     : " + IntToStr(Rec.MxPref));
            Display("  MXExchange       : " + Rec.HostName);
            break;
        case DnsQueryA:
        case DnsQueryAAAA:
            Display("  Address          : " + String(Rec.RDData));
            break;
        case DnsQueryPTR:
        case DnsQueryNS:
        case DnsQueryCNAME:
            Display("  Hostname         : " + Rec.HostName);
            break;
        default:
            Display("  Result           : " + String(Rec.RDData));
            break;
        }
    }

    DumpDnsResponse();
}

void __fastcall TNsLookupForm::AbortButtonClick(TObject *Sender)
{
    if (!LookupButton->Enabled || !AllButton->Enabled)
        DnsQuery1->AbortQuery();
}

void TNsLookupForm::PrepareQuery()
{
    AllButton->Enabled = false;
    LookupButton->Enabled = false;
    DnsQuery1->Proto = UdpRadioButton->Checked ? "udp" : "tcp";
    DnsQuery1->Addr = DnsEdit->Text;
    DnsQuery1->ServerStrat = static_cast<TDnsSrvStrat>(ServerStratagy->ItemIndex);
    DnsQuery1->ServerList = ServerList->Lines;   // copies the lines into the component
}

void __fastcall TNsLookupForm::AllButtonClick(TObject *Sender)
{
    PrepareQuery();
    if (RequestSync->Checked)
        DnsQuery1->QueryAllSync(Trim(NameEdit->Text));
    else {
        Timer1->Enabled = true;
        FRequestID = DnsQuery1->QueryAll(Trim(NameEdit->Text));
    }
}

void __fastcall TNsLookupForm::ClearDisplayBitBtnClick(TObject *Sender)
{
    DisplayMemo->Clear();
}

void __fastcall TNsLookupForm::LookupButtonClick(TObject *Sender)
{
    PrepareQuery();
    const int QueryNumber = DnsReqTable[DnsQueryType->ItemIndex].Num;
    if (RequestSync->Checked) {
        DnsQuery1->QueryAnySync(Trim(NameEdit->Text), QueryNumber);
        Display("Sync Request Done");
    }
    else {
        Timer1->Enabled = true;
        FRequestID = DnsQuery1->QueryAny(Trim(NameEdit->Text), QueryNumber);
        Display("Request ID         : " + IntToStr(FRequestID));
    }
}

void __fastcall TNsLookupForm::Timer1Timer(TObject *Sender)
{
    Timer1->Enabled = false;
    DnsQuery1->AbortQuery();   // gives up after the 30 second timer interval
}
