/*
  ICS Mail Queue demo - C++Builder translation of the Delphi demo OverbyteIcsMailQuTst1.pas
  (demos-delphi-vcl), ICS V9.8.

  Shows TIcsMailQueue, which prepares, queues and sends email from a background thread, retrying
  failures on a schedule. Mail goes through up to two relay servers, specific servers, or the
  recipient's MX servers found by DNS. "Queue Mail" builds one message per recipient line; the EML
  buttons queue an existing .eml file, either to the recipients in its header or to each recipient
  line. XOAuth2 and OAuthBearer logins get their token from TIcsRestEmail (one OAuth2 account only,
  and its refresh token is not cleared when the server account changes).

  How this C++ version differs from the Delphi original, apart from syntax:
  - The unit-level variables are form members, except IniFileName, DiagWinFlag and ViewQuWinFlag,
    which the diagnostic and queue windows also use.
  - The AbortFlag variable is not translated: nothing in the demo sets it.
  - The three queue buttons share PrepareToQueue (stop and restart the queue, copy the settings), and
    each keeps its button disabled with an RAII guard instead of try/finally.
  - FormDestroy is not bound: the destructor detaches the queue events and stops the queue, because
    the C++ members those events write to are destroyed before the VCL frees the components.
  - The log file is a TIcsBuffLogStream with no Owner, held in a std::unique_ptr.
*/

#include <vcl.h>
#pragma hdrstop

#include <System.SysUtils.hpp>
#include <Winapi.RichEdit.hpp>

#include "OverbyteIcsMailQuTst1.h"
#include "OverbyteIcsMailQuTstdiag.h"
#include "OverbyteIcsMailQuTstView.h"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsUtils.hpp"

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

TDemoForm *DemoForm;
String IniFileName;
bool DiagWinFlag = false;
bool ViewQuWinFlag = false;

namespace {
const int MaxAddressesAllowed = 20;   // stops sending too many emails by accident
const wchar_t *const SectionData = L"Main";

String ExtractEmail(const String &DoubleAddr)
{
    String friendlyName;
    return IcsParseEmail(DoubleAddr, friendlyName);
}

bool IsOAuth2(TSmtpAuthType Auth)
{
    return Auth == smtpAuthXOAuth2 || Auth == smtpAuthOAuthBearer;
}

// The edit boxes of one of the two mail servers on the Servers tab.
struct TServerControls
{
    TEdit *Smtp;
    TEdit *Port;
    TRadioGroup *AuthType;
    TEdit *AuthUser;
    TEdit *AuthPass;
    TComboBox *Secure;
    TComboBox *Security;
    TCheckBox *SslErr;
};

// Adds a mail server to the queue and fills it from the form.
TMailServer *AddMailServer(TIcsMailQueue *Queue, const TServerControls &Ctl, const String &HeloHost)
{
    // Delphi fills MailServers[1] for the second server, which does not exist when the first is blank.
    TMailServer *srv = static_cast<TMailServer*>(Queue->MailServers->Add());
    srv->Port = Ctl.Port->Text;   // 25, 465 or 587
    srv->Host = Ctl.Smtp->Text;
    srv->AuthType = static_cast<TSmtpAuthType>(Ctl.AuthType->ItemIndex);
    srv->Username = Ctl.AuthUser->Text;
    srv->Password = Ctl.AuthPass->Text;
    srv->SslType = static_cast<TSmtpSslType>(Ctl.Secure->ItemIndex);
    srv->LocalAddr = "0.0.0.0";
    srv->SignOn = HeloHost;
    srv->SslCliSecurity = static_cast<TSslCliSecurity>(Ctl.Security->ItemIndex);
    srv->RetryWithoutSsl = Ctl.SslErr->Checked;
    return srv;
}

// Disables a button for the lifetime of the guard.
class TButtonBusy
{
public:
    explicit TButtonBusy(TButton *Button) : FButton(Button) { FButton->Enabled = false; }
    ~TButtonBusy() { FButton->Enabled = true; }
    TButtonBusy(const TButtonBusy &) = delete;
    TButtonBusy &operator=(const TButtonBusy &) = delete;
private:
    TButton *FButton;
};
}

__fastcall TDemoForm::TDemoForm(TComponent *Owner)
    : TForm(Owner), FQuUpdateFlag(false)
{
}

__fastcall TDemoForm::~TDemoForm()
{
    TimerUpdates->Enabled = false;
    IcsMailQueue1->LogEvent = nullptr;
    IcsMailQueue1->QuChangedEvent = nullptr;
    IcsMailQueue1->OATokenEvent = nullptr;
    IcsMailQueue1->Active = false;
}

void __fastcall TDemoForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    FIcsBuffLogStream.reset();
}

