{
    This file is part of the Web Service Toolkit
    Copyright (c) 2006 by Inoussa OUEDRAOGO

    This file is provide under modified LGPL licence
    ( the files COPYING.modifiedLGPL and COPYING.LGPL).
    

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}
unit ics_tcp_protocol;

{$INCLUDE wst.inc}

interface

uses
  Classes, SysUtils,
  service_intf, imp_utils, base_service_intf,
  WSocket;

Const
  sTRANSPORT_NAME = 'TCP';

Type

  ETCPException = class(EServiceException)
  End;
  
{$M+}
  { TTCPTransport }
  TTCPTransport = class(TSimpleFactoryItem,ITransport)
  Private
    FPropMngr : IPropertyManager;
    FConnection : TWSocket;
    FContentType : string;
    FTarget: string;
    function GetAddress: string;
    function GetPort: string;
    procedure SetAddress(const AValue: string);
    procedure SetPort(const AValue: string);
  private
    FDataLength : LongInt;
    FDataBuffer : string;
    FAllDataRead : Boolean;
    FBeginRead : Boolean;
    FHasException : Boolean;
    FExceptionMessage : string;
    procedure DataAvailable(Sender: TObject; Error: Word);
    procedure BgExceptionHandler(Sender : TObject;E : Exception;var CanClose : Boolean);
  Public
    constructor Create();override;
    destructor Destroy();override;
    function GetPropertyManager():IPropertyManager;
    procedure SendAndReceive(ARequest,AResponse:TStream);
  Published
    property Target : string Read FTarget Write FTarget;
    property ContentType : string Read FContentType Write FContentType;
    property Address : string Read GetAddress Write SetAddress;
    property Port : string Read GetPort Write SetPort;
  End;
{$M+}

  procedure ICS_RegisterTCP_Transport();

implementation
uses binary_streamer, Math;

{ TTCPTransport }

function TTCPTransport.GetAddress: string;
begin
  Result := FConnection.Addr;
end;

function TTCPTransport.GetPort: string;
begin
  Result := FConnection.Port;
end;

procedure TTCPTransport.SetAddress(const AValue: string);
begin
  FConnection.Addr := AValue;
end;

procedure TTCPTransport.SetPort(const AValue: string);
begin
  FConnection.Port := AValue;
end;

procedure TTCPTransport.DataAvailable(Sender: TObject; Error: Word);
Var
  i,j : PtrInt;
  buff : string;
begin
  If Not FBeginRead Then Begin
    i := 1024;
    SetLength(buff,i);
    While ( FConnection.Receive(@(buff[1]),i) = i ) Do
      ;
    FDataBuffer := '';
    FDataLength := -1;
    Exit;
  End;

  If ( FDataLength < 0 ) Then Begin
    i := 4;
    if ( FConnection.Receive(@FDataLength,i) < i ) then
      raise ETCPException.Create('Error reading data length.');
    FDataLength := Reverse_32(FDataLength);
  End;
  If ( FDataLength > Length(FDataBuffer) ) Then Begin
    i := 1024;
    If ( i > FDataLength ) Then
      i := FDataLength;
    SetLength(buff,i);
    Repeat
      j := FConnection.Receive(@(buff[1]),i);
      FDataBuffer := FDataBuffer + Copy(buff,1,j);
      i := Min(1024,(FDataLength-Length(FDataBuffer)));
    Until ( i =0 ) or ( j <= 0 );
  End;
  FAllDataRead := ( FDataLength <= Length(FDataBuffer) );
end;

procedure TTCPTransport.BgExceptionHandler(Sender: TObject; E: Exception;var CanClose: Boolean);
begin
  CanClose := True;
  FHasException := True;
  FExceptionMessage := E.Message;
end;

constructor TTCPTransport.Create();
begin
  FDataLength := -1;
  FAllDataRead := False;
  FPropMngr := TPublishedPropertyManager.Create(Self);
  FConnection := TWSocket.Create(Nil);
  FConnection.OnDataAvailable := {$IFDEF FPC}@{$ENDIF}DataAvailable;
  FConnection.OnBgException := {$IFDEF FPC}@{$ENDIF}BgExceptionHandler;
end;

destructor TTCPTransport.Destroy();
begin
  FreeAndNil(FConnection);
  FPropMngr := Nil;
  inherited Destroy();
end;

function TTCPTransport.GetPropertyManager(): IPropertyManager;
begin
  Result := FPropMngr;
end;

procedure TTCPTransport.SendAndReceive(ARequest, AResponse: TStream);

Var
  wrtr : IDataStore;
  buffStream : TMemoryStream;
  strBuff : string;
{$IFDEF WST_DBG}
  s : string;
  i : Int64;
{$ENDIF WST_DBG}
begin
  buffStream := TMemoryStream.Create();
  Try
    wrtr := CreateBinaryWriter(buffStream);
    wrtr.WriteInt32S(0);
    wrtr.WriteStr(Target);
    wrtr.WriteStr(ContentType);
    SetLength(strBuff,ARequest.Size);
    ARequest.Position := 0;
    ARequest.Read(strBuff[1],Length(strBuff));
    wrtr.WriteStr(strBuff);
    buffStream.Position := 0;
    wrtr.WriteInt32S(buffStream.Size-4);

    If ( FConnection.State = wsClosed ) Then Begin
      FConnection.Connect();
      While ( FConnection.State < wsConnected ) Do
        FConnection.ProcessMessage();
    End;

    FDataBuffer := '';
    FDataLength := -1;
    FAllDataRead := False;
    FHasException := False;
    FExceptionMessage := '';
    FBeginRead := True;

    FConnection.Send(buffStream.Memory,buffStream.Size);
    FConnection.Flush();
    While Not ( FAllDataRead Or FHasException ) Do
      FConnection.ProcessMessage();
    If FHasException Then
      Raise ETCPException.Create(FExceptionMessage);
    AResponse.Size := 0;
    AResponse.Write(FDataBuffer[1],Length(FDataBuffer));
    FDataBuffer := '';
    FDataLength := -1;
    FAllDataRead := False;
    AResponse.Position := 0;
    {$IFDEF WST_DBG}
    i := AResponse.Position;
    SetLength(s,AResponse.Size);
    AResponse.Read(s[1],AResponse.Size);
    WriteLn(s);
    {$ENDIF WST_DBG}
  Finally
    buffStream.Free();
  End;
end;

procedure ICS_RegisterTCP_Transport();
begin
  GetTransportRegistry().Register(sTRANSPORT_NAME,TSimpleItemFactory.Create(TTCPTransport) as IItemFactory);
end;

end.
