program BarevClientPascal;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}
  Classes, SysUtils,
  Barev, BarevTypes;

type
  THandler = class
    procedure OnLog(const L, M: string);
    procedure OnStatus(B: TBarevBuddy; OldS, NewS: TBuddyStatus);
    procedure OnMsg(B: TBarevBuddy; const Msg: string);
    procedure OnConn(B: TBarevBuddy; S: TConnectionState);
    procedure OnTyping(B: TBarevBuddy; IsTyping: Boolean);
    procedure OnFTOffer(B: TBarevBuddy; const Sid, Name: string; Size: Int64);
    procedure OnFTProgress(B: TBarevBuddy; const Sid: string; Done, Total: Int64);
    procedure OnFTComplete(B: TBarevBuddy; const Sid, Path: string);
    procedure OnFTError(B: TBarevBuddy; const Sid, Err: string);
    procedure OnRoomMsg(const RoomJID, FromNick, MessageText: string);
    procedure OnRoomJoin(const RoomJID, Nick: string);
    procedure OnRoomLeft(const RoomJID, Nick: string);
    procedure OnRoomNick(const RoomJID, OldNick, NewNick: string);
  end;

  TNetThread = class(TThread)
  private
    C: TBarevClient;
  protected
    procedure Execute; override;
  public
    constructor Create(AClient: TBarevClient);
  end;

var
  Client: TBarevClient;

procedure THandler.OnLog(const L, M: string);
begin
  WriteLn('[', L, '] ', M);
end;

procedure THandler.OnStatus(B: TBarevBuddy; OldS, NewS: TBuddyStatus);
begin
  WriteLn('*** ', B.Nick, ' is now ', StatusToString(NewS));
end;

procedure THandler.OnMsg(B: TBarevBuddy; const Msg: string);
begin
  WriteLn;
  WriteLn('*** Message from ', B.Nick, ': ', Msg);
  Write('> '); Flush(Output);
end;

procedure THandler.OnConn(B: TBarevBuddy; S: TConnectionState);
begin
  WriteLn('*** Connection to ', B.Nick, ': ', Ord(S));
end;

procedure THandler.OnTyping(B: TBarevBuddy; IsTyping: Boolean);
begin
  WriteLn;
  if IsTyping then WriteLn('*** ', B.Nick, ' is typing...')
  else WriteLn('*** ', B.Nick, ' stopped typing');
  Write('> '); Flush(Output);
end;

procedure THandler.OnFTOffer(B: TBarevBuddy; const Sid, Name: string; Size: Int64);
begin
  WriteLn;
  WriteLn('*** FT offer from ', B.JID, ' sid=', Sid, ' file=', Name, ' size=', Size);
  Write('> '); Flush(Output);
end;

procedure THandler.OnFTProgress(B: TBarevBuddy; const Sid: string; Done, Total: Int64);
begin
  WriteLn('*** FT ', Sid, ' ', Done, '/', Total);
end;

procedure THandler.OnFTComplete(B: TBarevBuddy; const Sid, Path: string);
begin
  WriteLn('*** FT complete sid=', Sid, ' path=', Path);
end;

procedure THandler.OnFTError(B: TBarevBuddy; const Sid, Err: string);
begin
  WriteLn('*** FT error sid=', Sid, ' err=', Err);
end;

procedure THandler.OnRoomMsg(const RoomJID, FromNick, MessageText: string);
begin
  WriteLn;
  WriteLn('*** [', RoomJID, '] ', FromNick, ': ', MessageText);
  Write('> '); Flush(Output);
end;

procedure THandler.OnRoomJoin(const RoomJID, Nick: string);
begin
  WriteLn('*** [', RoomJID, '] join: ', Nick);
end;

procedure THandler.OnRoomLeft(const RoomJID, Nick: string);
begin
  WriteLn('*** [', RoomJID, '] left: ', Nick);
end;

procedure THandler.OnRoomNick(const RoomJID, OldNick, NewNick: string);
begin
  WriteLn('*** [', RoomJID, '] nick: ', OldNick, ' -> ', NewNick);
end;

constructor TNetThread.Create(AClient: TBarevClient);
begin
  inherited Create(False);
  FreeOnTerminate := False;
  C := AClient;
end;

procedure TNetThread.Execute;
begin
  while not Terminated do
  begin
    C.Process;
    Sleep(20);
  end;
end;