void __fastcall TDemoForm::FormCloseQuery(TObject *Sender, bool &CanClose)
{
    StopQueueMail();
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(IniFileName, true));
    // Check boxes are saved as the strings "True" and "False".
    auto writeChecked = [&ini](const String &Key, TCheckBox *Box) {
        ini->WriteString(SectionData, Key, Box->Checked ? "True" : "False");
    };
    ini->WriteString(SectionData, "FileAttachment_Text", FileAttachment->Text);
    ini->WriteString(SectionData, "MailBody_Lines", MailBody->Lines->CommaText);
    ini->WriteString(SectionData, "MailCC_Text", MailCC->Text);
    ini->WriteString(SectionData, "MailFrom_Text", MailFrom->Text);
    ini->WriteInteger(SectionData, "MailPriority_ItemIndex", MailPriority->ItemIndex);
    ini->WriteString(SectionData, "MailSubject_Text", MailSubject->Text);
    ini->WriteString(SectionData, "RecipList_Lines", RecipList->Lines->CommaText);
    ini->WriteString(SectionData, "PrefAttemptsList_Text", PrefAttemptsList->Text);
    ini->WriteString(SectionData, "PrefCARootBundle_Text", PrefCARootBundle->Text);
    ini->WriteString(SectionData, "PrefClientCertFile_Text", PrefClientCertFile->Text);
    ini->WriteString(SectionData, "PrefDnsServer_Text", PrefDnsServer->Text);
    ini->WriteString(SectionData, "PrefHeloHost_Text", PrefHeloHost->Text);
    ini->WriteString(SectionData, "PrefMailQuFolder_Text", PrefMailQuFolder->Text);
    ini->WriteInteger(SectionData, "PrefSendMethod_ItemIndex", PrefSendMethod->ItemIndex);
    writeChecked("PrefSslRevoke_Checked", PrefSslRevoke);
    ini->WriteInteger(SectionData, "PrefVerifyCertMode_ItemIndex", PrefVerifyCertMode->ItemIndex);
    ini->WriteString(SectionData, "PrefEmailAuthPass1_Text", PrefEmailAuthPass1->Text);
    ini->WriteString(SectionData, "PrefEmailAuthPass2_Text", PrefEmailAuthPass2->Text);
    ini->WriteInteger(SectionData, "PrefEmailAuthType1_ItemIndex", PrefEmailAuthType1->ItemIndex);
    ini->WriteInteger(SectionData, "PrefEmailAuthType2_ItemIndex", PrefEmailAuthType2->ItemIndex);
    ini->WriteString(SectionData, "PrefEmailAuthUser1_Text", PrefEmailAuthUser1->Text);
    ini->WriteString(SectionData, "PrefEmailAuthUser2_Text", PrefEmailAuthUser2->Text);
    ini->WriteString(SectionData, "PrefEmailPort1_Text", PrefEmailPort1->Text);
    ini->WriteString(SectionData, "PrefEmailPort2_Text", PrefEmailPort2->Text);
    ini->WriteInteger(SectionData, "PrefEmailSecure1_ItemIndex", PrefEmailSecure1->ItemIndex);
    ini->WriteInteger(SectionData, "PrefEmailSecure2_ItemIndex", PrefEmailSecure2->ItemIndex);
    ini->WriteInteger(SectionData, "PrefEmailSecurity1_ItemIndex", PrefEmailSecurity1->ItemIndex);
    ini->WriteInteger(SectionData, "PrefEmailSecurity2_ItemIndex", PrefEmailSecurity2->ItemIndex);
    ini->WriteString(SectionData, "PrefEmailSmtp1_Text", PrefEmailSmtp1->Text);
    ini->WriteString(SectionData, "PrefEmailSmtp2_Text", PrefEmailSmtp2->Text);
    writeChecked("PrefEmailSslErr1_Checked", PrefEmailSslErr1);
    writeChecked("PrefEmailSslErr2_Checked", PrefEmailSslErr2);
    ini->WriteString(SectionData, "DirLogs_Text", DirLogs->Text);
    ini->WriteString(SectionData, "PrefClientId_Text", PrefClientId->Text);
    ini->WriteString(SectionData, "PrefRefrToken_Text", PrefRefrToken->Text);
    ini->WriteInteger(SectionData, "PrefRestType_ItemIndex", PrefRestType->ItemIndex);
    ini->WriteString(SectionData, "PrefClientSecret_Text", PrefClientSecret->Text);
    ini->WriteString(SectionData, "PrefUserAuth_Text", PrefUserAuth->Text);
    writeChecked("PrefExtBrow_Checked", PrefExtBrow);
    ini->WriteString(SectionData, "EmlFileName_Text", EmlFileName->Text);

    ini->WriteInteger(SectionData, "Top", Top);
    ini->WriteInteger(SectionData, "Left", Left);
    ini->WriteInteger(SectionData, "Width", Width);
    ini->WriteInteger(SectionData, "Height", Height);
    ini->UpdateFile();
}

