/*
  ICS SSL News Reader demo - C++Builder translation of the Delphi demo OverbyteIcsSslNewsRdr1.pas
  (demos-delphi-vcl), ICS V9.8.

  Shows how to use TSslNntpCli (NNTP over TLS, port 563 by default) to write a news reader:
  connect, select a group, read articles, headers and bodies by number or ID, list groups,
  post a hard coded article, and the XOVER, XHDR, DATE, MODE READER and AUTHINFO commands.
  Commands that return text write it to the file named in the File box, when there is one.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The data stream is a std::unique_ptr member; the registry and ini objects too.
  - A new command never frees the stream of a command still running (see GetStream).
  - List and List MOTD are no longer reported as "Unknown request type".
*/

#include <vcl.h>
#pragma hdrstop

#include <System.DateUtils.hpp>
#include <System.Win.Registry.hpp>

#include "OverbyteIcsSslNewsRdr1.h"
#include "OverbyteIcsIniFiles.hpp"

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

TNNTPForm *NNTPForm;

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 KeyServer      = "Server";
const String KeyPort        = "Port";
const String KeyGroup       = "Group";
const String KeyArticleNum  = "ArticleNum";
const String KeyArticleID   = "ArticleID";
const String KeyFile        = "File";
const String KeyUser        = "User";
const String KeyUserName    = "UserName";
const String KeyPassword    = "Password";

} // namespace

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

__fastcall TNNTPForm::~TNNTPForm()
{
    // Free the components (and so the NNTP client) while FDataStream still exists.
    DestroyComponents();
}

void __fastcall TNNTPForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    FIniFileName = GetIcsIniFileName();
}

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

    String eMail = "your.name@yourcompany.domain";
    String userName = "Your Name";

    {
        // User name and email from the old Internet Mail and News settings, when present.
        std::unique_ptr<TRegistry> reg(new TRegistry);
        reg->RootKey = HKEY_CURRENT_USER;
        if (reg->OpenKey("\\Software\\Microsoft\\Internet Mail and News\\Mail", false)) {
            eMail = reg->ReadString("Sender EMail");
            userName = reg->ReadString("Sender Name");
        }
        reg->CloseKey();
    }

    {
        // PreserveAnsi must be passed in C++ (the Pascal default TRUE is not in the .hpp)
        std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
        Top = ini->ReadInteger(SectionWindow, KeyTop, Top);
        Left = ini->ReadInteger(SectionWindow, KeyLeft, Left);
        Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
        Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
        ServerEdit->Text = ini->ReadString(SectionData, KeyServer, "forums.embarcadero.com");
        PortEdit->Text = ini->ReadString(SectionData, KeyPort, "563");
        ArticleNumEdit->Text = ini->ReadString(SectionData, KeyArticleNum, "");
        ArticleIDEdit->Text = ini->ReadString(SectionData, KeyArticleID, "");
        FileEdit->Text = ini->ReadString(SectionData, KeyFile, "nntprdr.txt");
        UserNameEdit->Text = ini->ReadString(SectionData, KeyUserName, "");
        PasswordEdit->Text = ini->ReadString(SectionData, KeyPassword, "");
        UserEdit->Text = ini->ReadString(SectionData, KeyUser, "\"" + userName + "\" <" + eMail + ">");
        GroupEdit->Text = ini->ReadString(SectionData, KeyGroup,
                                          "embarcadero.public.delphi.thirdpartytools.general");
    }
    DisplayMemo->Clear();
}

void __fastcall TNNTPForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    ini->WriteString(SectionData, KeyServer, ServerEdit->Text);
    ini->WriteString(SectionData, KeyPort, PortEdit->Text);
    ini->WriteString(SectionData, KeyGroup, GroupEdit->Text);
    ini->WriteString(SectionData, KeyArticleNum, ArticleNumEdit->Text);
    ini->WriteString(SectionData, KeyArticleID, ArticleIDEdit->Text);
    ini->WriteString(SectionData, KeyFile, FileEdit->Text);
    ini->WriteString(SectionData, KeyUser, UserEdit->Text);
    ini->WriteString(SectionData, KeyUserName, UserNameEdit->Text);
    ini->WriteString(SectionData, KeyPassword, PasswordEdit->Text);
    ini->WriteInteger(SectionWindow, KeyTop, Top);
    ini->WriteInteger(SectionWindow, KeyLeft, Left);
    ini->WriteInteger(SectionWindow, KeyWidth, Width);
    ini->WriteInteger(SectionWindow, KeyHeight, Height);
    ini->UpdateFile();
}

