unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, Winapi.ShellApi,
  System.SysUtils, System.Variants, System.Classes, System.Generics.Collections,
  System.IniFiles,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.Grids, Vcl.DBGrids,
  Data.DB,
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf,
  FireDAC.Comp.DataSet, FireDAC.Comp.Client,
  Auth.Service, Auth.Server.Module, Api.Server.Module, App.Server.Module,
  ExeInfo, Api.Service, WebSocket.Manager, Ws.Server.Module;

type
  TFMain = class(TForm)
    btnApiSwaggerUI: TButton;
    btnExit: TButton;
    initTimer: TTimer;
    btnAuthSwaggerUI: TButton;
    ExeInfo1: TExeInfo;
    pgcMain: TPageControl;
    tabServerLog: TTabSheet;
    tabConnectedClients: TTabSheet;
    memoInfo: TMemo;
    tblConnectedClients: TFDMemTable;
    dsConnectedClients: TDataSource;
    grdConnectedClients: TDBGrid;
    pnlConnectedClientsActions: TPanel;
    btnDisconnectClient: TButton;
    edtClientMessage: TEdit;
    btnSendClientMessage: TButton;
    procedure btnApiSwaggerUIClick(Sender: TObject);
    procedure btnExitClick(Sender: TObject);
    procedure ContactFormData(AText: string);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure initTimerTimer(Sender: TObject);
    procedure btnAuthSwaggerUIClick(Sender: TObject);
    procedure btnDisconnectClientClick(Sender: TObject);
    procedure btnSendClientMessageClick(Sender: TObject);
  strict private
    procedure StartServers;
    procedure HandleConnectedClientsChanged;
    procedure RefreshConnectedClients;
    function LogValue(LabelName: string; Value: string; FromIni: Boolean): string;
  private
    function LocalBrowserUrl(Url: string): string;
  end;

var
  FMain: TFMain;

implementation

uses
  Common.Logging,
  Common.Config,
  Common.Ini,
  Sparkle.Utils,
  Api.Database,
  System.StrUtils;

{$R *.dfm}

procedure TFMain.btnExitClick(Sender: TObject);
begin
  Close;
end;

procedure TFMain.btnSendClientMessageClick(Sender: TObject);
var
  connectionId: string;
begin
  if not Assigned(WsServerModule) then
    Exit;

  if tblConnectedClients.IsEmpty then
    Exit;

  if Trim(edtClientMessage.Text) = '' then
    Exit;

  connectionId := tblConnectedClients.FieldByName('ConnectionId').AsString;
  WsServerModule.SendMessageToClient(connectionId, edtClientMessage.Text);
end;

procedure TFMain.btnDisconnectClientClick(Sender: TObject);
var
  connectionId: string;
begin
  if not Assigned(WsServerModule) then
    Exit;

  if tblConnectedClients.IsEmpty then
    Exit;

  connectionId := tblConnectedClients.FieldByName('ConnectionId').AsString;
  WsServerModule.DisconnectClient(connectionId);
end;

function TFMain.LocalBrowserUrl(Url: string): string;
begin
  Result := StringReplace(Url, '://0.0.0.0:', '://localhost:', [rfIgnoreCase]);
end;

procedure TFMain.btnApiSwaggerUIClick(Sender: TObject);
var
  url: string;
begin
  url := TSparkleUtils.CombineUrlFast(ApiServerModule.XDataServer1.BaseUrl, 'swaggerui');
  ShellExecute(Handle, 'open', PChar(LocalBrowserUrl(url)), nil, nil, SW_SHOWNORMAL);
end;

procedure TFMain.btnAuthSwaggerUIClick(Sender: TObject);
var
  url: string;
begin
  url := TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui');
  ShellExecute(Handle, 'open', PChar(LocalBrowserUrl(url)), nil, nil, SW_SHOWNORMAL);
end;

