/*
  ICS SSL SMTP client demo - C++Builder translation of the Delphi demo OverbyteIcsSslMailSnd1.pas
  (demos-delphi-vcl), ICS V9.8.

  Shows how to use TSslSmtpCli: one button per SMTP command (Connect, Helo, Ehlo, StartTls, Auth,
  MailFrom, RcptTo, Data, Quit), the combined Open and Mail methods, and an "All In One" button that
  chains the commands through OnRequestDone. TLS can be implicit (port 465) or explicit (STARTTLS).
  The message body comes either from OnGetData, one memo line at a time, or from the MailMessage
  property, which adds encoding and line wrapping. XOAuth2 and OAuthBearer logins get their token from
  TIcsRestEmail; hardcode your Google or Microsoft client application account in SetupOAuth2.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The message settings and the SSL settings, repeated in several Delphi button handlers, are each
    one helper (ApplyMessageSettings, ApplySslSettings).
  - The empty OnSslVerifyPeer and OnSslHandshakeDone handlers (their bodies were commented out in
    V9.6, the work is now done in TWSocket) are not translated and so not assigned.
  - WM_SSL_RECONNECT and its handler are not translated: nothing in the demo posts that message.
  - The ConvertToCharset check box is always hidden: Delphi hides it for every Unicode compiler.
*/

#include <vcl.h>
#pragma hdrstop

#include <algorithm>
#include <cstring>
#include <memory>
#include <System.SysUtils.hpp>

#include "OverbyteIcsSslMailSnd1.h"
#include "OverbyteIcsIniFiles.hpp"

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

TSslSmtpTestForm *SslSmtpTestForm;

namespace {
const wchar_t *const SectionData = L"Data";
const wchar_t *const KeyHost = L"HostName";
const wchar_t *const KeyPort = L"Port";
const wchar_t *const KeyFrom = L"From";
const wchar_t *const KeyTo = L"To";
const wchar_t *const KeyCc = L"Cc";
const wchar_t *const KeyBcc = L"Bcc";
const wchar_t *const KeySubject = L"Subject";
const wchar_t *const KeySignOn = L"SignOn";
const wchar_t *const KeyUser = L"UserName";
const wchar_t *const KeyPass = L"Password";
const wchar_t *const KeyAuth = L"Authentification";
const wchar_t *const KeyPriority = L"Priority";
const wchar_t *const SectionWindow = L"Window";
const wchar_t *const KeyTop = L"Top";
const wchar_t *const KeyLeft = L"Left";
const wchar_t *const KeyWidth = L"Width";
const wchar_t *const KeyHeight = L"Height";
const wchar_t *const SectionFileAttach = L"Files";
const wchar_t *const KeyFileAttach = L"File";
const wchar_t *const SectionMsgMemo = L"Message";
const wchar_t *const KeyMsgMemo = L"Msg";
const wchar_t *const KeySmtpSslMode = L"SmtpSslMode";
const wchar_t *const KeySslVerifyPeer = L"SslVerifyPeer";
const wchar_t *const KeySslKey = L"SslKey";
const wchar_t *const KeySslCert = L"SslCert";
const wchar_t *const KeySslCAFile = L"SslCaFile";
const wchar_t *const KeySslCAPath = L"SslCaPath";
const wchar_t *const KeySslPassPhrase = L"SslPassPhrase";
const wchar_t *const KeyConvertToCharset = L"ConvertToCharset";
const wchar_t *const KeyUseOnGetData = L"UseOnGetData";
const wchar_t *const KeyAllow8Bit = L"Allow8bit";
const wchar_t *const KeyFoldHeaders = L"FoldHeaders";
const wchar_t *const KeyWrapText = L"WrapText";
const wchar_t *const KeyWrapAt = L"WrapAt";
const wchar_t *const KeyCharSet = L"Charset";
const wchar_t *const KeyDefTransEnc = L"DefaultTransferEncoding";
const wchar_t *const KeyOAuth2ExtBrow = L"OAuth2ExtBrow";
const wchar_t *const KeyOAuth2UserAuth = L"OAuth2UserAuth";

// Saves the lines as Key0, Key1, ... in their own section; an empty list is saved as KeyEmptyFlag=Empty.
void SaveStringsToIniFile(TCustomIniFile *IniFile, const String &IniSection, const String &IniKey, TStrings *Strings)
{
    if (IniSection == "" || IniKey == "" || !Strings)
        return;
    IniFile->EraseSection(IniSection);
    if (Strings->Count <= 0)
        IniFile->WriteString(IniSection, IniKey + "EmptyFlag", "Empty");
    else
        for (int i = 0; i < Strings->Count; ++i)
            IniFile->WriteString(IniSection, IniKey + IntToStr(i), Strings->Strings[i]);
}

// Loads the lines written by SaveStringsToIniFile; returns false when the section holds none.
bool LoadStringsFromIniFile(TCustomIniFile *IniFile, const String &IniSection, const String &IniKey, TStrings *Strings)
{
    if (IniSection == "" || IniKey == "" || !Strings)
        return true;
    Strings->Clear();
    if (IniFile->ReadString(IniSection, IniKey + "EmptyFlag", "") != "")
        return true;
    IniFile->ReadSectionValues(IniSection, Strings);   // "Key=Value" lines
    const int keyLen = IniKey.Length();
    for (int i = Strings->Count - 1; i >= 0; --i) {
        const String line = Strings->Strings[i];
        // Keep only KeyN=Value entries; keep the value.
        if (line.Length() <= keyLen || CompareText(IniKey, line.SubString(1, keyLen)) != 0 ||
            line[keyLen + 1] < L'0' || line[keyLen + 1] > L'9')
            Strings->Delete(i);
        else
            Strings->Strings[i] = line.SubString(line.Pos("=") + 1, line.Length());
    }
    return Strings->Count != 0;
}

// BeginUpdate/EndUpdate on a TStrings for the lifetime of the guard.
class TLinesUpdate
{
public:
    explicit TLinesUpdate(TStrings *Lines) : FLines(Lines) { FLines->BeginUpdate(); }
    ~TLinesUpdate() { FLines->EndUpdate(); }
    TLinesUpdate(const TLinesUpdate &) = delete;
    TLinesUpdate &operator=(const TLinesUpdate &) = delete;
private:
    TStrings *FLines;
};
}