void __fastcall TDemoForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    IniFileName = GetIcsIniFileName();
    PageControl1->ActivePage = TabMessage;
    SendMessage(MailLog->Handle, EM_EXLIMITTEXT, 0, 6000000);   // 6 MB
    GSSL_DLL_DIR = ExtractFilePath(ParamStr(0));                  // OpenSSL from the exe directory first

    PrefEmailSecurity1->Items->Clear();   // SSL client security levels
    for (int level = sslCliSecIgnore; level <= sslCliSecHigh192; ++level)
        PrefEmailSecurity1->Items->Add(SslCliSecurityNames[level]);
    PrefEmailSecurity2->Items->Assign(PrefEmailSecurity1->Items);
    PrefEmailAuthType1->Items->Clear();   // server authentication methods
    PrefEmailAuthType2->Items->Clear();
    for (int auth = smtpAuthNone; auth <= smtpAuthOAuthBearer; ++auth) {
        PrefEmailAuthType1->Items->Add(SmtpAuthTypeNames[auth]);
        PrefEmailAuthType2->Items->Add(SmtpAuthTypeNames[auth]);
    }

    // The saved settings; the defaults are the values from the .dfm.
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(IniFileName, true));
    auto readChecked = [&ini](const String &Key, TCheckBox *Box) {
        Box->Checked = ini->ReadString(SectionData, Key, "False") == "True";
    };
    auto readText = [&ini](const String &Key, TEdit *Edit) {
        Edit->Text = ini->ReadString(SectionData, Key, Edit->Text);
    };
    // A generic lambda: Control is a TComboBox* or a TRadioGroup*, which share no ItemIndex base class.
    auto readIndex = [&ini](const String &Key, auto *Control) {
        Control->ItemIndex = ini->ReadInteger(SectionData, Key, Control->ItemIndex);
    };
    readText("FileAttachment_Text", FileAttachment);
    MailBody->Lines->CommaText = ini->ReadString(SectionData, "MailBody_Lines", "");
    readText("MailCC_Text", MailCC);
    readText("MailFrom_Text", MailFrom);
    readIndex("MailPriority_ItemIndex", MailPriority);
    readText("MailSubject_Text", MailSubject);
    RecipList->Lines->CommaText = ini->ReadString(SectionData, "RecipList_Lines", "");
    readText("PrefAttemptsList_Text", PrefAttemptsList);
    readText("PrefCARootBundle_Text", PrefCARootBundle);
    readText("PrefClientCertFile_Text", PrefClientCertFile);
    readText("PrefDnsServer_Text", PrefDnsServer);
    readText("PrefHeloHost_Text", PrefHeloHost);
    readText("PrefMailQuFolder_Text", PrefMailQuFolder);
    readIndex("PrefSendMethod_ItemIndex", PrefSendMethod);
    readChecked("PrefSslRevoke_Checked", PrefSslRevoke);
    readIndex("PrefVerifyCertMode_ItemIndex", PrefVerifyCertMode);
    readText("PrefEmailAuthPass1_Text", PrefEmailAuthPass1);
    readText("PrefEmailAuthPass2_Text", PrefEmailAuthPass2);
    readIndex("PrefEmailAuthType1_ItemIndex", PrefEmailAuthType1);
    readIndex("PrefEmailAuthType2_ItemIndex", PrefEmailAuthType2);
    readText("PrefEmailAuthUser1_Text", PrefEmailAuthUser1);
    readText("PrefEmailAuthUser2_Text", PrefEmailAuthUser2);
    readText("PrefEmailPort1_Text", PrefEmailPort1);
    readText("PrefEmailPort2_Text", PrefEmailPort2);
    readIndex("PrefEmailSecure1_ItemIndex", PrefEmailSecure1);
    readIndex("PrefEmailSecure2_ItemIndex", PrefEmailSecure2);
    readIndex("PrefEmailSecurity1_ItemIndex", PrefEmailSecurity1);
    readIndex("PrefEmailSecurity2_ItemIndex", PrefEmailSecurity2);
    readText("PrefEmailSmtp1_Text", PrefEmailSmtp1);
    readText("PrefEmailSmtp2_Text", PrefEmailSmtp2);
    readChecked("PrefEmailSslErr1_Checked", PrefEmailSslErr1);
    readChecked("PrefEmailSslErr2_Checked", PrefEmailSslErr2);
    readText("DirLogs_Text", DirLogs);
    readText("PrefClientId_Text", PrefClientId);
    readText("PrefRefrToken_Text", PrefRefrToken);
    readIndex("PrefRestType_ItemIndex", PrefRestType);
    readText("PrefClientSecret_Text", PrefClientSecret);
    PrefUserAuth->Text = ini->ReadString(SectionData, "PrefUserAuth_Text", PrefUserAuth->Text);
    readChecked("PrefExtBrow_Checked", PrefExtBrow);
    readText("EmlFileName_Text", EmlFileName);

    Top = ini->ReadInteger(SectionData, "Top", (Screen->Height - Height) / 2);
    Left = ini->ReadInteger(SectionData, "Left", (Screen->Width - Width) / 2);
    Width = ini->ReadInteger(SectionData, "Width", Width);
    Height = ini->ReadInteger(SectionData, "Height", Height);
    ini.reset();

    if (PrefEmailSecurity1->ItemIndex <= 0)
        PrefEmailSecurity1->ItemIndex = static_cast<int>(sslCliSecDefault);
    if (PrefEmailSecurity2->ItemIndex <= 0)
        PrefEmailSecurity2->ItemIndex = static_cast<int>(sslCliSecDefault);
    if (PrefUserAuth->Text == "")
        PrefUserAuth->Text = OAuthMsUserAuthDef;
}

