{$MODE OBJFPC}
unit dsocket;

interface

uses
  dstrnum,
  Sockets;

const
  SOCKET_TYPE_TCP = 0;
  SOCKET_TYPE_UDP = 1;

type
THandle = LongInt;
TSize = SizeInt;

PSocketAddress = ^TSocketAddress;
TSocketAddress = object
private
  FAddr: TInetSockAddr;
public
  procedure Init(Port: LongInt);
  procedure Init(const Addr: AnsiString);
  procedure Done;
  function ToStr: AnsiString;
end;

PSocket = ^TSocket;
TSocket = object
private
  FReady: Boolean;
  FHandle: THandle;
public
  procedure Init(SocketType: LongInt = SOCKET_TYPE_TCP); inline;
  procedure Done; inline;
  function Bind(const Addr: TSocketAddress): Boolean; inline;
  function Send(Msg: Pointer; Len: TSize; Flags: LongInt = 0): TSize; inline;
  function Recv(Buf: Pointer; Len: TSize; Flags: LongInt = 0): TSize; inline;
  function Listen(BackLog: LongInt): Boolean; inline;
  function Accept(out Socket: TSocket; out Addr: TSocketAddress): Boolean; inline;
  function Connect(const Addr: TSocketAddress): Boolean; inline;
end;

operator := (const Addr: AnsiString): TSocketAddress;
operator := (const Port: LongInt): TSocketAddress;

implementation

procedure TSocketAddress.Init(Port: LongInt);
begin
  FAddr.sin_family := AF_INET;
  FAddr.sin_port := htons(Port);
  FAddr.sin_addr.s_addr := 0;
end;

procedure TSocketAddress.Init(const Addr: AnsiString);
var
  Colon: LongInt;
  Port: LongInt;
  Host: AnsiString;
begin
  Port := 0;
  Colon := Pos(':', Addr);
  if Colon <> 0 then begin
    Port := StrToInt(Copy(Addr, Colon + 1, Length(Addr) - Colon));
    Host := Copy(Addr, 1, Colon - 1);
  end else
    Host := Addr;
  FAddr.sin_family := AF_INET;
  FAddr.sin_port := htons(Port);
  FAddr.sin_addr := StrToNetAddr(Host);
  //if FAddr.sin_addr.s_addr = 0 then begin
  //  FAddr.sin_addr := StrToHostAddr6(Host);
  //end;
end;

procedure TSocketAddress.Done;
begin
  // Nothing to do
end;

function TSocketAddress.ToStr: AnsiString;
begin
  if FAddr.sin_family = AF_INET then begin
    Result := NetAddrToStr(FAddr.sin_addr);
  end else if FAddr.sin_family = AF_INET6 then begin
    //Result := HostAddrToStr6(FAddr.sin6_addr);
    Result := '';
  end else
    Result := '';
end;

procedure TSocket.Init(SocketType: LongInt);
begin
  if SocketType = SOCKET_TYPE_UDP then begin
    // TODO
    FReady := False;
  end else begin // SOCKET_TYPE_TCP
    FHandle := fpSocket(AF_INET, SOCK_STREAM, 0);
    FReady := FHandle <> 0;
  end;
end;

procedure TSocket.Done;
begin
  if not FReady then
    CloseSocket(FHandle);
end;

function TSocket.Bind(const Addr: TSocketAddress): Boolean;
begin
  Result := fpBind(FHandle, @Addr.FAddr, SizeOf(Addr.FAddr)) = 0;
end;

function TSocket.Send(Msg: Pointer; Len: TSize; Flags: LongInt = 0): TSize;
begin
  Result := fpSend(FHandle, Msg, Len, Flags);
end;

function TSocket.Recv(Buf: Pointer; Len: TSize; Flags: LongInt = 0): TSize;
begin
  Result := fpRecv(FHandle, Buf, Len, Flags);
end;

function TSocket.Listen(BackLog: LongInt): Boolean;
begin
  Result := fpListen(FHandle, BackLog) = 0;
end;

function TSocket.Accept(out Socket: TSocket; out Addr: TSocketAddress): Boolean;
var
  ClientAddrSize: LongInt;
begin
  ClientAddrSize := SizeOf(Addr.FAddr);
  Socket.FHandle := fpAccept(FHandle, @Addr.FAddr, @ClientAddrSize);
  Result := Socket.FHandle <> 0;
end;

function TSocket.Connect(const Addr: TSocketAddress): Boolean;
begin
  Result := fpConnect(FHandle, @Addr.FAddr, SizeOf(Addr.FAddr)) = 0;
end;

operator := (const Addr: AnsiString): TSocketAddress;
begin
  Result.Init(Addr);
end;

operator := (const Port: LongInt): TSocketAddress;
begin
  Result.Init(Port);
end;

end.