__fastcall TSslSmtpTestForm::TSslSmtpTestForm(TComponent *Owner)
    : TForm(Owner), FInitialized(false), FAllInOneFlag(false), FEhloCount(0)
{
}

// Shows a line in the display memo, keeping at most 10000 lines, and scrolls to it.
void __fastcall TSslSmtpTestForm::Display(const String &Msg)
{
    {
        TLinesUpdate update(DisplayMemo->Lines);
        while (DisplayMemo->Lines->Count > 10000)
            DisplayMemo->Lines->Delete(0);
        DisplayMemo->Lines->Add(Msg);
    }
    SendMessage(DisplayMemo->Handle, EM_SCROLLCARET, 0, 0);
}

void __fastcall TSslSmtpTestForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    Application->OnException = ExceptionHandler;
    DisplayMemo->Clear();
    FIniFileName = GetIcsIniFileName();
}

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

    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    HostEdit->Text = ini->ReadString(SectionData, KeyHost, "localhost");
    PortEdit->Text = ini->ReadString(SectionData, KeyPort, "465");
    FromEdit->Text = ini->ReadString(SectionData, KeyFrom, "first.last@company.com");
    ToEdit->Text = ini->ReadString(SectionData, KeyTo, "john.doe@acme;tartempion@brol.fr");
    CcEdit->Text = ini->ReadString(SectionData, KeyCc, "");
    BccEdit->Text = ini->ReadString(SectionData, KeyBcc, "francois.piette@swing.be");
    SubjectEdit->Text = ini->ReadString(SectionData, KeySubject, "This is the message subject");
    SignOnEdit->Text = ini->ReadString(SectionData, KeySignOn, "your name");
    UsernameEdit->Text = ini->ReadString(SectionData, KeyUser, "account name");
    PasswordEdit->Text = ini->ReadString(SectionData, KeyPass, "account password");
    AuthComboBox->ItemIndex = ini->ReadInteger(SectionData, KeyAuth, static_cast<int>(smtpAuthLogin));
    SmtpSslModeCombobox->ItemIndex = ini->ReadInteger(SectionData, KeySmtpSslMode, static_cast<int>(smtpTlsImplicit));
    VerifyPeerCheckBox->Checked = ini->ReadBool(SectionData, KeySslVerifyPeer, false);
    OAuth2UserAuth->Text = ini->ReadString(SectionData, KeyOAuth2UserAuth, "");
    OAuth2ExtBrow->Checked = ini->ReadBool(SectionData, KeyOAuth2ExtBrow, false);
    KeyEdit->Text = ini->ReadString(SectionData, KeySslKey, "01key.pem");
    PassPhraseEdit->Text = ini->ReadString(SectionData, KeySslPassPhrase, "password");
    CertEdit->Text = ini->ReadString(SectionData, KeySslCert, "01cert.pem");
    CaFileEdit->Text = ini->ReadString(SectionData, KeySslCAFile, "TrustedCABundle.pem");
    CaPathEdit->Text = ini->ReadString(SectionData, KeySslCAPath, "TrustedCaStore");
    PriorityComboBox->ItemIndex = ini->ReadInteger(SectionData, KeyPriority, 2);

    if (!LoadStringsFromIniFile(ini.get(), SectionFileAttach, KeyFileAttach, FileAttachMemo->Lines))
        FileAttachMemo->Text = "ics_logo.gif\r\nfp_small.gif";
    if (!LoadStringsFromIniFile(ini.get(), SectionMsgMemo, KeyMsgMemo, MsgMemo->Lines))
        MsgMemo->Text =
            "This is the first line\r\n"
            "Then the second one\r\n"
            "The next one is empty\r\n"
            "\r\n"
            "The next one has only a single dot\r\n"
            ".\r\n"
            "Finally the last one\r\n";

    ConvertToCharsetCheckBox->Checked = ini->ReadBool(SectionData, KeyConvertToCharset, false);
    // ConvertToCharset is ignored by Unicode compilers, which always convert from UTF-16.
    ConvertToCharsetCheckBox->Visible = false;
    UseMailMessageCheckBox->Checked = ini->ReadBool(SectionData, KeyUseOnGetData, false);
    Allow8BitCheckBox->Checked = ini->ReadBool(SectionData, KeyAllow8Bit, false);
    FoldHeadersCheckBox->Checked = ini->ReadBool(SectionData, KeyFoldHeaders, true);
    WrapTextCheckBox->Checked = ini->ReadBool(SectionData, KeyWrapText, false);
    WrapAtEdit->Text = ini->ReadString(SectionData, KeyWrapAt, "76");
    IcsCharsetComboBox1->CharSet = ini->ReadString(SectionData, KeyCharSet, SslSmtpClient->CharSet);
    DefEncodingComboBox->ItemIndex = ini->ReadInteger(SectionData, KeyDefTransEnc, 0);
    if (OAuth2UserAuth->Text == "")
        OAuth2UserAuth->Text = OAuthMsUserAuthDef;

    Top = ini->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = ini->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
    Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
    Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
}

