/*
  ICS Time Server and Client demo - C++Builder translation of the Delphi demo
  OverbyteIcsTimeTst1.pas (demos-delphi-vcl), ICS V9.8.

  TIcsTimeServer and TIcsTimeClient: time server and client supporting the TIME protocol
  (UDP and TCP, RFC868) and SNTP v4 (RFC2030). Full NTP is not supported.

  How this C++ version differs from the Delphi original, apart from syntax:
  - Delphi's bare try/except becomes catch (Exception &E), which catches everything ICS
    and the RTL raise, but not standard C++ exceptions such as std::bad_alloc.
  - The Stop button is captioned "Stop"; the Delphi form has the typo "Sop".
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsTimeTst1.h"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsWSocket.hpp"

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

TTimeDemoForm *TimeDemoForm;

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

void __fastcall TTimeDemoForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    doServerStop->Caption = "Stop";        // the Delphi .dfm spells it "Sop"
    ServerIP->Items->Assign(LocalIPList());
}

void __fastcall TTimeDemoForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    IcsTimeServer1->Stop();
}

void __fastcall TTimeDemoForm::doCloseClick(TObject *Sender)
{
    Close();
}

void __fastcall TTimeDemoForm::doServerStartClick(TObject *Sender)
{
    IcsTimeServer1->Addr = ServerIP->Text;

    // The property is a set, so the chosen protocols are collected in a local set and assigned once.
    TTimeProtoSet protocols;
    if (ServerProtoSntp->Checked)
        protocols << tpSNTP;
    if (ServerProtoTCP->Checked)
        protocols << tpTCP;
    if (ServerProtoUdp->Checked)
        protocols << tpUDP;
    IcsTimeServer1->TimeProtocol = protocols;

    try {
        IcsTimeServer1->Start();
    }
    catch (Exception &E) {
        DisplayMemo->Lines->Add("Failed to start time server: " + IcsGetExceptMess(&E));
    }
}

void __fastcall TTimeDemoForm::doServerStopClick(TObject *Sender)
{
    IcsTimeServer1->Stop();
}

void __fastcall TTimeDemoForm::doTestConvClick(TObject *Sender)
{
    const TNtpTime ntpTime = IcsGetUTCNtpTime();
    DisplayMemo->Lines->Add("TNtpTime: " + IntToStr(static_cast<__int64>(ntpTime.Seconds)) + " seconds, " +
                            IntToStr(static_cast<__int64>(ntpTime.FracSecs)) + " fractional seconds");
    DisplayMemo->Lines->Add("Now converted back to TDateTime: " + IcsTimeToZStr(IcsNtp2TDateTime(ntpTime)));
}

void __fastcall TTimeDemoForm::doClientAbortClick(TObject *Sender)
{
    IcsTimeClient1->Abort();
    doClientStart->Enabled = true;
}

void __fastcall TTimeDemoForm::doClientStartClick(TObject *Sender)
{
    doCorrect->Enabled = false;
    LabelCorrection->Caption = "Correction Needed?";
    IcsTimeClient1->ServerAddr = ClientTimeServer->Text;
    IcsTimeClient1->SocketFamily = static_cast<TSocketFamily>(SockFamily->ItemIndex);
    IcsTimeClient1->TimeProtocol = static_cast<TTimeProtocol>(ClientProtocol->ItemIndex);
    IcsTimeClient1->TimeoutSecs = 5;
    DisplayMemo->Lines->Add("Starting time request to: " + IcsTimeClient1->ServerAddr);
    try {
        if (!IcsTimeClient1->GetTime())
            DisplayMemo->Lines->Add("Time request failed");
        else
            doClientStart->Enabled = false;
    }
    catch (Exception &E) {
        DisplayMemo->Lines->Add("Time request exception: " + IcsGetExceptMess(&E));
    }
}

void __fastcall TTimeDemoForm::doCorrectClick(TObject *Sender)
{
    IcsTimeClient1->ServerAddr = ClientTimeServer->Text;
    IcsTimeClient1->TimeProtocol = static_cast<TTimeProtocol>(ClientProtocol->ItemIndex);
    IcsTimeClient1->TimeoutSecs = 10;
    IcsTimeClient1->SocketFamily = sfAnyIPv4;   // prefer IPv4
    DisplayMemo->Lines->Add("Starting correction request to: " + IcsTimeClient1->ServerAddr);
    try {
        if (!IcsTimeClient1->GetTime())
            DisplayMemo->Lines->Add("Time request failed");
        else
            doClientStart->Enabled = false;
    }
    catch (Exception &E) {
        DisplayMemo->Lines->Add("Time request exception: " + IcsGetExceptMess(&E));
    }
}

/* Shows the reply. A second SNTP reply with a difference and a round trip under 500 ms,
   after the Correct Time button has armed itself, sets the PC clock (needs administrator rights). */
