unit uHCGameStatusViewer;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, IPPeerClient,
  IPPeerServer, System.Tether.Manager, System.Tether.AppProfile, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo,
{$IFDEF MSWINDOWS}
  Windows,
{$ENDIF}
  IdGlobal, IdException,
  IdSocketHandle, IdBaseComponent, IdComponent,
  IdIPMCastBase, IdIPMCastClient, IdStack,
  REST.JSON,
  uGameStatusTypeDefs,
  TGPSvcDiscoverUDP, FMX.TabControl, FMX.TMSBaseControl, FMX.TMSGridCell,
  FMX.TMSGridOptions, FMX.TMSGridData, FMX.TMSCustomGrid, FMX.TMSGrid;

type
  TfrmHCGameStatusViewer = class(TForm)
    memStats: TMemo;
    lblGameTime: TLabel;
    lblGameTimeLeft: TLabel;
    btnActivate: TButton;
    Button1: TButton;
    pct: TTabControl;
    tabGS: TTabItem;
    tabGSGrid: TTabItem;
    lblGameTimeLeftGrid: TLabel;
    grdGS: TTMSFMXGrid;
    lblGameTimeGrid: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure btnActivateClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure pctResize(Sender: TObject);
  private
    { Private declarations }
    MulticastClient: TTGPServiceDiscoveryUDPClient;
    procedure PrintGameStats(pGameStatus: TMMFGameStatus);
    procedure PrintGameStatsGrid(pGameStatus: TMMFGameStatus);