procedure TFMain.ContactFormData(AText: string);
begin
  if memoInfo.CanFocus then
    TThread.Queue(nil,
      procedure
      begin
        memoInfo.Lines.Add(AText);
      end)
  else
    TThread.Synchronize(nil,
      procedure
      begin
        memoInfo.Lines.Add(AText);
      end);
end;

procedure TFMain.initTimerTimer(Sender: TObject);
begin
  initTimer.Enabled := False;
  Caption := Caption + ' ver ' + ExeInfo1.FileVersion;

  ServerConfig := TServerConfig.Create;
  LoadIniEntries;
  LoadServerConfig;
  StartServers;
end;

procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Assigned(WsServerModule) then
    WsServerModule.OnClientsChanged := nil;

  FreeAndNil(WsServerModule);
  FreeAndNil(AppServerModule);
  FreeAndNil(ApiServerModule);
  FreeAndNil(AuthServerModule);
  FreeAndNil(IniEntries);
  FreeAndNil(ServerConfig);
end;

function TFMain.LogValue(LabelName: string; Value: string; FromIni: Boolean): string;
begin
  Result := LabelName + ': ' + Value + IfThen(FromIni, ' [from ini]', ' [default]');
end;

procedure TFMain.StartServers;
begin
  Logger.Log(1, '');
  Logger.Log(1, '*******************************************************');
  Logger.Log(1, '*                  emiMobile Server                   *');
  Logger.Log(1, Format('*               Version: %s', [FMain.ExeInfo1.FileVersion]));
  Logger.Log(1, '*            Developed by EM Systems, Inc.            *');
  Logger.Log(1, '*******************************************************');
  Logger.Log(1, '');

  Logger.Log(1, '--- Settings ---');
  Logger.Log(1, LogValue('--Settings->LogFileNum', IniEntries.LogFileNum.ToString, IniEntries.LogFileNumFromIni));
  Logger.Log(1, LogValue('--Settings->webClientVersion', IniEntries.WebClientVersion, IniEntries.WebClientVersionFromIni));

  Logger.Log(1, '');
  Logger.Log(1, '--- URLs ---');

  try
    AuthServerModule := TAuthServerModule.Create(Self);
    AuthServerModule.StartAuthServer(ServerConfig.Url, AUTH_MODEL);

    ApiServerModule := TApiServerModule.Create(Self);
    ApiServerModule.StartApiServer(ServerConfig.Url, API_MODEL);

    AppServerModule := TAppServerModule.Create(Self);
    AppServerModule.StartAppServer(ServerConfig.Url);

    WsServerModule := TWsServerModule.Create(Self);
    WsServerModule.OnClientsChanged := HandleConnectedClientsChanged;
    WsServerModule.StartWsServer;
  except
    on E: Exception do
      Logger.Log(2, 'Failed to start server modules: ' + E.Message);
  end;

  RefreshConnectedClients;
end;

procedure TFMain.HandleConnectedClientsChanged;
begin
  TThread.Queue(nil,
    procedure
    begin
      RefreshConnectedClients;
    end);
end;

procedure TFMain.RefreshConnectedClients;
var
  clients: TArray<TConnectedClientSnapshot>;
  client: TConnectedClientSnapshot;
begin
  if not Assigned(WsServerModule) then
    Exit;

  clients := WsServerModule.GetClientSnapshots;

  tblConnectedClients.DisableControls;
  try
    tblConnectedClients.EmptyDataSet;

    for client in clients do
    begin
      tblConnectedClients.Append;
      tblConnectedClients.FieldByName('ConnectionId').AsString := client.ConnectionId;
      tblConnectedClients.FieldByName('UserId').AsString := client.UserId;
      tblConnectedClients.FieldByName('ConnectedAt').AsDateTime := client.ConnectedAt;
      tblConnectedClients.Post;
    end;
  finally
    tblConnectedClients.EnableControls;
  end;

  tabConnectedClients.Caption := Format('Connected Clients (%d)', [Length(clients)]);
end;

end.