// Opens (or renames) the daily log file, written as UTF-8.
void __fastcall TDemoForm::OpenLogFile()
{
    if (DirLogs->Text == "")
        return;   // no log
    const String name = "\"" + IncludeTrailingPathDelimiter(DirLogs->Text) + "ics-mailqu-\"yyyy-mm-dd\".log\"";
    if (!FIcsBuffLogStream)
        FIcsBuffLogStream.reset(new TIcsBuffLogStream(nullptr, name, Caption + IcsCRLF, FileCPUtf8));
    else {
        if (name == FIcsBuffLogStream->NameMask)
            return;
        if (FIcsBuffLogStream->LogSize > 0)
            FIcsBuffLogStream->FlushFile(true);   // changing the log path: write the old log first
        FIcsBuffLogStream->NameMask = name;
    }
    MailDiagAdd(String(IcsCRLF) + "Opened log file: " + FIcsBuffLogStream->FullName);
}

void __fastcall TDemoForm::FormResize(TObject *Sender)
{
    MailLog->Left = PageControl1->Width;
    MailLog->Height = PageControl1->Height;
    MailLog->Width = ClientWidth - MailLog->Left;
    MailBody->Top = PageControl1->Height;
    MailBody->Width = ClientWidth;
    MailBody->Height = PanelBottom->Top - MailBody->Top;
}

void __fastcall TDemoForm::SelectAttachmentClick(TObject *Sender)
{
    OpenDialog->InitialDir = ExtractFileDir(FileAttachment->Text);
    if (OpenDialog->Execute())
        FileAttachment->Text = OpenDialog->FileName;
}

void __fastcall TDemoForm::SelectEmlFileClick(TObject *Sender)
{
    OpenDialog->InitialDir = ExtractFileDir(EmlFileName->Text);
    if (OpenDialog->Execute())
        EmlFileName->Text = OpenDialog->FileName;
}