void __fastcall TSslSmtpTestForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    ini->WriteString(SectionData, KeyHost, HostEdit->Text);
    ini->WriteString(SectionData, KeyPort, PortEdit->Text);
    ini->WriteString(SectionData, KeyFrom, FromEdit->Text);
    ini->WriteString(SectionData, KeyTo, ToEdit->Text);
    ini->WriteString(SectionData, KeyCc, CcEdit->Text);
    ini->WriteString(SectionData, KeyBcc, BccEdit->Text);
    ini->WriteString(SectionData, KeySubject, SubjectEdit->Text);
    ini->WriteString(SectionData, KeySignOn, SignOnEdit->Text);
    ini->WriteString(SectionData, KeyUser, UsernameEdit->Text);
    ini->WriteString(SectionData, KeyPass, PasswordEdit->Text);
    ini->WriteInteger(SectionData, KeyAuth, AuthComboBox->ItemIndex);
    ini->WriteInteger(SectionData, KeySmtpSslMode, SmtpSslModeCombobox->ItemIndex);
    ini->WriteBool(SectionData, KeySslVerifyPeer, VerifyPeerCheckBox->Checked);
    ini->WriteString(SectionData, KeyOAuth2UserAuth, OAuth2UserAuth->Text);
    ini->WriteBool(SectionData, KeyOAuth2ExtBrow, OAuth2ExtBrow->Checked);
    ini->WriteString(SectionData, KeySslKey, KeyEdit->Text);
    ini->WriteString(SectionData, KeySslPassPhrase, PassPhraseEdit->Text);
    ini->WriteString(SectionData, KeySslCert, CertEdit->Text);
    ini->WriteString(SectionData, KeySslCAFile, CaFileEdit->Text);
    ini->WriteString(SectionData, KeySslCAPath, CaPathEdit->Text);
    ini->WriteInteger(SectionData, KeyPriority, PriorityComboBox->ItemIndex);
    SaveStringsToIniFile(ini.get(), SectionFileAttach, KeyFileAttach, FileAttachMemo->Lines);
    SaveStringsToIniFile(ini.get(), SectionMsgMemo, KeyMsgMemo, MsgMemo->Lines);

    ini->WriteBool(SectionData, KeyConvertToCharset, ConvertToCharsetCheckBox->Checked);
    ini->WriteBool(SectionData, KeyUseOnGetData, UseMailMessageCheckBox->Checked);
    ini->WriteBool(SectionData, KeyAllow8Bit, Allow8BitCheckBox->Checked);
    ini->WriteBool(SectionData, KeyFoldHeaders, FoldHeadersCheckBox->Checked);
    ini->WriteBool(SectionData, KeyWrapText, WrapTextCheckBox->Checked);
    ini->WriteString(SectionData, KeyWrapAt, WrapAtEdit->Text);
    ini->WriteString(SectionData, KeyCharSet, IcsCharsetComboBox1->CharSet);
    ini->WriteInteger(SectionData, KeyDefTransEnc, DefEncodingComboBox->ItemIndex);

    ini->WriteInteger(SectionWindow, KeyTop, Top);
    ini->WriteInteger(SectionWindow, KeyLeft, Left);
    ini->WriteInteger(SectionWindow, KeyWidth, Width);
    ini->WriteInteger(SectionWindow, KeyHeight, Height);
    ini->UpdateFile();
}