procedure ShowHelp;
begin
  WriteLn('Commands:');
  WriteLn('  help');
  WriteLn('  /alias <name> <jid_or_roomjid>');
  WriteLn('  /set <alias_or_jid_or_roomjid>');
  WriteLn('  add <nick> <ipv6> [port]');
  WriteLn('  connect <jid>');
  WriteLn('  msg <jid> <text>');
  WriteLn('  joinroom <room_jid> [nick]');
  WriteLn('  leaveroom <room_jid>');
  WriteLn('  roommsg <room_jid> <text>');
  WriteLn('  typing <jid>');
  WriteLn('  paused <jid>');
  WriteLn('  sendfile <jid> <path>');
  WriteLn('  acceptfile <sid> <saveas>');
  WriteLn('  rejectfile <sid>');
  WriteLn('  quit');
end;

procedure RunLoop;
var
  Line, Cmd, A1, A2, A3, Sid: string;
  Parts: TStringList;
  Aliases: TStringList;
  CurrentTarget: string;
  B: TBarevBuddy;
  function ResolveTarget(const S: string): string;
  var
    i: Integer;
  begin
    Result := S;
    i := Aliases.IndexOfName(S);
    if i >= 0 then
      Result := Trim(Aliases.ValueFromIndex[i]);
  end;
begin
  Parts := TStringList.Create;
  Aliases := TStringList.Create;
  Aliases.NameValueSeparator := '=';
  CurrentTarget := '';
  try
    while True do
    begin
      Write('> ');
      ReadLn(Line);
      Line := Trim(Line);
      if Line = '' then Continue;

      Parts.Clear;
      ExtractStrings([' '], [], PChar(Line), Parts);
      if Parts.Count = 0 then Continue;

      Cmd := LowerCase(Parts[0]);

      if Cmd = 'help' then ShowHelp
      else if Cmd = '/alias' then
      begin
        if Parts.Count < 3 then begin WriteLn('usage: /alias <name> <jid_or_roomjid>'); Continue; end;
        Aliases.Values[Parts[1]] := Parts[2];
        WriteLn('alias set: ', Parts[1], ' -> ', Parts[2]);
      end
      else if Cmd = '/set' then
      begin
        if Parts.Count < 2 then begin WriteLn('usage: /set <alias_or_jid_or_roomjid>'); Continue; end;
        CurrentTarget := ResolveTarget(Parts[1]);
        WriteLn('current target: ', CurrentTarget);
      end
      else if Cmd = 'quit' then Break
      else if Cmd = 'add' then
      begin
        if Parts.Count < 3 then begin WriteLn('usage: add <nick> <ipv6> [port]'); Continue; end;
        A1 := Parts[1]; A2 := Parts[2];
        if Parts.Count >= 4 then A3 := Parts[3] else A3 := IntToStr(BAREV_DEFAULT_PORT);
        B := Client.AddBuddy(A1, A2, StrToIntDef(A3, BAREV_DEFAULT_PORT));
        if Assigned(B) then WriteLn('added ', B.JID) else WriteLn('add failed');
      end
      else if Cmd = 'connect' then
      begin
        if Parts.Count < 2 then WriteLn('usage: connect <jid>')
        else if Client.ConnectToBuddy(ResolveTarget(Parts[1])) then WriteLn('connect ok')
        else WriteLn('connect failed');
      end
      else if Cmd = 'msg' then
      begin
        if Parts.Count < 3 then begin WriteLn('usage: msg <jid> <text>'); Continue; end;
        A1 := ResolveTarget(Parts[1]);
        A2 := Copy(Line, Pos(Parts[1], Line) + Length(Parts[1]) + 1, MaxInt);
        if Client.SendMessage(A1, A2) then WriteLn('send ok') else WriteLn('send failed');
      end
      else if Cmd = 'joinroom' then
      begin
        if Parts.Count < 2 then begin WriteLn('usage: joinroom <room_jid_or_alias> [nick]'); Continue; end;
        A1 := ResolveTarget(Parts[1]);
        if Parts.Count >= 3 then A2 := Parts[2] else A2 := '';
        if Client.JoinRoom(A1, A2) then WriteLn('join ok') else WriteLn('join failed');
      end
      else if Cmd = 'leaveroom' then
      begin
        if Parts.Count < 2 then begin WriteLn('usage: leaveroom <room_jid_or_alias>'); Continue; end;
        if Client.LeaveRoom(ResolveTarget(Parts[1])) then WriteLn('leave ok') else WriteLn('leave failed');
      end
      else if Cmd = 'roommsg' then
      begin
        if Parts.Count < 3 then begin WriteLn('usage: roommsg <room_jid_or_alias> <text>'); Continue; end;
        A1 := ResolveTarget(Parts[1]);
        A2 := Copy(Line, Pos(Parts[1], Line) + Length(Parts[1]) + 1, MaxInt);
        if Client.SendRoomMessage(A1, A2) then WriteLn('room send ok') else WriteLn('room send failed');
      end
      else if Cmd = 'typing' then
      begin
        if Parts.Count < 2 then WriteLn('usage: typing <jid>')
        else if Client.SendTyping(Parts[1]) then WriteLn('typing ok')
        else WriteLn('typing failed');
      end
      else if Cmd = 'paused' then
      begin
        if Parts.Count < 2 then WriteLn('usage: paused <jid>')
        else if Client.SendPaused(Parts[1]) then WriteLn('paused ok')
        else WriteLn('paused failed');
      end
      else if Cmd = 'sendfile' then
      begin
        if Parts.Count < 3 then WriteLn('usage: sendfile <jid> <path>')
        else begin
          B := Client.FindBuddyByJID(Parts[1]);
          if not Assigned(B) then WriteLn('buddy not found')
          else begin
            Sid := Client.FileTransfer.OfferFile(B, Parts[2]);
            if Sid <> '' then WriteLn('offer sent sid=', Sid) else WriteLn('offer failed');
          end;
        end;
      end
      else if Cmd = 'acceptfile' then
      begin
        if Parts.Count < 3 then WriteLn('usage: acceptfile <sid> <saveas>')
        else if Client.FileTransfer.AcceptOffer(Parts[1], Parts[2]) then WriteLn('accept ok')
        else WriteLn('accept failed');
      end
      else if Cmd = 'rejectfile' then
      begin
        if Parts.Count < 2 then WriteLn('usage: rejectfile <sid>')
        else if Client.FileTransfer.RejectOffer(Parts[1]) then WriteLn('reject ok')
        else WriteLn('reject failed');
      end
      else
      begin
        if CurrentTarget <> '' then
        begin
          if Pos('@', CurrentTarget) > 0 then
          begin
            if Pos(' ', CurrentTarget) = 0 then
            begin
              if Client.SendMessage(CurrentTarget, Line) then WriteLn('send ok')
              else
              begin
                if Client.SendRoomMessage(CurrentTarget, Line) then WriteLn('room send ok')
                else WriteLn('send failed');
              end;
            end;
          end;
        end
        else
          WriteLn('unknown command');
      end;
    end;
  finally
    Parts.Free;
    Aliases.Free;
  end;