void __fastcall TDemoForm::StartQueueMail()
{
    OpenLogFile();
    if (IcsMailQueue1->Active)
        return;
    if (PrefMailQuFolder->Text == "") {
        MailLogAdd("Must Specify Mail Queue Directory");
        return;
    }
    if (!ForceDirectories(PrefMailQuFolder->Text)) {
        MailLogAdd("Failed to Create Mail Queue Directory: " + PrefMailQuFolder->Text);
        return;
    }
    DiagForm->Visible = true;

    String info;
    try {
        IcsMailQueue1->SslVerMethod = static_cast<TMailVerifyMethod>(PrefVerifyCertMode->ItemIndex);
        IcsMailQueue1->SslRevocation = PrefSslRevoke->Checked;
        IcsMailQueue1->SslReportChain = true;
        IcsMailQueue1->SslRootFile = PrefCARootBundle->Text;
        IcsMailQueue1->LogQuSent = true;      // log of sent email
        IcsMailQueue1->RetryList = PrefAttemptsList->Text;
        IcsMailQueue1->ArchiveSent = true;    // keep copies of sent mail
        IcsMailQueue1->DeleteFailed = false;  // keep failed mail
        IcsMailQueue1->Debug = true;
        IcsMailQueue1->QuStartDelay = 2;
        IcsMailQueue1->MailQuDir = PrefMailQuFolder->Text;
        IcsMailQueue1->DnsServers->Clear();
        IcsMailQueue1->DnsServers->Add(PrefDnsServer->Text);
        IcsMailQueue1->DnsServers->Add("1.1.1.1");   // Cloudflare
        IcsMailQueue1->DnsServers->Add("8.8.4.4");   // Google
        IcsMailQueue1->SmtpMethod = static_cast<TMailSmtpMethod>(PrefSendMethod->ItemIndex);
        IcsMailQueue1->MxSrvUseSsl = PrefEmailSecure1->ItemIndex > 0;
        IcsMailQueue1->QuHtmlSmtp->SignOn = PrefHeloHost->Text;
        if (PrefEmailSmtp1->Text == "" && IcsMailQueue1->SmtpMethod == MailSmtpRelay) {
            MailLogAdd("Must Specify a Mail Server");
            return;
        }

        // Several mail servers; not needed for specific servers or MX lookup.
        IcsMailQueue1->MailServers->Clear();
        bool tokenFlag = false;
        if (PrefEmailSmtp1->Text != "") {
            const TServerControls ctl = { PrefEmailSmtp1, PrefEmailPort1, PrefEmailAuthType1, PrefEmailAuthUser1,
                PrefEmailAuthPass1, PrefEmailSecure1, PrefEmailSecurity1, PrefEmailSslErr1 };
            TMailServer *srv = AddMailServer(IcsMailQueue1, ctl, PrefHeloHost->Text);
            if (IsOAuth2(srv->AuthType)) {
                tokenFlag = true;
                IcsRestEmail1->AccountHint = PrefEmailAuthUser1->Text;
            }
            // As in Delphi, the client certificate is used for the first server only.
            if (PrefClientCertFile->Text != "") {
                if (FileExists(PrefClientCertFile->Text))
                    srv->SslCliCert->LoadFromFile(PrefClientCertFile->Text);
                else
                    MailLogAdd("SSL client certificate ignored, not found: " + PrefClientCertFile->Text);
            }
        }
        if (PrefEmailSmtp2->Text != "") {
            const TServerControls ctl = { PrefEmailSmtp2, PrefEmailPort2, PrefEmailAuthType2, PrefEmailAuthUser2,
                PrefEmailAuthPass2, PrefEmailSecure2, PrefEmailSecurity2, PrefEmailSslErr2 };
            TMailServer *srv = AddMailServer(IcsMailQueue1, ctl, PrefHeloHost->Text);
            if (IsOAuth2(srv->AuthType)) {
                tokenFlag = true;
                IcsRestEmail1->AccountHint = PrefEmailAuthUser2->Text;
            }
        }

        // OAuth2 settings, needed for Gmail and Outlook/Live mail (different SMTP servers may need different ones).
        if (PrefClientId->Text != "" && PrefClientSecret->Text != "") {
            if (PrefRestType->ItemIndex == 0)
                IcsRestEmail1->RestEmailType = RestEmailGoogle;
            else if (PrefRestType->ItemIndex == 1)
                IcsRestEmail1->RestEmailType = RestEmailMSSmtp;
            else {
                // Delphi stops here without a message (the OAuth2 account type has no default).
                MailLogAdd("Must Select the OAuth2 Account Type");
                return;
            }
            IcsRestEmail1->ClientId = PrefClientId->Text;
            IcsRestEmail1->ClientSecret = PrefClientSecret->Text;
            IcsRestEmail1->RefrToken = PrefRefrToken->Text;
            IcsRestEmail1->MsUserAuth = PrefUserAuth->Text;
            IcsRestEmail1->OAAuthType = PrefExtBrow->Checked ? OAuthTypeWeb : OAuthTypeEmbed;

            // Not essential, the queue gets a token when it needs one, but it is easier at startup.
            if (tokenFlag && !IcsMailQueue1->CheckOAuthLogins())
                MailLogAdd("Failed to get OAuth2 Bearer Token");
        }
        else if (tokenFlag) {
            MailLogAdd("Can Not Use OAuth2 Authentication without Client Id and Secret");
            return;
        }

        IcsMailQueue1->Active = true;
        if (IcsMailQueue1->Active) {
            info = "Started Mail Queue OK";
            doQueue->Caption = "Stop Queue";
        }
    }
    catch (Exception &E) {
        info = "Failed to Start Mail Queue - " + IcsGetExceptMess(&E);
    }
    MailLogAdd(info);
    LabelCount->Caption = info;
}

void __fastcall TDemoForm::StopQueueMail()
{
    doQueue->Caption = "Start Queue";
    if (IcsMailQueue1->Active)
        IcsMailQueue1->Active = false;
    const String info = IcsMailQueue1->Active ? "Failed to Stop Mail Queue" : "";
    MailLogAdd(info);
    LabelCount->Caption = info;
}