void __fastcall TSslSmtpTestForm::SmtpClientDisplay(TObject *Sender, String Msg)
{
    Display(Msg);
}

// Supplies the message body one memo line at a time (used when MailMessage is not).
void __fastcall TSslSmtpTestForm::SslSmtpClientGetData(TObject *Sender, int LineNum, void *MsgLine, int MaxLen, bool &More)
{
    if (LineNum > MsgMemo->Lines->Count) {
        More = false;
        return;
    }
    // Copy the line as ANSI into the component's buffer, truncated if too long (it should wrap).
    const AnsiString line = MsgMemo->Lines->Strings[LineNum - 1];
    const int len = std::min(line.Length(), MaxLen - 1);
    char *buffer = static_cast<char*>(MsgLine);
    if (len > 0)
        std::memcpy(buffer, line.c_str(), len);
    buffer[len] = '\0';
}

// Shows how to add a line to the header: append "\r\nComments: ..." to the From: line.
void __fastcall TSslSmtpTestForm::SslSmtpClientHeaderLine(TObject *Sender, void *Msg, int Size)
{
    static const char extra[] = "\r\nComments: This is a test";
    char *line = static_cast<char*>(Msg);
    const size_t len = std::strlen(line);
    // Delphi appends without checking that the line still fits in the Size-byte buffer.
    if (len > 0 && _strnicmp(line, "From:", 5) == 0 && len + sizeof(extra) <= static_cast<size_t>(Size))
        std::strcat(line, extra);
}

void __fastcall TSslSmtpTestForm::ClearDisplayButtonClick(TObject *Sender)
{
    DisplayMemo->Clear();
}