void TNNTPForm::Display(const String &Msg)
{
    // Limit the memo to 100 lines
    while (DisplayMemo->Lines->Count > 100)
        DisplayMemo->Lines->Delete(1);
    DisplayMemo->Lines->Add(Msg);
}

void __fastcall TNNTPForm::NntpCli1SessionConnected(TObject *Sender, System::Word ErrCode)
{
    AbortButton->Enabled = true;
    Display("Connected, StatusCode = " + IntToStr(NntpCli1->StatusCode));
    if (NntpCli1->PostingPermited)
        Display("Posting permited");
    else
        Display("Posting not permited");
    Display(String(NntpCli1->LastResponse));
}

void __fastcall TNNTPForm::NntpCli1SessionClosed(TObject *Sender, System::Word ErrCode)
{
    AbortButton->Enabled = false;
    ConnectButton->Enabled = true;
    Display("Connection closed");
}

// Called for each NNTP command when it has been executed (correctly or not).
void __fastcall TNNTPForm::NntpCli1RequestDone(TObject *Sender, TNntpRequest RqType, System::Word ErrCode)
{
    Display("Request done. LastResponse = " + String(NntpCli1->LastResponse));
    if (ErrCode == 0)
        Display("No error");
    else
        Display("Error #" + IntToStr(ErrCode));

    switch (RqType) {
    case nntpConnect:
        if (ErrCode != 0) {
            AbortButton->Enabled = false;
            ConnectButton->Enabled = true;
            Display("Connect failed");
        }
        break;
    case nntpGroup:
        Display("ArticleEstimated = " + IntToStr(NntpCli1->ArticleEstimated));
        Display("ArticleFirst     = " + IntToStr(NntpCli1->ArticleFirst));
        Display("ArticleLast      = " + IntToStr(NntpCli1->ArticleLast));
        ArticleNumEdit->Text = IntToStr(NntpCli1->ArticleFirst);
        break;
    // The Delphi demo leaves out nntpList and nntpListMotd, which it also sends,
    // so they are reported as "Unknown request type."
    case nntpList:
    case nntpListMotd:
    case nntpPost:
    case nntpQuit:
    case nntpAbort:
    case nntpHelp:
    case nntpNewGroups:
    case nntpNewNews:
    case nntpXOver:
    case nntpListOverViewFmt:
    case nntpAuthenticate:
    case nntpModeReader:
    case nntpXHdr:
        break;   // nothing to do
    case nntpDate:
        Display("Server Date is " + DateTimeToStr(NntpCli1->ServerDate));
        break;
    case nntpStatByNumber:
    case nntpStatByID:
    case nntpHeadByNumber:
    case nntpHeadByID:
    case nntpBodyByNumber:
    case nntpBodyByID:
    case nntpArticleByNumber:
    case nntpArticleByID:
    case nntpNext:
    case nntpLast:
        Display("ArticleNumber    = " + IntToStr(NntpCli1->ArticleNumber));
        Display("ArticleID        = <" + String(NntpCli1->ArticleID) + ">");
        if (ErrCode == 0) {
            ArticleNumEdit->Text = IntToStr(NntpCli1->ArticleNumber);
            ArticleIDEdit->Text = String(NntpCli1->ArticleID);
        }
        break;
    default:
        Display("Unknown request type.");
        break;
    }

    // The request is finished: free its stream, if any. RequestDone arrives as a posted
    // message, so if a new command has already started, the stream is that command's: keep it.
    if (NntpCli1->State != nntpWaitingResponse)
        FDataStream.reset();
}

// Called when data arrives that the component does not expect. Should not normally occur.
void __fastcall TNNTPForm::NntpCli1DataAvailable(TObject *Sender, System::Word ErrCode)
{
    Display("Data: " + String(NntpCli1->LastResponse));
}

// Called just before a message is received: a place to open a file or start a progress bar.
void __fastcall TNNTPForm::NntpCli1MessageBegin(TObject *Sender)
{
    Display("Message begin");
}

// Called for each line of an incoming message, header and body: a place to write to a file,
// update the screen or a progress bar, or intercept header lines.
void __fastcall TNNTPForm::NntpCli1MessageLine(TObject *Sender)
{
    Display("Line: " + String(NntpCli1->LastResponse));
    AnsiString newsGroupName;
    int lastArticle = 0;
    int firstArticle = 0;
    char postingFlag = '\0';
    ParseListLine(NntpCli1->LastResponse, newsGroupName, lastArticle, firstArticle, postingFlag);
    // This is the place to do something with newsGroupName, lastArticle, firstArticle and postingFlag
}