// Shared start of the three queue buttons: restart the queue if its folder changed, start it if
// stopped, and copy the send settings. Returns false when the queue is not running.
bool __fastcall TDemoForm::PrepareToQueue()
{
    LabelProg->Caption = "";
    DiagForm->Visible = true;
    if (IcsMailQueue1->MailQuDir != PrefMailQuFolder->Text)
        StopQueueMail();
    StartQueueMail();
    if (!IcsMailQueue1->Active)
        return false;
    IcsMailQueue1->SmtpMethod = static_cast<TMailSmtpMethod>(PrefSendMethod->ItemIndex);
    IcsMailQueue1->SslVerMethod = static_cast<TMailVerifyMethod>(PrefVerifyCertMode->ItemIndex);
    IcsMailQueue1->SslRevocation = PrefSslRevoke->Checked;
    return true;
}

// Queues one message per recipient line (lines starting with * and very short lines are skipped).
void __fastcall TDemoForm::doSendClick(TObject *Sender)
{
    if (RecipList->Lines->Count > MaxAddressesAllowed) {
        MailLogAdd("Trying to queue too many emails");
        return;
    }
    TButtonBusy busy(doSend);
    if (!PrepareToQueue())
        return;

    // The message details.
    THtmlSmtpCli *smtp = IcsMailQueue1->QuHtmlSmtp;
    smtp->SignOn = PrefHeloHost->Text;
    smtp->EmailFiles->Clear();
    smtp->RcptName->Clear();
    smtp->Allow8bitChars = false;     // true prevents the encoding of headers
    smtp->ConvertToCharset = true;    // ignored by Unicode compilers, which always convert
    smtp->CharSet = "utf-8";          // so full Unicode is supported
    smtp->FoldHeaders = true;
    smtp->HdrFrom = MailFrom->Text;
    smtp->FromName = ExtractEmail(MailFrom->Text);
    smtp->HdrCc = MailCC->Text;
    smtp->HdrReplyTo = smtp->FromName;
    smtp->HdrSubject = MailSubject->Text;
    smtp->HdrPriority = static_cast<TSmtpPriority>(MailPriority->ItemIndex);
    smtp->ContentType = smtpPlainText;   // or smtpHtml, with the body in HtmlText
    smtp->PlainText->Text = MailBody->Text;
    if (FileAttachment->Text != "")
        smtp->EmailFiles->Add(FileAttachment->Text);

    int doneNr = 0;
    int errNr = 0;
    for (int index = 0; index < RecipList->Lines->Count; ++index) {
        const String recip = RecipList->Lines->Strings[index];
        if (recip.Pos("*") == 1 || recip.Length() <= 2)
            continue;
        ActiveControl = MailLog;
        MailLog->SelStart = 999999999;
        try {
            smtp->RcptName->Clear();
            smtp->RcptName->Add(ExtractEmail(recip));
            if (MailCC->Text != "")
                smtp->RcptName->Add(ExtractEmail(MailCC->Text));
            smtp->HdrTo = recip;
            LabelCount->Caption = "Processing " + IntToStr(index + 1) + " of " + IntToStr(RecipList->Lines->Count) +
                " with " + IntToStr(errNr) + " Errors";
            LabelProg->Caption = "Current Recipient - " + recip;
            Refresh();
            const int item = IcsMailQueue1->SmtpMethod == MailSmtpSpecific
                ? IcsMailQueue1->QueueMail(PrefEmailSmtp1->Text, PrefEmailSmtp2->Text, "")   // up to three specific servers
                : IcsMailQueue1->QueueMail();
            // Delphi counts a failure as queued unless the error message starts with a number, and
            // never as an error.
            if (item == 0) {
                MailLogAdd("Failed to Queue Mail - " + smtp->ErrorMessage);
                System::Sysutils::Beep();
                ++errNr;
            }
            else {
                MailLogAdd("Mail Queued OK as Item " + IntToStr(item));
                ++doneNr;
            }
        }
        catch (Exception &E) {
            MailLogAdd(IcsGetExceptMess(&E));
            System::Sysutils::Beep();
            ++errNr;
        }
        Application->ProcessMessages();
    }
    const String info = "Mail Queued - " + IntToStr(doneNr) + " Items With " + IntToStr(errNr) + " Errors";
    MailLogAdd(info + IcsCRLF);
    LabelCount->Caption = info;
    LabelProg->Caption = "Finished";
    TimerUpdatesTimer(this);
    doShowQuClick(this);
}

