/*
  ICS Socks and HTTP Tunnel Proxy test - C++Builder translation of the Delphi demo
  OverbyteIcsSocksTst1.pas (demos-delphi-vcl), ICS V9.8.

  Connects a TWSocket to a target host through a SOCKS 4, SOCKS 5 or HTTP tunnel proxy, or through
  a single proxy URL, and sends a command once connected. Received data is shown one line at a time.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The Delphi handlers whose bodies are empty or commented out (session closed, SOCKS connected,
    SOCKS error, SOCKS authentication state, HTTP tunnel connected and error) are not converted,
    and their bindings are removed from the .dfm. TWSocket reports these steps through the debug
    display event instead.
  - The receive loop finds a line feed at the first byte too and shows a full buffer, see
    WSocket1DataAvailable.
*/

#include <vcl.h>
#pragma hdrstop

#include <cstring>
#include <memory>

#include "OverbyteIcsSocksTst1.h"
#include "OverbyteIcsIniFiles.hpp"

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

TSocksTestForm *SocksTestForm;

namespace {

const String SectionWindow    = "Windows";
const String KeyTop           = "Top";
const String KeyLeft          = "Left";
const String KeyWidth         = "Width";
const String KeyHeight        = "Height";
const String SectionData      = "Data";
const String KeyTargetHost    = "TargetHost";
const String KeyTargetPort    = "TargetPort";
const String KeySocksServer   = "SocksServer";
const String KeySocksPort     = "SocksPort";
const String KeySocksUsercode = "SocksUsercode";
const String KeySocksPassword = "SocksPassword";
const String KeySocksAuth     = "SocksAuthentification";
const String KeySocks4        = "Socks4";
const String KeySocks5        = "Socks5";
const String KeyHttpTunnel    = "HttpTunnel";
const String KeyProxyUrl      = "ProxyUrl";
const String KeyCommand       = "Command";

} // namespace

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

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

void __fastcall TSocksTestForm::FormShow(TObject *Sender)
{
    if (FInitialized)
        return;
    FInitialized = true;
    DisplayMemo->Clear();

    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
    Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
    Top = ini->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = ini->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
    TargetHostEdit->Text = ini->ReadString(SectionData, KeyTargetHost, "");
    TargetPortEdit->Text = ini->ReadString(SectionData, KeyTargetPort, "");
    SocksServerEdit->Text = ini->ReadString(SectionData, KeySocksServer, "");
    SocksPortEdit->Text = ini->ReadString(SectionData, KeySocksPort, "1080");
    SocksUsercodeEdit->Text = ini->ReadString(SectionData, KeySocksUsercode, "");
    SocksPasswordEdit->Text = ini->ReadString(SectionData, KeySocksPassword, "");
    SocksAuthCheckBox->Checked = ini->ReadInteger(SectionData, KeySocksAuth, 0) != 0;
    Socks4RadioButton->Checked = ini->ReadInteger(SectionData, KeySocks4, 0) != 0;
    Socks5RadioButton->Checked = ini->ReadInteger(SectionData, KeySocks5, 1) != 0;
    HttpTunnelRadioButton->Checked = ini->ReadInteger(SectionData, KeyHttpTunnel, 0) != 0;
    ProxyUrlEdit->Text = ini->ReadString(SectionData, KeyProxyUrl, "");
    CommandEdit->Text = ini->ReadString(SectionData, KeyCommand, "Hello");
}

void __fastcall TSocksTestForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    ini->WriteInteger(SectionWindow, KeyTop, Top);
    ini->WriteInteger(SectionWindow, KeyLeft, Left);
    ini->WriteInteger(SectionWindow, KeyWidth, Width);
    ini->WriteInteger(SectionWindow, KeyHeight, Height);
    ini->WriteString(SectionData, KeyTargetHost, Trim(TargetHostEdit->Text));
    ini->WriteString(SectionData, KeyTargetPort, Trim(TargetPortEdit->Text));
    ini->WriteString(SectionData, KeySocksServer, Trim(SocksServerEdit->Text));
    ini->WriteString(SectionData, KeySocksPort, Trim(SocksPortEdit->Text));
    ini->WriteString(SectionData, KeySocksUsercode, Trim(SocksUsercodeEdit->Text));
    ini->WriteString(SectionData, KeySocksPassword, Trim(SocksPasswordEdit->Text));
    ini->WriteInteger(SectionData, KeySocksAuth, SocksAuthCheckBox->Checked ? 1 : 0);
    ini->WriteInteger(SectionData, KeySocks5, Socks5RadioButton->Checked ? 1 : 0);
    ini->WriteInteger(SectionData, KeySocks4, Socks4RadioButton->Checked ? 1 : 0);
    ini->WriteInteger(SectionData, KeyHttpTunnel, HttpTunnelRadioButton->Checked ? 1 : 0);
    ini->WriteString(SectionData, KeyProxyUrl, Trim(ProxyUrlEdit->Text));
    ini->WriteString(SectionData, KeyCommand, Trim(CommandEdit->Text));
    ini->UpdateFile();
}

void __fastcall TSocksTestForm::WSocket1DebugDisplay(TObject *Sender, const String Msg)
{
    DisplayMemo->Lines->Add(Msg);
}