// Called when a message has been received completely: a place to close a file, remove the
// progress bar and alert the user.
void __fastcall TNNTPForm::NntpCli1MessageEnd(TObject *Sender)
{
    Display("Message End");
}

// Creates a file stream for the name in the File box, or returns nullptr when the box is blank.
// The stream is given to commands that store data (ArticleByNumber and so on) and is freed in
// OnRequestDone.
TStream *TNNTPForm::GetStream()
{
    // While a command runs, its stream must stay: return none, and the command call that
    // follows raises "Not ready". The Delphi demo frees the running command's stream first.
    if (NntpCli1->State != nntpReady)
        return nullptr;

    FDataStream.reset();
    const String fileName = Trim(FileEdit->Text);
    if (fileName.IsEmpty())
        return nullptr;
    try {
        FDataStream.reset(new TFileStream(fileName, fmCreate));
    }
    catch (Exception &E) {
        Display(E.Message);
        throw;   // show the exception box
    }
    return FDataStream.get();
}

void __fastcall TNNTPForm::ConnectButtonClick(TObject *Sender)
{
    DisplayMemo->Clear();
    ConnectButton->Enabled = false;
    NntpCli1->Host = ServerEdit->Text;
    NntpCli1->Port = PortEdit->Text;
    try {
        NntpCli1->Connect();
    }
    catch (Exception &) {
        // When Connect raises, the Delphi demo leaves the button disabled.
        ConnectButton->Enabled = true;
        throw;
    }
}

void __fastcall TNNTPForm::AbortButtonClick(TObject *Sender)
{
    NntpCli1->Abort();
}

void __fastcall TNNTPForm::QuitButtonClick(TObject *Sender)
{
    NntpCli1->Quit();
}

void __fastcall TNNTPForm::GroupButtonClick(TObject *Sender)
{
    NntpCli1->Group(AnsiString(GroupEdit->Text));
}

void __fastcall TNNTPForm::NextButtonClick(TObject *Sender)
{
    NntpCli1->Next();
}

void __fastcall TNNTPForm::LastButtonClick(TObject *Sender)
{
    NntpCli1->Last();
}

void __fastcall TNNTPForm::ArticleByIDButtonClick(TObject *Sender)
{
    NntpCli1->ArticleByID(AnsiString(ArticleIDEdit->Text), GetStream());
}

// The number is read before the stream is created, as in the Delphi argument order.
void __fastcall TNNTPForm::ArticleByNumberButtonClick(TObject *Sender)
{
    const int number = StrToInt(ArticleNumEdit->Text);
    NntpCli1->ArticleByNumber(number, GetStream());
}

void __fastcall TNNTPForm::HeadByIDButtonClick(TObject *Sender)
{
    NntpCli1->HeadByID(AnsiString(ArticleIDEdit->Text), GetStream());
}

void __fastcall TNNTPForm::HeadByNumberButtonClick(TObject *Sender)
{
    const int number = StrToInt(ArticleNumEdit->Text);
    NntpCli1->HeadByNumber(number, GetStream());
}

void __fastcall TNNTPForm::BodyByIDButtonClick(TObject *Sender)
{
    NntpCli1->BodyByID(AnsiString(ArticleIDEdit->Text), GetStream());
}

void __fastcall TNNTPForm::BodyByNumberButtonClick(TObject *Sender)
{
    const int number = StrToInt(ArticleNumEdit->Text);
    NntpCli1->BodyByNumber(number, GetStream());
}

void __fastcall TNNTPForm::StatByIDButtonClick(TObject *Sender)
{
    NntpCli1->StatByID(AnsiString(ArticleIDEdit->Text));
}

void __fastcall TNNTPForm::StatByNumberButtonClick(TObject *Sender)
{
    NntpCli1->StatByNumber(StrToInt(ArticleNumEdit->Text));
}

void __fastcall TNNTPForm::ListButtonClick(TObject *Sender)
{
    if (Application->MessageBox(L"This could take a VERY long time, proceed ? ",
                                L"Warning", MB_YESNO) != IDYES)
        return;
    NntpCli1->List(GetStream());
}

void __fastcall TNNTPForm::NewGroupsButtonClick(TObject *Sender)
{
    NntpCli1->NewGroups(IncDay(Now(), -10), false, "", GetStream());
}