end;

var
  H: THandler;
  T: TNetThread;
  Nick, Ip, PStr: string;
  Port: Word;
begin
  if ParamCount < 2 then
  begin
    WriteLn('usage: barevclient <nick> <my_ipv6> [port]');
    Halt(2);
  end;

  Nick := ParamStr(1);
  Ip := ParamStr(2);
  if ParamCount >= 3 then PStr := ParamStr(3) else PStr := IntToStr(BAREV_DEFAULT_PORT);
  Port := StrToIntDef(PStr, BAREV_DEFAULT_PORT);

  H := THandler.Create;
  Client := TBarevClient.Create(Nick, Ip, Port);
  try
    Client.OnLog := @H.OnLog;
    Client.OnBuddyStatus := @H.OnStatus;
    Client.OnMessageReceived := @H.OnMsg;
    Client.OnConnectionState := @H.OnConn;
    Client.OnTypingNotification := @H.OnTyping;
    Client.FileTransfer.OnFileOffer := @H.OnFTOffer;
    Client.FileTransfer.OnProgress := @H.OnFTProgress;
    Client.FileTransfer.OnComplete := @H.OnFTComplete;
    Client.FileTransfer.OnError := @H.OnFTError;
    Client.OnRoomMessage := @H.OnRoomMsg;
    Client.OnRoomOccupantJoined := @H.OnRoomJoin;
    Client.OnRoomOccupantLeft := @H.OnRoomLeft;
    Client.OnRoomOccupantNickChanged := @H.OnRoomNick;

    if not Client.Start then Halt(1);

    T := TNetThread.Create(Client);
    try
      WriteLn('barevclient (pascal) started as ', Client.MyJID);
      ShowHelp;
      RunLoop;
    finally
      T.Terminate;
      T.WaitFor;
      T.Free;
    end;

    Client.Stop;
  finally
    Client.Free;
    H.Free;
  end;
end.