void __fastcall TSslSmtpTestForm::ExceptionHandler(TObject *Sender, Exception *E)
{
    Application->ShowException(E);
}

bool __fastcall TSslSmtpTestForm::IsOAuth2() const
{
    return SslSmtpClient->AuthType == smtpAuthXOAuth2 || SslSmtpClient->AuthType == smtpAuthOAuthBearer;
}

void __fastcall TSslSmtpTestForm::SetupOAuth2()
{
    // Hardcode your Google or Microsoft client application account details here.
    IcsRestEmail1->RestEmailType = RestEmailMSSmtp;
    IcsRestEmail1->ClientId = "";
    IcsRestEmail1->ClientSecret = "";

    if (IcsRestEmail1->ClientId == "") {
        Display("OAuth2 authentication requires an client application account");
        return;
    }
    IcsRestEmail1->Clear();
    if (PasswordEdit->Text.Length() > 32)
        IcsRestEmail1->RefrToken = PasswordEdit->Text;   // ideally the refresh token is saved separately

    IcsRestEmail1->AccountHint = UsernameEdit->Text;
    IcsRestEmail1->MsUserAuth = OAuth2UserAuth->Text;
    IcsRestEmail1->OAAuthType = OAuth2ExtBrow->Checked ? OAuthTypeWeb : OAuthTypeEmbed;

    const String host = SslSmtpClient->Host;
    if (host.Pos("gmail") > 0)
        IcsRestEmail1->RestEmailType = RestEmailGoogle;
    else if (host.Pos("office365") > 0 || host.Pos("outlook") > 0)
        IcsRestEmail1->RestEmailType = RestEmailMSSmtp;
    else
        Display("Can not use OAuth2 Authentication with this SMTP Server");
}

// Message body and character set options shared by Open, Data, Mail and All In One. Assign
// MailMessage and unassign OnGetData for automatic encoding and line wrapping.
void __fastcall TSslSmtpTestForm::ApplyMessageSettings()
{
    if (UseMailMessageCheckBox->Checked) {
        SslSmtpClient->MailMessage = MsgMemo->Lines;
        SslSmtpClient->OnGetData = nullptr;
        SslSmtpClient->WrapMsgMaxLineLen = StrToIntDef(WrapAtEdit->Text, 76);
        SslSmtpClient->WrapMessageText = WrapTextCheckBox->Checked;
    }
    else {
        SslSmtpClient->OnGetData = SslSmtpClientGetData;
        SslSmtpClient->MailMessage->Clear();
    }
    SslSmtpClient->CharSet = IcsCharsetComboBox1->CharSet;                    // generally UTF-8
    SslSmtpClient->ConvertToCharset = ConvertToCharsetCheckBox->Checked;      // Unicode compilers always convert
    SslSmtpClient->DefaultEncoding = static_cast<TSmtpDefaultEncoding>(DefEncodingComboBox->ItemIndex);
    SslSmtpClient->Allow8bitChars = Allow8BitCheckBox->Checked;               // must be off to allow inline encoding
    SslSmtpClient->FoldHeaders = FoldHeadersCheckBox->Checked;
}

// SSL context settings for Connect, Open and All In One; SslType must already be set. Returns false
// (after saying why) when a client certificate or key is named but missing.
bool __fastcall TSslSmtpTestForm::ApplySslSettings()
{
    if (SslSmtpClient->SslType == smtpTlsNone)
        return true;
    if (KeyEdit->Text != "" || CertEdit->Text != "") {
        if (!(FileExists(KeyEdit->Text) && FileExists(CertEdit->Text))) {
            Display("Client SSL certificate and key must exist, if specified and required");
            return false;
        }
        SslContext1->SslPrivKeyFile = KeyEdit->Text;
        SslContext1->SslPassPhrase = PassPhraseEdit->Text;
        SslContext1->SslCertFile = CertEdit->Text;
    }
    SslContext1->UseSharedCAStore = true;
    SslContext1->SslCAFile = CaFileEdit->Text;   // no longer needed since V9.6 (shared CA store)
    SslContext1->SslCAPath = CaPathEdit->Text;
    SslContext1->SslVerifyPeer = VerifyPeerCheckBox->Checked;
    if (SslContext1->SslVerifyPeer)
        SslSmtpClient->SslCertVerMethod = CertVerBundle;
    return true;
}