procedure IPMCastRead(Sender: TObject;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
  public
    { Public declarations }
  end;

var
  frmHCGameStatusViewer: TfrmHCGameStatusViewer;

const
  version = 1; // ALE 20150820 TMMFGameStatus structure version

implementation

{$R *.fmx}
{$R *.iPad.fmx IOS}

uses uHCDefines, uHCDateTime, uHCTypeDefs;

procedure TfrmHCGameStatusViewer.btnActivateClick(Sender: TObject);
begin
  MulticastClient.Active := True;
end;

procedure TfrmHCGameStatusViewer.Button1Click(Sender: TObject);
var
  FLocalIPs: TIdStackLocalAddressList;
  lI: NativeInt;
  IdSH: TIdSocketHandle;
begin
{
 FLocalIPs := TIdStackLocalAddressList.Create;
  // ALE 20150903 tinker with listing local IPs for binding
  TIdStack.IncUsage;
  GStack.GetLocalAddressList(FLocalIPs);

  //for lI := 0 to FLocalIPs.Count - 1 do
  //begin
  //  ShowMessage(FLocalIPs[lI].IPAddress);
  //end;
  IdIPMCastClient.Bindings.Clear;
  for lI := 0 to FLocalIPs.Count - 1 do
  begin
    if FLocalIPs[lI].IPVersion = TIdIPVersion.Id_IPv4 then
    begin
      IdSH := IdIPMCastClient.Bindings.Add;
      IdSH.SetBinding(FLocalIPs[lI].IPAddress, IdIPMCastClient.DefaultPort, FLocalIPs[lI].IPVersion);
    //ShowMessage(FLocalIPs[lI].IPAddress);
    end;
  end;
  IdIPMCastClient.Bindings.Clear;
  IdSH := IdIPMCastClient.Bindings.Add;
  IdSH.SetBinding('192.168.77.145', 45353, TIdIPVersion.Id_IPv4);
  IdIPMCastClient.Active := True;
  FLocalIPs.Free;
  }
end;

procedure TfrmHCGameStatusViewer.FormCreate(Sender: TObject);
begin
  Caption := Caption + ' v.' + CHCGameStatusVersion;
  MulticastClient := TTGPServiceDiscoveryUDPClient.Create(Self);
  MulticastClient.OnIPMCastRead := IPMCastRead;
  MulticastClient.Group := 'HCGameStatus';
  MulticastClient.GroupAddress := '224.0.0.1';
  MulticastClient.Active := True;
end;

procedure TfrmHCGameStatusViewer.FormDestroy(Sender: TObject);
begin
  MulticastClient.Active := False;
end;

procedure TfrmHCGameStatusViewer.IPMCastRead(Sender: TObject;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
  TThread.Queue(nil,
    procedure
    var
      stlStatus: TStringList;
      strData: String;
      strJSON: String;
      lGameStatus: TMMFGameStatus;
      dtNow: THCDateTimeRec;
    begin
      stlStatus := TStringList.Create;
      strData := BytesToString(AData);
      stlStatus.CommaText := strData;
      if ((stlStatus.Count > 0) AND (stlStatus[0] = 'HCGameStatus')) then
      begin
        // ALE 20150909 We get here if we are in the right multicast group
        if (stlStatus.Count > 2) then
        begin
          try
            strJSON := Copy(strData, AnsiPos('{', strData), Length(strData));
            lGameStatus := TJson.JsonToObject<TMMFGameStatus>(strJSON);
            if ((lGameStatus.version > 0) And (lGameStatus.version <> version))
            then
            begin
              MessageDlg
                (Format('Viewer Server (%d) and Viewer (%d) structure versions do not match',
                [lGameStatus.version, version]),
                System.UITypes.TMsgDlgType.mtError,
                [System.UITypes.TMsgDlgBtn.mbOK], 0);
              // Application.Terminate;
            end;
            lblGameTimeLeft.Text := Format('GameTimeLeft: %0.0n seconds',
              [lGameStatus.gametimeleft/1.0]);
            lblGameTimeLeftGrid.Text := lblGameTimeLeft.Text;
            dtNow := HCDateTimeInc(lGameStatus.gamestartdatetime,
              lGameStatus.gametime);
            lblGameTime.Text := 'GameDateTime: ' +
              Format('%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d',
              [dtNow.year, dtNow.month, dtNow.day, dtNow.hour, dtNow.minute,
              dtNow.second]);
            lblGameTimeGrid.Text := lblGameTime.Text;
            PrintGameStats(lGameStatus);
            PrintGameStatsGrid(lGameStatus);
          except
            // ALE 20150909 we only care if we get valid data
            ShowMessage('Error decoding JSON');
          end;
        end;
      end;

      // memMulticastMessages.Lines.Insert(0, ABinding.PeerIP + ' says ' +
      // BytesToString(AData));
      lGameStatus.Free;
      stlStatus.Free;
    end);

end;

procedure TfrmHCGameStatusViewer.pctResize(Sender: TObject);
begin
  grdGS.Height := pct.Height - grdGS.Position.Y - 30;
  grdGS.Width := pct.Width - grdGS.Position.X - 8;
end;

procedure TfrmHCGameStatusViewer.PrintGameStats(pGameStatus: TMMFGameStatus);
var
  r, c: NativeInt;
  i: Integer;

begin
  grdGS.BeginUpdate;
  grdGS.Clear;
  c := 0;
  r := 0;
  Inc(c); grdGS.Cells[c,r] := 'Blue';
  Inc(c); grdGS.Cells[c,r] := 'Red';
  Inc(c); grdGS.Cells[c,r] := 'Green';
  c := 0;
  r := 0;
  Inc(r); grdGS.Cells[c,r] := 'Carriers';
  Inc(r); grdGS.Cells[c,r] := 'Ships';
  Inc(r); grdGS.Cells[c,r] := 'Subs';
  Inc(r); grdGS.Cells[c,r] := 'Planes';
  Inc(r); grdGS.Cells[c,r] := 'Helicopters';
  Inc(r); grdGS.Cells[c,r] := 'Installations';
  Inc(r); grdGS.Cells[c,r] := 'AD Fixed';
  Inc(r); grdGS.Cells[c,r] := 'AD Mobile';
  Inc(r); grdGS.Cells[c,r] := 'Armored';
  Inc(r); grdGS.Cells[c,r] := 'Unarmored';

  for r := 0 to 9 do
  begin
    for c := 0 to SIDES-1 do
    begin
      grdGS.Cells[c+1,r+1] := Format('%0.0n', [pGameStatus.killcounts[r][c]/1.0]);
    end;
  end;

  grdGS.AutoSizeColumn(0, True);
  grdGS.EndUpdate;
end;

procedure TfrmHCGameStatusViewer.PrintGameStatsGrid(pGameStatus: TMMFGameStatus);
var
  i: NativeInt;
begin
  memStats.BeginUpdate;
  memStats.Lines.Clear;
  memStats.Lines.Add('            Blue    Red  Green');
  i := 0;
  memStats.Lines.Add(Format('Carriers   %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Ships      %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Subs       %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Planes     %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Heli       %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Bases      %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('AD Fixed   %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('AD Mobile  %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Armored    %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  Inc(i);
  memStats.Lines.Add(Format('Unarmored  %5d  %5d  %5d',
    [pGameStatus.killcounts[i][BLUESIDE], pGameStatus.killcounts[i][REDSIDE],
    pGameStatus.killcounts[i][GREENSIDE]]));
  memStats.EndUpdate;
end;

end.