void __fastcall TNNTPForm::NewNewsButtonClick(TObject *Sender)
{
    NntpCli1->NewNews(IncDay(Now(), -1), false, AnsiString(GroupEdit->Text), "", GetStream());
}

void __fastcall TNNTPForm::HelpButtonClick(TObject *Sender)
{
    NntpCli1->Help(GetStream());
}

void TNNTPForm::LineToStream(TStream &Stream, const AnsiString &Buf)
{
    Display("Line: " + String(Buf));
    const AnsiString line = Buf + "\r\n";
    Stream.WriteBuffer(line.c_str(), static_cast<NativeInt>(line.Length()));
}

// Posting needs the whole message, header included. It is built on the fly in a memory
// stream; normally it would come from a file written by some user interface.
void __fastcall TNNTPForm::PostButtonClick(TObject *Sender)
{
    // Built in a local stream and kept only once Post has accepted it, so a refused Post
    // leaves the running command's stream alone (the Delphi demo frees it first).
    std::unique_ptr<TMemoryStream> message(new TMemoryStream);

    // Message header, ended by a blank line
    LineToStream(*message, AnsiString("From: " + UserEdit->Text));
    LineToStream(*message, AnsiString("Newsgroups: " + GroupEdit->Text));
    LineToStream(*message, "Subject: Internet Components Suite (ICS)");
    LineToStream(*message, "Organization: None");
    LineToStream(*message, "X-Newsreader: NNTP component (http://www.overbyte.be)");
    LineToStream(*message, "");

    // Message body
    const char *const body[] = {
        "",
        "The Internet Component Suite is a set of native",
        "components for Borland Delphi (all versions,",
        "including 16 bits) and Borland C++ Builder. The",
        "major TCP/IP protocols are supported for building",
        "client/server, intranet or Internet applications.",
        "",
        "TCP, UDP, TELNET, FTP, SMTP, POP3, PING, FINGER, HTTP,",
        "NNTP and more. Each component has samples writen",
        "in Delphi and in C++ Builder. Several client/server",
        "applications, including an event-driven and a",
        "multi-threaded server, a complete FTP client and",
        "TELNET client with ansi emulation are provided.",
        "Full source code provided for everything.",
        "",
        "The Internet Component Suite is freeware, royalty",
        "free and support is done using a mailing list.",
        "Visit our website and download now from",
        "http://www.overbyte.be",
    };
    for (const char *line : body)
        LineToStream(*message, line);

    // TNntpCli posts from the current position.
    message->Seek(static_cast<__int64>(0), soBeginning);

    // Posting runs in the background; OnRequestDone fires when it is done and frees the stream.
    NntpCli1->Post(message.get());
    FDataStream = std::move(message);
}

void __fastcall TNNTPForm::XOverButtonClick(TObject *Sender)
{
    NntpCli1->XOver(AnsiString(ArticleNumEdit->Text), GetStream());
}

void __fastcall TNNTPForm::OverViewFmtButtonClick(TObject *Sender)
{
    NntpCli1->ListOverViewFmt(GetStream());
}

void __fastcall TNNTPForm::DateButtonClick(TObject *Sender)
{
    NntpCli1->Date();
}

void __fastcall TNNTPForm::AuthenticateButtonClick(TObject *Sender)
{
    NntpCli1->UserName = UserNameEdit->Text;
    NntpCli1->PassWord = PasswordEdit->Text;
    NntpCli1->Authenticate();
}

void __fastcall TNNTPForm::ModeReaderButtonClick(TObject *Sender)
{
    NntpCli1->ModeReader();
}

void __fastcall TNNTPForm::XHdrButtonClick(TObject *Sender)
{
    TStream *stream = GetStream();
    NntpCli1->XHdr(stream, "subject", AnsiString(ArticleNumEdit->Text));
}

void __fastcall TNNTPForm::NntpCli1XHdrBegin(TObject *Sender)
{
    Display("XHdr begin");
}

void __fastcall TNNTPForm::NntpCli1XHdrEnd(TObject *Sender)
{
    Display("Xhdr End");
}

void __fastcall TNNTPForm::NntpCli1XHdrLine(TObject *Sender)
{
    Display("XHdr: " + String(NntpCli1->LastResponse));
}

void __fastcall TNNTPForm::ListMotdButtonClick(TObject *Sender)
{
    NntpCli1->ListMotd(GetStream());
}