void __fastcall TSocksTestForm::ConnectButtonClick(TObject *Sender)
{
    WSocket1->SetSocks("");        // clear the socks settings
    WSocket1->SetHTTPTunnel("");   // clear the tunnel settings

    TWSDebugOptions debugOptions = TWSDebugOptions() << wsdlogConnWS << wsdlogSsl << wsdlogErrs << wsdlogDns;
    if (SocksLogging->Checked)
        debugOptions << wsdlogProxy;   // log the SOCKS protocol packets
    WSocket1->WSDebugOptions = debugOptions;

    // a proxy URL sets everything in one go
    if (ProxyUrlEdit->Text != "") {
        WSocket1->ProxyURL = ProxyUrlEdit->Text;
        DisplayMemo->Lines->Add("Connecting using Proxy URL");
    }
    else {
        WSocket1->ProxyURL = "";
        if (Socks5RadioButton->Checked)
            WSocket1->SocksLevel = "5";
        else if (Socks4RadioButton->Checked && SocksAuthCheckBox->Checked)
            WSocket1->SocksLevel = "4A";   // SOCKS 4A is the variant that carries a user code
        else
            WSocket1->SocksLevel = "4";

        if (HttpTunnelRadioButton->Checked) {
            DisplayMemo->Lines->Add("Connecting using HTTP Tunnel Proxy");
            WSocket1->HttpTunnelServer = Trim(SocksServerEdit->Text);
            WSocket1->HttpTunnelPort = Trim(SocksPortEdit->Text);
            WSocket1->HttpTunnelUsercode = Trim(SocksUsercodeEdit->Text);
            WSocket1->HttpTunnelPassword = Trim(SocksPasswordEdit->Text);
            WSocket1->HttpTunnelAuthType = SocksAuthCheckBox->Checked ? htatBasic : htatNone;
        }
        else {
            DisplayMemo->Lines->Add("Connecting using Socks" + WSocket1->SocksLevel + " Proxy");
            WSocket1->SocksServer = Trim(SocksServerEdit->Text);
            WSocket1->SocksPort = Trim(SocksPortEdit->Text);
            WSocket1->SocksUsercode = Trim(SocksUsercodeEdit->Text);
            WSocket1->SocksPassword = Trim(SocksPasswordEdit->Text);
            WSocket1->SocksAuthentication = SocksAuthCheckBox->Checked ? socksAuthenticateUsercode : socksNoAuthentication;
        }
    }
    WSocket1->Proto = "tcp";
    WSocket1->Addr = Trim(TargetHostEdit->Text);
    WSocket1->Port = Trim(TargetPortEdit->Text);
    DisplayMemo->Lines->Add("Proxy Target: " + WSocket1->Addr + ":" + WSocket1->Port);
    WSocket1->Connect();
}

void __fastcall TSocksTestForm::DisconnectButtonClick(TObject *Sender)
{
    WSocket1->Close();
}

void __fastcall TSocksTestForm::cmdButtonClick(TObject *Sender)
{
    if (CommandEdit->Text != "" && WSocket1->State == wsConnected) {
        DisplayMemo->Lines->Add("> " + CommandEdit->Text);
        WSocket1->SendStr(CommandEdit->Text + IcsCRLF);
    }
}

void __fastcall TSocksTestForm::WSocket1SessionConnected(TObject *Sender, System::Word Error)
{
    if (Error == 0)
        cmdButtonClick(this);   // send the command as soon as the target is reached
}

// Collects received bytes in FRcvBuf and shows each complete line (CR LF or LF ended)
void __fastcall TSocksTestForm::WSocket1DataAvailable(TObject *Sender, System::Word Error)
{
    const int len = WSocket1->Receive(&FRcvBuf[FRcvCnt], static_cast<int>(sizeof(FRcvBuf)) - FRcvCnt - 1);
    if (len < 0)
        return;
    FRcvCnt += len;
    FRcvBuf[FRcvCnt] = '\0';

    while (FRcvCnt > 0) {
        // Delphi started the search at the second byte, so a line that was only "LF" never showed
        int i = 0;
        while (i < FRcvCnt && FRcvBuf[i] != '\n')
            i++;
        if (i >= FRcvCnt) {
            // Delphi waited forever once the buffer was full without a line feed; show what we have
            if (FRcvCnt >= static_cast<int>(sizeof(FRcvBuf)) - 1) {
                DisplayMemo->Lines->Add("Received: '" + String(AnsiString(FRcvBuf)) + "'");
                FRcvCnt = 0;
            }
            return;
        }
        FRcvBuf[i] = '\0';   // remove the LF
        if (i > 0 && FRcvBuf[i - 1] == '\r')
            FRcvBuf[i - 1] = '\0';   // and the CR before it

        DisplayMemo->Lines->Add("Received: '" + String(AnsiString(FRcvBuf)) + "'");

        // move what follows the line (and the terminating NUL) to the start of the buffer
        std::memmove(FRcvBuf, &FRcvBuf[i + 1], FRcvCnt - i);
        FRcvCnt = FRcvCnt - i - 1;
    }
}

void __fastcall TSocksTestForm::ClearButtonClick(TObject *Sender)
{
    DisplayMemo->Clear();
}