// Moves the buffered log lines to the windows; a timer keeps mass mailing fast.
void __fastcall TDemoForm::TimerUpdatesTimer(TObject *Sender)
{
    if (!FBuffDiags.IsEmpty()) {
        if (DiagWinFlag) {
            try {
                FBuffDiags.SetLength(FBuffDiags.Length() - 2);   // remove the last CRLF
                DiagForm->TraceBox->Lines->Add(FBuffDiags);
                SendMessage(DiagForm->TraceBox->Handle, WM_VSCROLL, SB_BOTTOM, 0);
            }
            catch (Exception &) {
                LabelProg->Caption = "Error writing to diag log";
            }
        }
        FBuffDiags = "";
    }
    if (!FBuffInfos.IsEmpty()) {
        try {
            FBuffInfos.SetLength(FBuffInfos.Length() - 2);   // remove the last CRLF
            MailLog->Lines->Add(FBuffInfos);
            SendMessage(MailLog->Handle, WM_VSCROLL, SB_BOTTOM, 0);
        }
        catch (Exception &) {
            LabelProg->Caption = "Error writing to log";
        }
        FBuffInfos = "";
    }
    if (FQuUpdateFlag) {
        FQuUpdateFlag = false;
        LabelQueue->Caption = "Mail Queue: Items Queued " + IntToStr(IcsMailQueue1->MailTotItems);
        if (ViewQuWinFlag)
            ViewQuForm->UpdateList();
    }
}

void __fastcall TDemoForm::MailLogAdd(const String &Info)
{
    FBuffInfos = FBuffInfos + Info + IcsCRLF;
}

void __fastcall TDemoForm::PrefEmailSecure1Change(TObject *Sender)
{
    PrefChange(this);
    PrefEmailPort1->Text = PrefEmailSecure1->ItemIndex == static_cast<int>(smtpTlsImplicit) ? "465" : "25";
}

void __fastcall TDemoForm::PrefEmailSecure2Change(TObject *Sender)
{
    PrefChange(this);
    PrefEmailPort2->Text = PrefEmailSecure2->ItemIndex == static_cast<int>(smtpTlsImplicit) ? "465" : "25";
}

// New settings need a queue restart.
void __fastcall TDemoForm::PrefChange(TObject *Sender)
{
    if (IcsMailQueue1->Active)
        StopQueueMail();
}

void __fastcall TDemoForm::IcsMailQueue1LogEvent(TMailLogLevel LogLevel, const String Info)
{
    if (LogLevel != MLogLevelDiag)
        MailLogAdd(Info);
    MailDiagAdd(Info);
}

// Called by the queue when a server needs an OAuth2 token.
void __fastcall TDemoForm::IcsMailQueue1OATokenEvent(int ServNr, String &Token, String &TokAccount, TDateTime &TokExpireDT)
{
    MailLogAdd("Starting to get OAuth2 Bearer Token");
    // Interactive: waits for the browser window to complete.
    if (!IcsRestEmail1->GetNewToken(true))
        MailLogAdd("Failed to get OAuth2 Bearer Token");
    else {
        Token = IcsRestEmail1->AccToken;
        TokExpireDT = IcsRestEmail1->AccExpireDT;
        TokAccount = IcsRestEmail1->NewAccEmail;
    }
}

void __fastcall TDemoForm::IcsMailQueue1QuChangedEvent(TObject *Sender)
{
    FQuUpdateFlag = true;
}

// The refresh token arrives only after a login window, so it is saved.
void __fastcall TDemoForm::IcsRestEmail1EmailNewToken(TObject *Sender)
{
    if (IcsRestEmail1->RefrToken != "")
        PrefRefrToken->Text = IcsRestEmail1->RefrToken;
    PrefTokenAccnt->Text = IcsRestEmail1->NewAccEmail;
}

void __fastcall TDemoForm::IcsRestEmail1EmailProg(TObject *Sender, TLogOption LogOption, const String Msg)
{
    MailDiagAdd(Msg);
}

void __fastcall TDemoForm::IcsRestEmail1OAEmbed(TObject *Sender, const String URL, bool &Success)
{
    Success = OAuthBrowser1->BrowserEmbedded(static_cast<TRestOAuth*>(Sender), this, URL);
}

void __fastcall TDemoForm::MailDiagAdd(const String &Info)
{
    if (DiagWinFlag)
        FBuffDiags = FBuffDiags + Info + IcsCRLF;

    if (DirLogs->Text == "" || !FIcsBuffLogStream)
        return;
    try {
        FIcsBuffLogStream->WriteLine(Info);
    }
    catch (Exception &) {
        // Delphi ignores a failed log-file write silently.
        LabelProg->Caption = "Error writing to log file";
    }
}

void __fastcall TDemoForm::doExitClick(TObject *Sender)
{
    DiagForm->Close();
    Close();
}

