{*******************************************************}
{                                                       }
{       Tocsg.Packet                                    }
{                                                       }
{       Copyright (C) 2022 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.Packet;

interface

uses
  Tocsg.Obj, Tocsg.Exception, System.SysUtils, Winapi.Windows,
  superobject, System.Classes;

const
  SIZE_INTEGER = SizeOf(Integer);
  CTX_PACKET_SIGNATURE: AnsiString = '@OC$.G';
  LEN_CTX_PACKET_SIGNATURE = 6;

type
// Server -> Client
  PCtxPacketHeader = ^TCtxPacketHeader;
  TCtxPacketHeader = packed record
    sSig: array [0..5] of AnsiChar;  // #SPK.$$
    wRank: WORD;
    dwId,
    dwSize: DWORD;
  end;


// 종류 추가 되면 패킷 해더의 wRank 수정해야함
  TTgPacketKind = (pkNormal, pkFileQueue, pkIgnore, pkCritical);
  TTgPacketHeader = record
    Cmd,
    Result: Integer;
    Toss,
    Handle,
    WndMessage: LONGLONG;
    ResultMsg: String;
    PacketKind: TTgPacketKind;
  end;

  TSnTextEncoding = System.SysUtils.TEncoding;
  TSnBytes = TBytes;

  ETgPacket = class(ETgException);
  ITgPacket = interface
    ['{5F9E8D1B-7371-41D2-AA7A-E8DDAE3788B9}']
    function _GetPacketHeader: TTgPacketHeader;
    function _GetJsonObject: ISuperObject;

    function ToJsonString: String;
    function ToBytes(var SnBytes: TSnBytes): Integer;
    function ToBytesDataOnly(var SnBytes: TSnBytes): Integer;
    function SaveToFile(sPath: String): Boolean;
  end;

  ISendPacket = interface(ITgPacket)
    ['{0322E57A-C699-40BE-B0DF-64B9A69A5701}']
    function GetCommand: Integer;
    procedure SetPacketKind(aPacketKind: TTgPacketKind);
    function GetPacketKind: TTgPacketKind;
    procedure SetHandle(llHandle: LONGLONG);
    procedure SetWndMessage(llWndMessage: LONGLONG);
    procedure SetToss(llToss: LONGLONG);
    procedure SetResult(nResult: Integer);
    procedure SetResultMsg(const sResultMsg: String);
    function GetResult: Integer;
    function GetResultMsg: String;
    function GetSocket: TObject;
    procedure SetSocket(aSocket: TObject);

    function GetO(const sPath: String): ISuperObject;

    procedure PutO(const sPath: String; const Value: ISuperObject);
    procedure PutB(const sPath: String; Value: Boolean);
    procedure PutI(const sPath: String; Value: SuperInt);
    procedure PutC(const sPath: String; Value: Currency);
    procedure PutD(const sPath: String; Value: Double);
    procedure PutS(const sPath: String; const Value: String);

    property Command: Integer read GetCommand;
    property Handle: LONGLONG write SetHandle;
    property WndMessage: LONGLONG write SetWndMessage;
    property Toss: LONGLONG write SetToss;
    property Result: Integer read GetResult write SetResult;
    property ResultMsg: String read GetResultMsg write SetResultMsg;
    property PacketKind: TTgPacketKind read GetPacketKind write SetPacketKind;
    property Socket: TObject read GetSocket write SetSocket;

    property O[const sPath: String]: ISuperObject read GetO write PutO; default;
    property B[const sPath: String]: Boolean write PutB;
    property I[const sPath: String]: SuperInt write PutI;
    property D[const sPath: String]: Double write PutD;
    property C[const sPath: String]: Currency write PutC;
    property S[const sPath: String]: String write PutS;
  end;

  IRcvPacket = interface(ITgPacket)
    ['{C1BFCE0F-1B34-4FCC-84AC-4F24CA6D72A5}']
    function GetCommand: Integer;
    function GetPacketKind: TTgPacketKind;
    function GetHandle: LONGLONG;
    function GetWndMessage: LONGLONG;
    function GetToss: LONGLONG;
    function GetResult: Integer;
    function GetResultMsg: String;
    function GetSocket: TObject;
    function GetRcvPacketSize: DWORD;

    function ToJsonString: String;

    function GetO(const sPath: String): ISuperObject;
    function GetB(const sPath: String): Boolean;
    function GetI(const sPath: String): SuperInt;
    function GetD(const sPath: String): Double;
    function GetC(const sPath: String): Currency;
    function GetS(const sPath: String): String;
    function GetA(const sPath: String): TSuperArray;

    property Command: Integer read GetCommand;
    property PacketKind: TTgPacketKind read GetPacketKind;
    property Handle: LONGLONG read GetHandle;
    property WndMessage: LONGLONG read GetWndMessage;
    property Toss: LONGLONG read GetToss;
    property Result: Integer read GetResult;
    property ResultMsg: String read GetResultMsg;
    property Socket: TObject read GetSocket;
    property RcvPacketSize: DWORD read GetRcvPacketSize;

    property O[const sPath: String]: ISuperObject read GetO; default;
    property B[const sPath: String]: Boolean read GetB;
    property I[const sPath: String]: SuperInt read GetI;
    property D[const sPath: String]: Double read GetD;
    property C[const sPath: String]: Currency read GetC;
    property S[const sPath: String]: String read GetS;
    property A[const sPath: String]: TSuperArray read GetA;
  end;

  TTgPacket = class(TInterfacedObject, ITgPacket, ISendPacket, IRcvPacket)
  protected
    Socket_: TObject;
    PacketHeader_: TTgPacketHeader;
    SuperObject_: ISuperObject;
    dwRcvLen_: DWORD;

    function _GetPacketHeader: TTgPacketHeader;
    function _GetJsonObject: ISuperObject;

    procedure PutO(const sPath: String; const Value: ISuperObject);
    procedure PutB(const sPath: String; Value: Boolean);
    procedure PutI(const sPath: String; Value: SuperInt);
    procedure PutC(const sPath: String; Value: Currency);
    procedure PutD(const sPath: String; Value: Double);
    procedure PutS(const sPath: String; const Value: String);

    function GetO(const sPath: String): ISuperObject;
    function GetB(const sPath: String): Boolean;
    function GetI(const sPath: String): SuperInt;
    function GetD(const sPath: String): Double;
    function GetC(const sPath: String): Currency;
    function GetS(const sPath: String): String;
    function GetA(const sPath: String): TSuperArray;

    function GetCommand: Integer;
    procedure SetPacketKind(aPacketKind: TTgPacketKind);
    function GetPacketKind: TTgPacketKind;
    procedure SetHandle(llHandle: LONGLONG);
    function GetHandle: LONGLONG;
    procedure SetWndMessage(llWndMessage: LONGLONG);
    function GetWndMessage: LONGLONG;
    procedure SetToss(llToss: LONGLONG);
    function GetToss: LONGLONG;
    procedure SetResult(nResult: Integer);
    function GetResult: Integer;
    procedure SetResultMsg(const sResultMsg: String);
    function GetResultMsg: String;
    function GetSocket: TObject;
    procedure SetSocket(aSocket: TObject);
    function GetRcvPacketSize: DWORD;  // for IRcvPacket
  public
    Constructor Create; overload;
    Constructor Create(const aCmd: Integer; PacketKind: TTgPacketKind = pkNormal); overload;
    Constructor Create(aPacket: ITgPacket; PacketKind: TTgPacketKind = pkNormal); overload;
    Constructor Create(const aRcvData: Pointer; nRcvLen: Integer); overload;
    Constructor Create(const aRcvData: TSnBytes; bIncludeLen: Boolean = false; aSocket: TObject= nil{for ClientContext}); overload;
    Constructor Create(aSocket: TObject{for ClientContext}; const aRcvData: Pointer; nRcvLen: Integer); overload;
    Constructor Create(const sJsonData: String); overload;
    function ToJsonString: String;
    function ToBytes(var SnBytes: TSnBytes): Integer;
    function ToBytesDataOnly(var SnBytes: TSnBytes): Integer;
    function SaveToFile(sPath: String): Boolean;

    property Command: Integer read GetCommand;
    property PacketKind: TTgPacketKind read GetPacketKind;
    property Toss: LONGLONG read GetToss write SetToss;
    property Handle: LONGLONG read GetHandle write SetHandle;
    property WndMessage: LONGLONG read GetWndMessage write SetWndMessage;
    property Result: Integer read GetResult write SetResult;
    property ResultMsg: String read GetResultMsg write SetResultMsg;
    property Socket: TObject read GetSocket write SetSocket;

    property O[const sPath: String]: ISuperObject read GetO write PutO; default;
    property B[const sPath: String]: Boolean read GetB write PutB;
    property I[const sPath: String]: SuperInt read GetI write PutI;
    property D[const sPath: String]: Double read GetD write PutD;
    property C[const sPath: String]: Currency read GetC write PutC;
    property S[const sPath: String]: String read GetS write PutS;
  end;

const
  LEN_CTX_PACKET_HEADER = SizeOf(TCtxPacketHeader);
  BUFFER_SIZE = 32 * 1024;
  MAX_BUF_LEN = BUFFER_SIZE;

implementation

uses
  Tocsg.Safe, Tocsg.JSON;

{ TTgPacket }

Constructor TTgPacket.Create;
begin
  Inherited Create;
  ZeroMemory(@PacketHeader_, SizeOf(PacketHeader_));
  dwRcvLen_ := 0;
end;

Constructor TTgPacket.Create(const aCmd: Integer; PacketKind: TTgPacketKind = pkNormal);
begin
  Create;
  PacketHeader_.Cmd := aCmd;
  PacketHeader_.PacketKind := PacketKind;
  SuperObject_ := TSuperObject.Create;
end;

Constructor TTgPacket.Create(aPacket: ITgPacket; PacketKind: TTgPacketKind = pkNormal);
begin
  Create;
  PacketHeader_ := aPacket._GetPacketHeader;
  SuperObject_ := aPacket._GetJsonObject;
  PacketHeader_.PacketKind := PacketKind;
end;

Constructor TTgPacket.Create(const aRcvData: TSnBytes; bIncludeLen: Boolean = false; aSocket: TObject = nil{for ClientContext});

  procedure ExtractData;
  var
    nDataLen: Integer;
    sJsonData: UTF8String;
  begin
    nDataLen := 0;

    if bIncludeLen then
    begin
      dwRcvLen_ := Length(aRcvData);
      if dwRcvLen_ < 4 then
        raise ETgPacket.Create('Rcv 버퍼가 손상되었습니다.');

      CopyMemory(@nDataLen, @aRcvData[0], SIZE_INTEGER);

      if nDataLen <> (dwRcvLen_ - SIZE_INTEGER) then
      begin
        raise ETgPacket.CreateFmt('Rcv 데이터 크기가 잘못되었습니다. (DataLen = %d, RcvBufLen = %d',
                                    [nDataLen, dwRcvLen_ - SIZE_INTEGER]);
      end;

      SetLength(sJsonData, nDataLen);
      CopyMemory(@sJsonData[1], @aRcvData[4], nDataLen);
    end else begin
      nDataLen := Length(aRcvData);
      dwRcvLen_ := nDataLen;
      SetLength(sJsonData, nDataLen);
      CopyMemory(@sJsonData[1], @aRcvData[0], nDataLen);
    end;

    SuperObject_ := SO(sJsonData);

    try
      with SuperObject_['Header'], PacketHeader_ do
      begin
        Cmd        := I['Cmd'];
        Handle     := I['Handle'];
        WndMessage := I['WndMessage'];
        PacketKind := TTgPacketKind(I['PacketKind']);
        Result     := I['Result'];
        Toss       := I['Toss'];
        ResultMsg  := S['ResultMsg'];
      end;
    except
      raise ETgPacket.Create('Invalid packet ..');
    end;
  end;

begin
  Create;
  Socket_ := aSocket;
  ExtractData;
end;

Constructor TTgPacket.Create(aSocket: TObject{for ClientContext}; const aRcvData: Pointer; nRcvLen: Integer);
begin
  Create(aRcvData, nRcvLen);
  Socket_ := aSocket;
end;

Constructor TTgPacket.Create(const aRcvData: Pointer; nRcvLen: Integer);

  procedure ExtractData;
  var
    sJsonData: UTF8String;
  begin
    dwRcvLen_ := nRcvLen;
    SetLength(sJsonData, nRcvLen);
    CopyMemory(@sJsonData[1], aRcvData, nRcvLen);

    SuperObject_ := SO(sJsonData);

    try
      with SuperObject_['Header'], PacketHeader_ do
      begin
        Cmd        := I['Cmd'];
        Handle     := I['Handle'];
        WndMessage := I['WndMessage'];
        PacketKind := TTgPacketKind(I['PacketKind']);
        Result     := I['Result'];
        Toss       := I['Toss'];
        ResultMsg  := S['ResultMsg'];
      end;
    except
      raise ETgPacket.Create('Invalid packet ..');
    end;
  end;

begin
  Create;
  ExtractData;
end;

Constructor TTgPacket.Create(const sJsonData: String);
begin
  Inherited Create;
  SuperObject_ := SO(sJsonData);
  with SuperObject_['Header'], PacketHeader_ do
  begin
    Cmd        := I['Cmd'];
    Handle     := I['Handle'];
    WndMessage := I['WndMessage'];
    PacketKind := TTgPacketKind(I['PacketKind']);
    Result     := I['Result'];
    Toss       := I['Toss'];
    ResultMsg  := S['ResultMsg'];
  end;
end;

function TTgPacket._GetPacketHeader: TTgPacketHeader;
begin
  Result := PacketHeader_;
end;

function TTgPacket._GetJsonObject: ISuperObject;
begin
  Result := SuperObject_;
end;

function TTgPacket.ToJsonString: String;
var
  O: ISuperObject;
begin
  Result := '';
  if SuperObject_ <> nil then
  begin
    O := SO;
    with PacketHeader_, O do
    begin
      I['Cmd'] := Cmd;
      if Handle <> 0 then
        I['Handle'] := Handle;
      if WndMessage <> 0 then
        I['WndMessage'] := WndMessage;
      if PacketKind <> pkNormal then
        I['PacketKind'] := Integer(PacketKind);
      if Result <> 0 then
        I['Result'] := Result;
      if Toss <> 0 then
        I['Toss'] := Toss;
      if ResultMsg <> '' then
        S['ResultMsg'] := ResultMsg;
    end;
    SuperObject_.O['Header'] := O;
    Result := SuperObject_.AsJSon;
  end;
end;

function TTgPacket.ToBytes(var SnBytes: TSnBytes): Integer;
var
  sJsonData: UTF8String;
  nDataLen: Integer;
begin
  Result := 0;
  sJsonData := UTF8Encode(ToJsonString);
  if sJsonData <> '' then
  begin
    nDataLen := Length(sJsonData);
    Result := SIZE_INTEGER + Length(sJsonData);
    SetLength(SnBytes, Result);
    CopyMemory(@SnBytes[0], @nDataLen, SIZE_INTEGER);
    CopyMemory(@SnBytes[4], @sJsonData[1], nDataLen);
//    SetLength(sJsonData, 0);
  end;
end;

function TTgPacket.ToBytesDataOnly(var SnBytes: TSnBytes): Integer;
var
  sJsonData: UTF8String;
begin
  Result := 0;
  sJsonData := UTF8Encode(ToJsonString);
  if sJsonData <> '' then
  begin
    Result := Length(sJsonData);
    SetLength(SnBytes, Result);
    CopyMemory(@SnBytes[0], @sJsonData[1], Result);
  end;
end;

function TTgPacket.SaveToFile(sPath: String): Boolean;
var
  ss: TStringStream;
begin
  Result := false;
  try
    Guard(ss, TStringStream.Create(ToJsonString, TEncoding.UTF8));
    ss.SaveToFile(sPath);
    Result := true;
  except
  //
  end;
end;

procedure TTgPacket.PutO(const sPath: String; const Value: ISuperObject);
begin
  SuperObject_.O[sPath] := Value;
end;

procedure TTgPacket.PutB(const sPath: String; Value: Boolean);
begin
  SuperObject_.B[sPath] := Value;
end;

procedure TTgPacket.PutI(const sPath: String; Value: SuperInt);
begin
  SuperObject_.I[sPath] := Value;
end;

procedure TTgPacket.PutC(const sPath: String; Value: Currency);
begin
  SuperObject_.C[sPath] := Value;
end;

procedure TTgPacket.PutD(const sPath: String; Value: Double);
begin
  SuperObject_.D[sPath] := Value;
end;

procedure TTgPacket.PutS(const sPath: String; const Value: String);
begin
  SuperObject_.S[sPath] := Value;
end;

function TTgPacket.GetO(const sPath: String): ISuperObject;
begin
  Result := SuperObject_.O[sPath];
end;

function TTgPacket.GetB(const sPath: String): Boolean;
begin
  Result := SuperObject_.B[sPath];
end;

function TTgPacket.GetI(const sPath: String): SuperInt;
begin
  Result := SuperObject_.I[sPath];
end;

function TTgPacket.GetD(const sPath: String): Double;
begin
  Result := SuperObject_.D[sPath];
end;

function TTgPacket.GetC(const sPath: String): Currency;
begin
  Result := SuperObject_.C[sPath];
end;

function TTgPacket.GetS(const sPath: String): String;
begin
  Result := SuperObject_.S[sPath];
end;

function TTgPacket.GetA(const sPath: String): TSuperArray;
begin
  Result := SuperObject_.A[sPath];
end;

function TTgPacket.GetCommand: Integer;
begin
  Result := PacketHeader_.Cmd;
end;

procedure TTgPacket.SetPacketKind(aPacketKind: TTgPacketKind);
begin
  if PacketHeader_.PacketKind <> aPacketKind then
    PacketHeader_.PacketKind := aPacketKind;
end;

function TTgPacket.GetPacketKind: TTgPacketKind;
begin
  Result := PacketHeader_.PacketKind;
end;

procedure TTgPacket.SetHandle(llHandle: LONGLONG);
begin
  if PacketHeader_.Handle <> llHandle then
    PacketHeader_.Handle := llHandle;
end;

function TTgPacket.GetHandle: LONGLONG;
begin
  Result := PacketHeader_.Handle;
end;

procedure TTgPacket.SetWndMessage(llWndMessage: LONGLONG);
begin
  if PacketHeader_.WndMessage <> llWndMessage then
    PacketHeader_.WndMessage := llWndMessage;
end;

function TTgPacket.GetWndMessage: LONGLONG;
begin
  Result := PacketHeader_.WndMessage;
end;

procedure TTgPacket.SetToss(llToss: LONGLONG);
begin
  if PacketHeader_.Toss <> llToss then
    PacketHeader_.Toss := llToss;
end;

function TTgPacket.GetToss: LONGLONG;
begin
  Result := PacketHeader_.Toss;
end;

procedure TTgPacket.SetResult(nResult: Integer);
begin
  if PacketHeader_.Result <> nResult then
    PacketHeader_.Result := nResult;
end;

function TTgPacket.GetResult: Integer;
begin
  Result := PacketHeader_.Result;
end;

procedure TTgPacket.SetResultMsg(const sResultMsg: String);
begin
  if PacketHeader_.ResultMsg <> sResultMsg then
    PacketHeader_.ResultMsg := sResultMsg;
end;

function TTgPacket.GetResultMsg: String;
begin
  Result := PacketHeader_.ResultMsg;
end;

function TTgPacket.GetSocket: TObject;
begin
  Result := Socket_;
{$IFDEF DEBUG}
  if Result = nil then
    raise ETgPacket.Create('소켓이 지정 되지있지 않습니다.');
{$ENDIF}
end;

procedure TTgPacket.SetSocket(aSocket: TObject);
begin
  if Socket_ <> aSocket then
    Socket_ := aSocket;
end;

function TTgPacket.GetRcvPacketSize: DWORD;
begin
  Result := dwRcvLen_;
end;

end.
