with Pipe;
with Connections;
with Window_Io;
with Split_Window;
with System_Utilities;
with Message;
with Io_Exceptions;
with Io;
with Bounded_String;
package body Commands is

    package Sio renames Io;

    subtype User_Name is Bounded_String.Variable_String (20);

    package Raw renames Window_Io.Raw;
    function "=" (K1, K2 : Raw.Key) return Boolean renames Raw."=";

    Quit : Raw.Key;
    Delete : Raw.Key;
    Return_Key : Raw.Key;

    Send_Delete : Raw.Key;
    Send_Return : Raw.Key;

    Found : Boolean;

    Terminal_Type : constant String := System_Utilities.Terminal_Type;

    package Key_Ops is new Pipe.Type_Specific_Operations (Raw.Key);

    task type Keyboard_Manager is
        entry Start (Caller : Connections.Handle;
                     Answerer : Connections.Handle;
                     Users_Name : String);
    end Keyboard_Manager;

    task body Keyboard_Manager is
        Stream : Raw.Stream_Type;
        A_Key : Raw.Key;

        Call : Connections.Handle;
        Answer : Connections.Handle;

        Call_Pipe : Pipe.Handle;
        Answer_Pipe : Pipe.Handle;

        User : User_Name;
        A_Window : Window_Io.File_Type;

        task Pipe_Manager is
            entry Start;
        end Pipe_Manager;

        task body Pipe_Manager is
            A_Key : Raw.Key;
        begin

            accept Start;

            loop
                Key_Ops.Read (Answer_Pipe, A_Key);

                if A_Key = Quit then
                    Raw.Close (Stream);

                    Connections.Break (Answer);
                    Sio.Put_Line (Sio.Standard_Error,
                                  Bounded_String.Image (User) &
                                     " terminated the connection");

                    exit;

                elsif A_Key = Send_Delete then
                    Split_Window.Window_Manager.Delete_Char_Bottom;
                elsif A_Key = Send_Return then
                    Split_Window.Window_Manager.Write_To_Bottom (Ascii.Cr);
                elsif A_Key in Raw.Simple_Key then
                    Split_Window.Window_Manager.Write_To_Bottom
                       (Raw.Convert (A_Key));
                else
                    null;
                end if;
            end loop;

            abort Keyboard_Manager;
        end Pipe_Manager;

    begin

        accept Start (Caller : Connections.Handle;
                      Answerer : Connections.Handle;
                      Users_Name : String) do
            Call := Caller;
            Answer := Answerer;

            Call_Pipe := Connections.Convert (Caller);
            Answer_Pipe := Connections.Convert (Answerer);

            Pipe_Manager.Start;
            Bounded_String.Copy (User, Users_Name);
        end Start;

        Raw.Open (Stream);

        loop
            Raw.Get (Stream, A_Key);

            if A_Key = Quit then
                Key_Ops.Write (Call_Pipe, A_Key);

                Raw.Close (Stream);
                Connections.Break (Answer);
                Sio.Put_Line (Sio.Standard_Error,
                              "Call to " & Bounded_String.Image (User) &
                                 " terminated");
                abort Pipe_Manager;
                exit;
            elsif A_Key = Delete then
                Key_Ops.Write (Call_Pipe, Send_Delete);

                Split_Window.Window_Manager.Delete_Char_Top;

            elsif A_Key = Return_Key then
                Key_Ops.Write (Call_Pipe, Send_Return);

                Split_Window.Window_Manager.Write_To_Top (Ascii.Cr);

            elsif A_Key in Raw.Simple_Key then
                Key_Ops.Write (Call_Pipe, A_Key);

                Split_Window.Window_Manager.Write_To_Top (Raw.Convert (A_Key));

            else
                Window_Io.Bell (A_Window);
            end if;

        end loop;
    end Keyboard_Manager;

    procedure Phone (User : String;
                     Session : String := Commands.Any_Session;
                     Wait : Duration := 30.0) is
        Calling_Connection : Connections.Handle;
        Return_Connection : Connections.Handle;
        Elapsed : Duration := 0.0;
        Terminate_Me : exception;
    begin
        begin
            Calling_Connection := Connections.Establish
                                     (System_Utilities.User_Name,
                                      System_Utilities.Session_Name, User,
                                      Session, Connections.Call);

        exception
            when Connections.Already_Exists =>
                Sio.Put_Line (Sio.Standard_Error,
                              "You are already calling " & User);
                raise Terminate_Me;
            when Connections.Not_Established =>
                Sio.Put_Line (Sio.Standard_Error,
                              "Unable to place call to " & User);
                raise Terminate_Me;
        end;

        Message.Send (User, "I am phoning you");
        Sio.Put_Line (Sio.Standard_Error, "Phoning " & User);

        loop
            begin
                Return_Connection :=
                   Connections.Establish
                      (User, Session, System_Utilities.User_Name,
                       System_Utilities.Session_Name,
                       Connections.Listen_Answer);
                exit;
            exception
                when Connections.Not_Available =>
                    if Elapsed > Wait then
                        Sio.Put_Line (Sio.Standard_Error,
                                      User & " did not answer");
                        Connections.Break (Calling_Connection);
                        raise Terminate_Me;
                    else
                        Sio.Put (Sio.Standard_Error, "ringing...");
                        delay 8.0;
                        Elapsed := Elapsed + 8.0;
                    end if;
                when Connections.Not_Established =>
                    Sio.Put_Line (Sio.Standard_Error,
                                  "Problem getting answer connection");
                    Connections.Break (Calling_Connection);
                    raise Terminate_Me;
                when Connections.Already_Exists =>
                    Sio.Put_Line (Sio.Standard_Error,
                                  "Specific session must be specified");
                    Connections.Break (Calling_Connection);
                    raise Terminate_Me;
            end;
        end loop;

        Split_Window.Window_Manager.Create;

        declare
            Keyboard : Keyboard_Manager;
        begin
            Keyboard.Start (Calling_Connection, Return_Connection, User);
        end;

        loop
            begin
                Connections.Break (Calling_Connection);
                exit;
            exception
                when Pipe.Use_Error =>
                    null;
            end;
        end loop;

        Split_Window.Window_Manager.Delete;

    exception
        when Terminate_Me =>
            Split_Window.Window_Manager.Delete;

    end Phone;

    procedure Answer (User : String := Commands.Whoever_Is_Phoning;
                      Session : String := Commands.Any_Session) is

        Answered_Connection : Connections.Handle;
        Return_Connection : Connections.Handle;

        Terminate_Me : exception;

        First_Available_User : constant String :=
           Connections.Is_Available
              (User, Session, System_Utilities.User_Name,
               System_Utilities.Session_Name, Connections.Call);
    begin
        begin
            if User = Whoever_Is_Phoning then
                Answered_Connection := Connections.Establish
                                          (First_Available_User, Session,
                                           System_Utilities.User_Name,
                                           System_Utilities.Session_Name,
                                           Connections.Listen_Call);
            else
                Answered_Connection :=
                   Connections.Establish (User, Session,
                                          System_Utilities.User_Name,
                                          System_Utilities.Session_Name,
                                          Connections.Listen_Call);

            end if;
        exception
            when Connections.Not_Available =>
                if User = Whoever_Is_Phoning then
                    Sio.Put_Line (Sio.Standard_Error, "Nobody is calling you");
                else
                    Sio.Put_Line (Sio.Standard_Error,
                                  User & " is not calling you");

                end if;

                raise Terminate_Me;
            when Connections.Not_Established =>
                Sio.Put_Line (Sio.Standard_Error, "Unable to answer " & User);
                raise Terminate_Me;
            when Connections.Already_Exists =>
                Sio.Put_Line (Sio.Standard_Error,
                              "Specific session must be specified");
                raise Terminate_Me;
        end;

        begin
            Return_Connection := Connections.Establish
                                    (System_Utilities.User_Name,
                                     System_Utilities.Session_Name,
                                     First_Available_User,
                                     Session, Connections.Answer);
        exception
            when Connections.Already_Exists =>
                Sio.Put_Line (Sio.Standard_Error,
                              "You are already calling" & User);
                Connections.Break (Answered_Connection);
                raise Terminate_Me;
            when Connections.Not_Established =>
                Sio.Put_Line (Sio.Standard_Error,
                              "Problem getting answer connection");
                Connections.Break (Answered_Connection);
                raise Terminate_Me;
        end;

        Split_Window.Window_Manager.Create;

        declare
            Keyboard : Keyboard_Manager;
        begin
            if User = Whoever_Is_Phoning then
                Sio.Put_Line (Sio.Standard_Error,
                              "Connected to " & First_Available_User);
                Keyboard.Start (Return_Connection, Answered_Connection,
                                First_Available_User);
            else
                Sio.Put_Line (Sio.Standard_Error, "Connected to " & User);
                Keyboard.Start (Return_Connection, Answered_Connection, User);
            end if;
        end;

        loop
            begin
                Connections.Break (Return_Connection);
                exit;
            exception
                when Pipe.Use_Error =>
                    null;
            end;
        end loop;

        Split_Window.Window_Manager.Delete;

    exception
        when Terminate_Me =>
            Split_Window.Window_Manager.Delete;

    end Answer;
begin
    Raw.Value ("Delete", "RATIONAL", Send_Delete, Found);
    Raw.Value ("Carriage_Return", "RATIONAL", Send_Return, Found);

    if Terminal_Type = "RATIONAL" then
        Raw.Value ("C_C", Terminal_Type, Quit, Found);
        Raw.Value ("Delete", Terminal_Type, Delete, Found);
        Raw.Value ("Carriage_Return", Terminal_Type, Return_Key, Found);
    elsif Terminal_Type = "VT100" then
        Raw.Value ("C_C", Terminal_Type, Quit, Found);
        Raw.Value ("Delete", Terminal_Type, Delete, Found);
        Raw.Value ("Enter", Terminal_Type, Return_Key, Found);
    elsif Terminal_Type = "CIT500R" then
        Raw.Value ("C_C", Terminal_Type, Quit, Found);
        Raw.Value ("Backspace", Terminal_Type, Delete, Found);
        Raw.Value ("Carriage_Return", Terminal_Type, Return_Key, Found);
    end if;
end Commands;