void __fastcall TDemoForm::doQueueClick(TObject *Sender)
{
    if (IcsMailQueue1->Active)
        StopQueueMail();
    else
        StartQueueMail();
}

void __fastcall TDemoForm::doShowQuClick(TObject *Sender)
{
    ViewQuForm->Show();
    ViewQuForm->UpdateList();
}

void __fastcall TDemoForm::doClearClick(TObject *Sender)
{
    RecipList->Lines->Clear();
    MailSubject->Text = "";
    MailBody->Lines->Clear();
    MailFrom->Text = "";
    MailCC->Text = "";
}

void __fastcall TDemoForm::doCheckEmlClick(TObject *Sender)
{
    TMailQuItem item;
    if (IcsMailQueue1->CheckEML(EmlFileName->Text, item)) {
        MailLogAdd("Mail From: " + item.XSender);
        MailLogAdd("Mail To: " + item.XReceivers);
        MailLogAdd("Mail Subject: " + item.Subject);
    }
}

// Queues the EML file to the recipients in its own header.
void __fastcall TDemoForm::doSendEmlHdrClick(TObject *Sender)
{
    if (RecipList->Lines->Count > MaxAddressesAllowed) {
        MailLogAdd("Trying to queue too many emails");
        return;
    }
    TButtonBusy busy(doSendEmlHdr);
    if (!PrepareToQueue())
        return;
    ActiveControl = MailLog;
    MailLog->SelStart = 999999999;
    try {
        LabelCount->Caption = "Processing 1 of 1 with 0 Errors";
        Refresh();
        const int item = IcsMailQueue1->SmtpMethod == MailSmtpSpecific
            ? IcsMailQueue1->QueueEML(EmlFileName->Text, "", PrefEmailSmtp1->Text, PrefEmailSmtp2->Text, "")
            : IcsMailQueue1->QueueEML(EmlFileName->Text, "");
        if (item == 0)
            MailLogAdd("Failed to Queue Mail - " + IcsMailQueue1->QuHtmlSmtp->ErrorMessage);
        else
            MailLogAdd("Mail Queued OK as Item " + IntToStr(item));
    }
    catch (Exception &E) {
        MailLogAdd(IcsGetExceptMess(&E));
        System::Sysutils::Beep();
    }
    LabelProg->Caption = "Finished";
    TimerUpdatesTimer(this);
    doShowQuClick(this);
}

// Queues the EML file once per recipient line, ignoring the recipients in its header.
void __fastcall TDemoForm::doSendEmlRecipClick(TObject *Sender)
{
    if (RecipList->Lines->Count > MaxAddressesAllowed) {
        MailLogAdd("Trying to queue too many emails");
        return;
    }
    TButtonBusy busy(doSendEmlRecip);
    if (!PrepareToQueue())
        return;

    int doneNr = 0;
    int errNr = 0;
    for (int index = 0; index < RecipList->Lines->Count; ++index) {
        const String recip = RecipList->Lines->Strings[index];
        if (recip.Pos("*") == 1 || recip.Length() <= 2)
            continue;
        ActiveControl = MailLog;
        MailLog->SelStart = 999999999;
        try {
            LabelCount->Caption = "Processing " + IntToStr(index + 1) + " of " + IntToStr(RecipList->Lines->Count) +
                " with " + IntToStr(errNr) + " Errors";
            LabelProg->Caption = "Current Recipient - " + recip;
            Refresh();
            const int item = IcsMailQueue1->SmtpMethod == MailSmtpSpecific
                ? IcsMailQueue1->QueueEML(EmlFileName->Text, ExtractEmail(recip), PrefEmailSmtp1->Text, PrefEmailSmtp2->Text, "")
                : IcsMailQueue1->QueueEML(EmlFileName->Text, ExtractEmail(recip));
            // Delphi counts a failure as queued unless the error message starts with a number.
            if (item == 0) {
                MailLogAdd("Failed to Queue Mail - " + IcsMailQueue1->QuHtmlSmtp->ErrorMessage);
                System::Sysutils::Beep();
                ++errNr;
            }
            else {
                MailLogAdd("Mail Queued OK as Item " + IntToStr(item));
                ++doneNr;
            }
        }
        catch (Exception &E) {
            MailLogAdd(IcsGetExceptMess(&E));
            System::Sysutils::Beep();
            ++errNr;
        }
        Application->ProcessMessages();
    }
    const String info = "Mail Queued - " + IntToStr(doneNr) + " Items With " + IntToStr(errNr) + " Errors";
    MailLogAdd(info + IcsCRLF);
    LabelCount->Caption = info;
    LabelProg->Caption = "Finished";
    TimerUpdatesTimer(this);
    doShowQuClick(this);
}