void __fastcall TTimeDemoForm::IcsTimeClient1Time(TObject *Sender, TDateTime DateTime)
{
    // only IcsTimeClient1 is wired to this handler
    TIcsTimeClient *client = static_cast<TIcsTimeClient*>(Sender);

    if (DateTime.Val == 0) {
        DisplayMemo->Lines->Add("Unable to Get Network Time, Server: " + client->ServerAddr +
                                " [" + client->IpAddress + "] - " + client->LastProgress);
        doCorrect->Enabled = false;
    }
    else {
        DisplayMemo->Lines->Add("Response from Server: " + client->ServerAddr + " [" + client->IpAddress +
                                "], Attempts " + IntToStr(client->Attempts));
        DisplayMemo->Lines->Add("UTC/GMT " + IcsDateTimeZZtoStr(client->DTTransmit));
        DisplayMemo->Lines->Add("Difference: " + client->SecsDiffDisp + " secs, Roundtrip: " +
                                IcsIntToCStr(client->MSecsRoundtrip) + "ms");
        DisplayMemo->Lines->Add("Version=" + IntToStr(client->ServVN) +
                                ", Mode=" + IntToStr(client->ServMode) +
                                ", Statum=" + IntToStr(client->SNTP.Statum) +
                                ", Precision=" + IntToStr(client->SNTP.Precision) +
                                ", Root Delay=" + IntToStr(client->SNTP.RootDelay));
        DisplayMemo->Lines->Add("Local PC: UTC/GMT " + IcsDateTimeZZtoStr(IcsGetUTCTime()));
        LabelCorrection->Caption = "Correction Needed: " + client->SecsDiffDisp + " secs";
        if (IcsTimeClient1->TimeProtocol != tpSNTP)
            DisplayMemo->Lines->Add("Beware TIME protocol only accurate to one second");
        else if (client->SecsDifference != 0 && client->MSecsRoundtrip < 500) {
            if (doCorrect->Enabled) {
                // correct the PC clock by the time difference, not the absolute time, because of round trip delays
                doCorrect->Enabled = false;
                const TDateTime oldUtc = IcsGetUTCTime();
                const TDateTime newUtc = IcsGetNewTime(oldUtc, client->DTDifference);
                DisplayMemo->Lines->Add("!!! Changing system time from UTC: " + IcsDateTimeZZtoStr(oldUtc) +
                                        " to " + IcsDateTimeZZtoStr(newUtc));
                if (!IcsSetUTCTime(newUtc))
                    DisplayMemo->Lines->Add("Clock correction failed, requires administrator rights");
                DisplayMemo->Lines->Add("Local PC: UTC/GMT " + IcsDateTimeZZtoStr(IcsGetUTCTime()));
            }
            else
                doCorrect->Enabled = true;   // correct next time
        }
    }
    doClientStart->Enabled = true;
    DisplayMemo->Lines->Add("");
}

void __fastcall TTimeDemoForm::IcsTimeClient1TimeInfo(TObject *Sender, String Info)
{
    DisplayMemo->Lines->Add(Info);
}

void __fastcall TTimeDemoForm::IcsTimeServer1Query(TObject *Sender, const TSockAddrIn &SocketAddr,
                                                   TTimeProtocol TimeProtocol, bool &Continue)
{
    const String remAddr = WSocketIPv4ToStr(SocketAddr.sin_addr.s_addr);
    String prot;
    if (TimeProtocol == tpUDP)
        prot = "UDP";
    else if (TimeProtocol == tpTCP)
        prot = "TCP";
    else if (TimeProtocol == tpSNTP)
        prot = "SNTP";
    else
        prot = "??";
    DisplayMemo->Lines->Add("Time Request from " + remAddr + " using " + prot);
}

void __fastcall TTimeDemoForm::IcsTimeServer1QueryDone(TObject *Sender, Word Error)
{
    if (Error == 0)
        return;
    const String remAddr = WSocketIPv4ToStr(static_cast<TIcsTimeServer*>(Sender)->SrcIPAddr.sin_addr.s_addr);
    DisplayMemo->Lines->Add("Time Server Request Error " + IntToStr(Error) + " to " + remAddr);
}

void __fastcall TTimeDemoForm::IcsTimeServer1Start(TObject *Sender)
{
    DisplayMemo->Lines->Add("Time server started");
}

void __fastcall TTimeDemoForm::IcsTimeServer1Stop(TObject *Sender)
{
    DisplayMemo->Lines->Add("Time server stopped");
}
