unit Ws.ServiceImpl;

interface

uses
  Common.Logging,
  Common.Config,
  System.SysUtils,
  XData.Server.Module,
  XData.Service.Common,
  Sparkle.WebSocket,
  System.JSON,
  Sparkle.Sys.Timer,
  Sparkle.HttpServer.Context,
  Ws.Service,
  BaseRequest,
  LoginRequest,
  Pkg.Json.DTO,
  Generics.Collections;

type
  [ServiceImplementation]
  TWebSocketService = class(TInterfacedObject, IWebSocketService)
  private
    function WebSockerConnectionHandler: string;
    function Health: string;
    procedure ProcessJsonMessage(const AMsg: string);
  end;

implementation

{ TWebSocketService }

function TWebSocketService.WebSockerConnectionHandler: string;
var
  Upgrader: IWebSocketUpgrader;
  WebSocket: IWebSocket;
  Timer: TSparkleTimer;
  Msg: IWebSocketMessage;
  MsgStr: string;
begin
  Logger.Log(1, 'websocket begin-------');

  Upgrader := THttpServerContext.Current.Item<IWebSocketUpgrader>;
  if Upgrader = nil then
  begin
    TXDataOperationContext.Current.Handler.SetStatusCode(400);
    Exit;
  end;
  Logger.Log(1, 'websocket upgrading-------');

  WebSocket := Upgrader.Upgrade;
  Logger.Log(1, 'websocket upgraded-------');

  // First message must be the JWT token for authentication
  Msg := WebSocket.Receive;
  if Msg.MessageType <> TWebSocketMessageType.Text then
  begin
    Logger.Log(1, 'websocket auth failed - expected text token-------');
    WebSocket.SendClose(WebSocketStatusCodes.PolicyViolation);
    Exit;
  end;
  MsgStr := TEncoding.Default.GetString(Msg.Data);
  //TODO: verify JWT in MsgStr against serverConfig.jwtTokenSecret
  //      use the BCL JOSE verify API once the correct method is confirmed
  Logger.Log(1, 'websocket auth ok (token received, verification pending)-------');

  Timer := TSparkleTimer.Create(
    procedure(Value: TObject)
    begin
      if WebSocket.State = TWebSocketState.Open then
        WebSocket.Send('Server message at ' + TimeToStr(Time));
    end,
    nil, 2000, TTimerType.Periodic
  );

  try
    while WebSocket.State = TWebSocketState.Open do
    begin
      Msg := WebSocket.Receive;
      case Msg.MessageType of
        TWebSocketMessageType.Text:
        begin
          MsgStr := TEncoding.Default.GetString(Msg.Data);
          Logger.Log(1, 'Web Socket Message: ' + MsgStr);
          //TODO: store connection in map with device id, implement message protocol
        end;
        TWebSocketMessageType.Close:
          WebSocket.SendClose(WebSocketStatusCodes.NormalClosure);
      end;
    end;
  finally
    Timer.Free;
  end;
end;

function TWebSocketService.Health: string;
begin
  Result := 'ok';
end;

procedure TWebSocketService.ProcessJsonMessage(const AMsg: string);
var
  MsgDTO:  TBaseRequest;
  request: TRequest;
  rawJsObj: TJSONArray;
begin
  MsgDTO := nil;
  try
    rawJsObj:=TJSONObject.ParseJSONValue(AMsg) as TJSONArray;
    MsgDTO.FromJson(AMsg);

    for request in MsgDTO.Request do
    begin
      // Process each Request object
      Logger.Log(1, 'Action: ' + request.RequestId);
      if  request.RequestId = 'doLogin' then
      begin
        Logger.Log(1, 'Action: ' + request.RequestId);
        //TODO: implement instantiation and handling of each request type
        //  not sure if upcasting works in delphi here but probably not so we do this:
        //     rawJsObj:=TJSONObject.ParseJSONValue(AMsg) as TJSONArray
        //          and then ---->
        //     loginRequest.FromJson(rawJsObj.Items[0].ToJSON());
        var
          loginRequest:TLoginRequest;
        begin
          loginRequest.FromJson(rawJsObj.Items[0].ToJSON());
          //loginRequest.LoginInfo.Shift
          //TODO: it would be really great to have the JDO convert to database sql
        end;
      end;


    end;
    // ... further logic
  finally
    MsgDTO.Free;
  end;
end;


initialization
  RegisterServiceType(TWebSocketService);

end.
