////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2023 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWKIT.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// DIM demo test client.                                                      //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20190220 - Last Win32 version by A.K.                                      //
// 20230524 - Modified for FPC (A.K.)                                         //
////////////////////////////////////////////////////////////////////////////////

program dim_demotest_client; // Test client to illustrate DIM features.

{$IFDEF WINDOWS}{$APPTYPE CONSOLE}{$ENDIF WINDOWS}

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$R *.res}

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes,
 _crw_alloc, _crw_cmdargs, _crw_environ, _crw_str, _crw_fio, _crw_rtc,
 _crw_dim, _crw_dimq, _crw_dimc, _crw_dims;

var
 dns_version  : Integer = 0;                // To receive DNS version
 no_link      : Integer = -1;               // To mark "server die" event.
 InfoServices : array of Integer = nil;     // List of subscribed inform. DIM services
 Terminated   : Boolean = false;            // Terminator for main loop
 Info1        : record                      // 1 user published data set
  CallCount   : Integer;                    // Call counter
  TickCount   : Integer;                    // GetTickCount
  Now         : Double;                     // Current time
 end;
 Info2        : record                      // 2 user published data set
  FileTime    : Int64;                      // System time as file time
  DateTime    : array[byte] of char;        // Date and time string
 end;
 cmnd         : array[byte] of char;        // Command to send to server

 //
 // Thread safe printing: Print(s,1) => StdOut, Print(s,2) => StdErr
 //
function Print(const Msg:LongString; n:Integer=1):DWORD;
var h:THandle;
begin
 Result:=0;
 if n=1 then h:=GetStdHandle(STD_OUTPUT_HANDLE) else
 if n=2 then h:=GetStdHandle(STD_ERROR_HANDLE)  else h:=INVALID_HANDLE_VALUE;
 if (h>0) and (Msg<>'') then Result:=FileWrite(h,PChar(Msg)^,Length(Msg));
end;

function PrintLn(const Msg:LongString; n:Integer=1):DWORD;
const Delimeter=EOL;
begin
 Result:=Print(Msg+Delimeter,n);
end;

 //
 // Callback on receive DNS version
 //
procedure on_got_dns_version(var tag:TDimLong; buff:Pointer; var size:Integer); cdecl;
begin
 try
  if (size=SizeOf(no_link)) and (Integer(buff^) = no_link) then begin
   PrintLn('DNS server is dead. Please restart DNS.EXE.');
   Exit;
  end;
  if size=SizeOf(dns_version) then begin
   PrintLn(Format('Got DNS version: %d, tag:%d',[Integer(buff^),tag]));
   dns_version:=Integer(buff^);
   Exit;
  end;
  PrintLn(Format('Invalid data size: %d, tag:%d',[size,tag]));
 except
  on E:Exception do PrintLn(E.Message,2);
 end;
end;

 //
 // Callback on receive information service data
 //
procedure on_got_data(var tag:TDimLong; buff:Pointer; var size:Integer); cdecl;
var node:array[byte] of char; pid:Integer;
begin
 try
  if (size=SizeOf(no_link)) and (Integer(buff^) = no_link) then begin
   PrintLn(Format('Service %d disconnected. Please restart server.',[tag]));
   Exit;
  end;
  node:=''; pid:=0; // Get server info
  if dic_get_server(node)=0 then node:='';
  if dic_get_server_pid(pid)=0 then pid:=0;
  case tag of
   1: begin // Receive dataset #1
       if size=SizeOf(Info1) then begin
        Move(buff^,Info1,size);
        PrintLn(Format('Got data: CallCount=%d, TickCount=%d, Now=%g from server %s PID %d',
                    [Info1.CallCount,Info1.TickCount,Info1.Now,node,pid]));
       end else PrintLn(Format('Invalid data set %d',[tag]));
      end;
   2: begin // Receive dataset #2
       if size<=SizeOf(Info2) then begin
        Move(buff^,Info2,size);
        PrintLn(Format('Got data: FileTime=%d, DateTime=%s from server %s PID %d',
                    [Int64(Info2.FileTime),Info2.DateTime,node,pid]));
       end else PrintLn(Format('Invalid data set %d',[tag]));
      end;
   else PrintLn(Format('Invalid service %d',[tag]));
  end;
 except
  on E:Exception do PrintLn(E.Message,2);
 end;