void __fastcall TSslSmtpTestForm::ConnectButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->Host = HostEdit->Text;
    SslSmtpClient->Port = PortEdit->Text;
    SslSmtpClient->HdrPriority = static_cast<TSmtpPriority>(PriorityComboBox->ItemIndex);
    SslSmtpClient->AuthType = static_cast<TSmtpAuthType>(AuthComboBox->ItemIndex);
    SslSmtpClient->Username = UsernameEdit->Text;
    SslSmtpClient->Password = PasswordEdit->Text;
    if (IsOAuth2())
        SetupOAuth2();
    SslSmtpClient->WSDebugOptions = DefWSDebugClient;   // what TWSocket logs through OnDisplay
    SslSmtpClient->SslType = static_cast<TSmtpSslType>(SmtpSslModeCombobox->ItemIndex);
    if (!ApplySslSettings())
        return;
    SslSmtpClient->Connect();
}

// Sends HELO with our local identification.
void __fastcall TSslSmtpTestForm::HeloButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->SignOn = SignOnEdit->Text;
    SslSmtpClient->Helo();
}

void __fastcall TSslSmtpTestForm::EhloButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->SignOn = SignOnEdit->Text;
    SslSmtpClient->Ehlo();
}

void __fastcall TSslSmtpTestForm::AuthButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->Username = UsernameEdit->Text;
    SslSmtpClient->Password = PasswordEdit->Text;
    SslSmtpClient->AuthType = static_cast<TSmtpAuthType>(AuthComboBox->ItemIndex);
    SslSmtpClient->Auth();
}

// Leaving OAuth2 for another authentication type clears an old token from the password box.
void __fastcall TSslSmtpTestForm::AuthComboBoxChange(TObject *Sender)
{
    const TSmtpAuthType auth = static_cast<TSmtpAuthType>(AuthComboBox->ItemIndex);
    if (auth != smtpAuthXOAuth2 && auth != smtpAuthOAuthBearer && PasswordEdit->Text.Length() > 32)
        PasswordEdit->Text = "";
}

// Open combines Connect and Helo when no authentication is selected, otherwise Connect, Ehlo and Auth.
void __fastcall TSslSmtpTestForm::OpenButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    ApplyMessageSettings();
    SslSmtpClient->Host = HostEdit->Text;
    SslSmtpClient->Port = PortEdit->Text;
    SslSmtpClient->SignOn = SignOnEdit->Text;
    SslSmtpClient->Username = UsernameEdit->Text;
    SslSmtpClient->Password = PasswordEdit->Text;
    SslSmtpClient->AuthType = static_cast<TSmtpAuthType>(AuthComboBox->ItemIndex);
    if (IsOAuth2())
        SetupOAuth2();
    SslSmtpClient->WSDebugOptions = TWSDebugOptions() << wsdlogConnWS << wsdlogSsl << wsdlogHdr << wsdlogCerts << wsdlogErrs;
    SslSmtpClient->HdrPriority = static_cast<TSmtpPriority>(PriorityComboBox->ItemIndex);
    SslSmtpClient->SslType = static_cast<TSmtpSslType>(SmtpSslModeCombobox->ItemIndex);
    if (!ApplySslSettings())
        return;
    SslSmtpClient->Open();
}

// Sends the originator.
void __fastcall TSslSmtpTestForm::MailFromButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->FromName = FromEdit->Text;
    SslSmtpClient->MailFrom();
}

// Sends the recipients (To, Cc and Bcc).
void __fastcall TSslSmtpTestForm::RcptToButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->RcptName->Clear();
    SslSmtpClient->RcptNameAdd(ToEdit->Text, CcEdit->Text, BccEdit->Text);
    SslSmtpClient->RcptTo();
}

