﻿/*
  ICS Batch DNS Lookup - C++Builder translation of the Delphi demo
  OverbyteIcsBatchDnsLookup1.pas (demos-delphi-vcl), ICS V9.8.

  Looks up a list of host names asynchronously (IPv4 and IPv6) with TIcsDomainNameCache, and
  converts international domain names to Punycode and back. Names and settings are kept in
  the ICS ini file.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The lookup method is saved in the [Setup] section, where it is read from.
  - FInstances is an int, not a Byte.
  - The ini file object is held in std::unique_ptr and released in FormDestroy.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsBatchDnsLookup1.h"
#include "OverbyteIcsUtils.hpp"

#pragma package(smart_init)
#pragma link "OverbyteIcsDnsQuery"
#pragma link "OverbyteIcsTypes"
#pragma link "OverbyteIcsWndControl"
#pragma resource "*.dfm"

TBatchDnsLookupForm *BatchDnsLookupForm;

namespace {

const String SectionWindow    = "Window";
const String KeyTop           = "Top";
const String KeyLeft          = "Left";
const String SectionSetup     = "Setup";
const String KeyDNCacheMethod = "DNCacheMethod";
const String KeyInstances     = "NumberOfInstances";
const String SectionDnsNames  = "DnsNames";
const String KeyDnsName       = "Item";

// Default list, loaded only when the ini file holds no names. It mixes IPv4, IPv6, Unicode
// names (they need a Unicode build) and two invalid names.
const wchar_t *const DefaultDnsNames[] = {
    L"www.overbyte.eu",
    L"svn.overbyte.be",
    L"wiki.overbyte.eu",
    L"mail.magsys.co.uk",
    L"ipv4.magsys.co.uk",
    L"ipv6.magsys.co.uk",
    L"www.cloudflare.com",
    L"cloudflare-ech.com",
    L"www.microsoft.com",
    L"ipv6.google.com",
    L"www.google.com",
    L"sourceforge.net",
    L"msdn.microsoft.com",
    L"localhost",
    L"127.0.0.1",
    L"::1",
    L"strøm.no",
    L"prøve.ftptest.co.uk",
    L"テスト.ftptest.co.uk",
    L"ölçek.ftptest.co.uk",
    L"www.bad(name).com",
    L"www.-badname.com",
};

static_assert(sizeof(wchar_t) == sizeof(WideChar), "UnicodeString literals need a 16-bit wchar_t");

} // namespace

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

void __fastcall TBatchDnsLookupForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    IcsNameThreadForDebugging("Main");
    ResultMemo->Clear();
    DnsNamesMemo->WordWrap = false;
    FIniFile.reset(new TIcsIniFile(GetIcsIniFileName(), true));
    ActiveControl = StartButton;
}

void __fastcall TBatchDnsLookupForm::FormDestroy(TObject *Sender)
{
    FIniFile.reset();
}

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

    Top = FIniFile->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = FIniFile->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
    DNCacheMethod->ItemIndex = FIniFile->ReadInteger(SectionSetup, KeyDNCacheMethod, 0);
    FInstances = FIniFile->ReadInteger(SectionSetup, KeyInstances, 4);
    FIniFile->ReadStrings(SectionDnsNames, KeyDnsName, DnsNamesMemo->Lines);

    // The names are stored in the ini file, so a new default list only appears once those
    // lines are removed (double click the names memo, then close the program).
    if (DnsNamesMemo->Lines->Count == 0) {
        for (const wchar_t *name : DefaultDnsNames)
            DnsNamesMemo->Lines->Add(name);
    }
    InstancesEdit->Text = IntToStr(FInstances);
}

void __fastcall TBatchDnsLookupForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    try {
        FIniFile->WriteInteger(SectionWindow, KeyTop, Top);
        FIniFile->WriteInteger(SectionWindow, KeyLeft, Left);
        // Delphi wrote this to SectionWindow but read it from SectionSetup, so it was never restored
        FIniFile->WriteInteger(SectionSetup, KeyDNCacheMethod, DNCacheMethod->ItemIndex);
        FInstances = StrToIntDef(InstancesEdit->Text, 3);
        FIniFile->WriteInteger(SectionSetup, KeyInstances, FInstances);
        FIniFile->WriteStrings(SectionDnsNames, KeyDnsName, DnsNamesMemo->Lines);
        FIniFile->UpdateFile();
    }
    catch (Exception &E) {
        MessageDlg(E.ClassName() + " " + E.Message, mtError, TMsgDlgButtons() << mbOK, 0);
    }
}

// Called by TIcsDomainNameCache when a lookup finishes; the request tag identifies the memo line.
void __fastcall TBatchDnsLookupForm::IcsDomainNameCache1DNUpdateEvent(TObject *Sender, int ItemNr)
{
    TIcsDomainNameCache *cache = static_cast<TIcsDomainNameCache*>(Sender);
    const int row = cache->GetDNItem(ItemNr).ReqTag;
    // As in the Delphi, a reply that arrives after Start is pressed again can still fill its old
    // row; this check only stops it writing to a line that does not exist (yet).
    if (row < 0 || row >= ResultMemo->Lines->Count)
        return;
    ResultMemo->Lines->Strings[row] = cache->BuildRespList(ItemNr);
    ResultMemo->Update();
}

void __fastcall TBatchDnsLookupForm::StartButtonClick(TObject *Sender)
{
    IDNMemo->Clear();
    DecodeMemo->Clear();
    ResultMemo->Clear();   // Delphi demo bug fix: its Clear sat inside a loop over the names, so an empty list kept old results
    ResultMemo->Update();
    IDNMemo->Update();
    FInstances = StrToIntDef(InstancesEdit->Text, 1);

    IcsDomainNameCache1->MaxLookups = FInstances;
    IcsDomainNameCache1->DNMethod = static_cast<TDNMethod>(DNCacheMethod->ItemIndex);
    for (int i = 0; i < DnsNamesMemo->Lines->Count; i++) {
        Application->ProcessMessages();
        const String name = DnsNamesMemo->Lines->Strings[i];
        if (name != "") {
            bool errFlag = false;
            ResultMemo->Lines->Add("Resolving..");
            IDNMemo->Lines->Add(IcsIDNAToASCII(IcsTrim(name), false, errFlag));
            IcsDomainNameCache1->LookupHostAsync(IcsTrim(name), i,   // the tag is the ResultMemo line
                static_cast<TSocketFamily>(SocketFamilyComboBox->ItemIndex),
                IcsDomainNameCache1DNUpdateEvent);
        }
        else {
            ResultMemo->Lines->Add("");
            IDNMemo->Lines->Add("");
        }
    }
}

void __fastcall TBatchDnsLookupForm::DNCacheMethodClick(TObject *Sender)
{
    IcsDomainNameCache1->MaintClearAll();   // clear the cache when the lookup method changes
}

void __fastcall TBatchDnsLookupForm::DnsNamesMemoDblClick(TObject *Sender)
{
    DnsNamesMemo->Lines->Clear();
}

void __fastcall TBatchDnsLookupForm::doIDNEncodeClick(TObject *Sender)
{
    IDNMemo->Clear();
    DecodeMemo->Clear();
    for (int i = 0; i < DnsNamesMemo->Lines->Count; i++) {
        bool errFlag = false;
        String uniStr;
        String punyStr = IcsIDNAToASCII(DnsNamesMemo->Lines->Strings[i], true, errFlag);
        if (errFlag)
            punyStr = "IDNAToAscii Failed";
        else {
            uniStr = IcsIDNAToUnicode(punyStr, errFlag);
            if (errFlag)
                uniStr = "IDNAToUnicode Failed";
        }
        IDNMemo->Lines->Add(punyStr);
        DecodeMemo->Lines->Add(uniStr);
    }
}