end;

 //
 // Callback on command sent to server
 //
procedure on_cmnd_sent(var tag:TDimLong; var ret_code:Integer); cdecl;
begin
 try
  if ret_code>0
  then PrintLn(Format('Sent command %d - Ok',[tag]))
  else PrintLn(Format('Could not send command %d',[tag]));
 except
  on E:Exception do PrintLn(E.Message,2);
 end;
end;

 //
 // Callback on Client Error
 //
procedure on_client_error(severity:Integer; error_code:Integer; error_message:PChar); cdecl;
begin
 try
  PrintLn(Format('%s - %s (%d) - %s',[FormatDateTime('yyyy.mm.dd-hh:nn:ss',Now),
                                      dim_severity_name(severity),error_code,error_message]));
  if (dic_get_error_services<>nil) and (strlen(dic_get_error_services)>0)
  then PrintLn('Error services: '+dic_get_error_services);
 except
  on E:Exception do PrintLn(E.Message,2);
 end;
end;

procedure Main;
var i:Integer; dns_node:array[byte] of char;
begin
 try
  CmdArgs.ListOptVal:='-dns';
  if CmdArgs.HasOptionValue('-dns')
  then SetEnv('DIM_DNS_NODE',CmdArgs.GetOptionValue('-dns'));
  //
  // 1. Initialize DIM, set DNS node.
  //
  dim_init;
  dis_disable_padding;
  dic_disable_padding;
  dic_add_error_handler(on_client_error);
  if dim_get_dns_node(dns_node)=0 then raise Exception.Create('Fail get DNS.');
  if StrLen(dns_node)=0 then dim_set_dns_node('localhost');
  if dim_get_dns_node(dns_node)=0 then raise Exception.Create('Fail get DNS.');
  if StrLen(dns_node)=0 then raise Exception.Create('Fail set DNS.');
  PrintLn('DIM_DNS_NODE='+dns_node);
  //
  // 2. Subscribe DNS server version to call it only once.
  //
  dic_info_service('DIS_DNS/VERSION_NUMBER',        // DNS server version service name
                   ONCE_ONLY,                       // Call it only once
                   10,                              // Timeout, seconds
                   nil, 0, on_got_dns_version,      // Callback to receive DNS version
                   1,                               // Tag to identify this service
                   @no_link, SizeOf(no_link));      // Data to send in case of timeout
  //
  // 3. Subscribe information services published by server.
  //
  SetLength(InfoServices,2);
  InfoServices[0]:=dic_info_service('DEMO/INFO1',   // Name of published service
                   MONITORED,                       // Refresh by server only
                   10,                              // Timeout, sec, on server die
                   nil, 0, on_got_data,             // Callback on data received
                   1,                               // Tag to identify this service
                   @no_link, SizeOf(no_link));      // Data to send in case of timeout
  InfoServices[1]:=dic_info_service('DEMO/INFO2',   // Name of published service
                   MONITORED,                       // Refresh by server only
                   10,                              // Timeout, sec, on server die
                   nil, 0, on_got_data,             // Callback on data received
                   2,                               // Tag to identify this service
                   @no_link, SizeOf(no_link));      // Data to send in case of timeout
  //
  // 4. Main client loop
  //
  while not Terminated do begin
   PrintLn('Enter command to send to server (RESET for example):');
   readln(cmnd); if strlen(cmnd)=0 then continue;
   if StrIComp(cmnd,'QUIT')=0 then break;
   PrintLn(Format('Sending command: "%s"',[cmnd]));
   dic_cmnd_callback('DEMO/COMMAND1',               // Name of command service
                     @cmnd,strlen(cmnd)+1,          // Command to send
                     on_cmnd_sent,                  // Callback on data sent
                     1);                            // Tag to identify this service
  end;
  //
  // 5. Stop DIM.
  //
  PrintLn('Stop DIM...');
  for i:=0 to Length(InfoServices)-1 do dic_release_service(InfoServices[i]);
  dic_close_dns; dic_stop;
  PrintLn('Goodbye.');
  Sleep(2000);
 except
  on E:Exception do PrintLn(E.Message,2);
 end;
end;

begin
 Main;
end.

//////////////
// END OF FILE
//////////////