// Sends the text and attached files.
void __fastcall TSslSmtpTestForm::DataButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    ApplyMessageSettings();
    SslSmtpClient->RcptName->Clear();
    SslSmtpClient->RcptNameAdd(ToEdit->Text, CcEdit->Text, BccEdit->Text);
    SslSmtpClient->HdrFrom = FromEdit->Text;
    SslSmtpClient->HdrTo = ToEdit->Text;
    SslSmtpClient->HdrCc = CcEdit->Text;
    SslSmtpClient->HdrSubject = SubjectEdit->Text;
    SslSmtpClient->EmailFiles = FileAttachMemo->Lines;
    SslSmtpClient->SendMode = smtpCopyToStream;
    SslSmtpClient->Data();
}

// MailFrom, RcptTo and Data combined.
void __fastcall TSslSmtpTestForm::MailButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    ApplyMessageSettings();
    SslSmtpClient->RcptName->Clear();
    SslSmtpClient->RcptNameAdd(ToEdit->Text, CcEdit->Text, BccEdit->Text);
    SslSmtpClient->HdrFrom = FromEdit->Text;
    SslSmtpClient->HdrTo = ToEdit->Text;
    SslSmtpClient->HdrCc = CcEdit->Text;
    SslSmtpClient->HdrSubject = SubjectEdit->Text;
    SslSmtpClient->SignOn = SignOnEdit->Text;
    SslSmtpClient->FromName = FromEdit->Text;
    SslSmtpClient->EmailFiles = FileAttachMemo->Lines;
    SslSmtpClient->Host = HostEdit->Text;
    SslSmtpClient->Port = PortEdit->Text;
    SslSmtpClient->Mail();
}

void __fastcall TSslSmtpTestForm::QuitButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->Quit();
}

void __fastcall TSslSmtpTestForm::AbortButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->Abort();
}

void __fastcall TSslSmtpTestForm::SslSmtpClientRequestDone(TObject *Sender, TSmtpRequest RqType, System::Word ErrorCode)
{
    // Every operation reports its status.
    if (ErrorCode > 0 && ErrorCode < 10000)
        Display("RequestDone Rq=" + IntToStr(static_cast<int>(RqType)) + " Error=" + SslSmtpClient->ErrorMessage);
    else
        Display("RequestDone Rq=" + IntToStr(static_cast<int>(RqType)) + " Error=" + IntToStr(static_cast<int>(ErrorCode)));

    // In the All In One demo, start the next operation if the previous one worked.
    if (!FAllInOneFlag)
        return;
    if (ErrorCode != 0) {
        FAllInOneFlag = false;
        Display("Error, stoped All-In-One demo");
        return;
    }

    const bool explicitTls = SslSmtpClient->SslType == smtpTlsExplicit;
    const bool noAuth = SslSmtpClient->AuthType == smtpAuthNone;
    switch (RqType) {
    case smtpConnect:
        // Delphi sends HELO whenever there is no authentication, so explicit TLS never ran STARTTLS.
        if (noAuth && !explicitTls)
            SslSmtpClient->Helo();
        else
            SslSmtpClient->Ehlo();
        break;
    case smtpHelo:
        SslSmtpClient->MailFrom();
        break;
    case smtpEhlo:
        if (explicitTls && ++FEhloCount == 1)
            SslSmtpClient->StartTls();
        else if (noAuth)   // reached only with explicit TLS (see smtpConnect)
            SslSmtpClient->MailFrom();
        else
            SslSmtpClient->Auth();
        break;
    case smtpStartTls:
        SslSmtpClient->Ehlo();   // EHLO must be sent again after STARTTLS
        break;
    case smtpAuth:
        SslSmtpClient->MailFrom();
        break;
    case smtpMailFrom:
        SslSmtpClient->RcptTo();
        break;
    case smtpRcptTo:
        SslSmtpClient->Data();
        break;
    case smtpData:
        SslSmtpClient->Quit();
        break;
    case smtpQuit:
        Display("All-In-One done !");
        break;
    default:
        break;
    }
}

void __fastcall TSslSmtpTestForm::AllInOneButtonClick(TObject *Sender)
{
    if (SslSmtpClient->Connected) {
        MessageBeep(MB_OK);
        Display("All-In-One demo started in connected state.");
        Display("Please quit or abort the connection first.");
        return;
    }

    ApplyMessageSettings();
    FEhloCount = 0;
    // Initialise all the SMTP component properties from the form.
    SslSmtpClient->Host = HostEdit->Text;
    SslSmtpClient->Port = PortEdit->Text;
    SslSmtpClient->SignOn = SignOnEdit->Text;
    SslSmtpClient->FromName = FromEdit->Text;
    SslSmtpClient->HdrFrom = FromEdit->Text;
    SslSmtpClient->HdrTo = ToEdit->Text;
    SslSmtpClient->HdrCc = CcEdit->Text;
    SslSmtpClient->HdrSubject = SubjectEdit->Text;
    SslSmtpClient->EmailFiles = FileAttachMemo->Lines;
    SslSmtpClient->AuthType = static_cast<TSmtpAuthType>(AuthComboBox->ItemIndex);
    if (IsOAuth2())
        SetupOAuth2();
    SslSmtpClient->WSDebugOptions = TWSDebugOptions() << wsdlogConnWS << wsdlogSsl << wsdlogHdr << wsdlogCerts << wsdlogErrs;
    SslSmtpClient->SslType = static_cast<TSmtpSslType>(SmtpSslModeCombobox->ItemIndex);
    if (!ApplySslSettings())
        return;
    // Delphi sets the flag before the SSL check above, leaving it set when that check stops the demo.
    FAllInOneFlag = true;

    SslSmtpClient->Username = UsernameEdit->Text;
    SslSmtpClient->Password = PasswordEdit->Text;
    SslSmtpClient->HdrPriority = static_cast<TSmtpPriority>(PriorityComboBox->ItemIndex);
    // The recipient list is built from the To, Cc and Bcc fields.
    SslSmtpClient->RcptName->Clear();
    SslSmtpClient->RcptNameAdd(ToEdit->Text, CcEdit->Text, BccEdit->Text);
    Display("Connecting to SMTP server...");
    // The first operation; the next ones are started from OnRequestDone.
    SslSmtpClient->Connect();
}

void __fastcall TSslSmtpTestForm::SslSmtpClientBeforeFileOpen(TObject *Sender, int Idx, String FileName, TSmtpBeforeOpenFileAction &Action)
{
    Action = smtpBeforeOpenFileNone;
}

void __fastcall TSslSmtpTestForm::TlsButtonClick(TObject *Sender)
{
    FAllInOneFlag = false;
    SslSmtpClient->StartTls();
}

// Called by the SMTP component when it needs an OAuth2 token.
void __fastcall TSslSmtpTestForm::SslSmtpClientGetNewToken(TObject *Sender)
{
    // Interactive: waits for the browser window to complete.
    if (!IcsRestEmail1->GetNewToken(true)) {
        Display("Failed to get OAuth2 Bearer Token");
        return;
    }
    const String account = IcsRestEmail1->NewAccEmail;
    if (SslSmtpClient->Username.Pos(account) == 0 && account != "")
        Display("OAuth2 Token for Wrong Account, Expected: " + SslSmtpClient->Username + ", Got: " + account);
    else {
        SslSmtpClient->OAuthToken = IcsRestEmail1->AccToken;
        SslSmtpClient->TokenExpireDT = IcsRestEmail1->AccExpireDT;
        Display("Got New OAuth2 Bearer Token OK");
    }
}

// Called by TIcsRestEmail when it has a new refresh token that should be saved.
void __fastcall TSslSmtpTestForm::IcsRestEmail1EmailNewToken(TObject *Sender)
{
    if (IcsRestEmail1->RefrToken != "") {
        PasswordEdit->Text = IcsRestEmail1->RefrToken;
        Display("Saved OAuth2 Refresh Token");
    }
}

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

// Called by TIcsRestEmail to show the embedded browser window.
void __fastcall TSslSmtpTestForm::IcsRestEmail1OAEmbed(TObject *Sender, const String URL, bool &Success)
{
    Success = OAuthBrowser1->BrowserEmbedded(static_cast<TRestOAuth*>(Sender), this, URL);
}
