with
  Ada.Integer_Text_IO,
  Ada.Strings.Unbounded.Text_IO,
  Ada.Text_IO,
  Interfaces.C.Strings,
  POSIX.IO,
  POSIX.Process_Primitives;
with Fork, Wait;

procedure Tiny_BC is
   use type Interfaces.C.int;
   use type POSIX.IO.File_Descriptor;

   type Pipe_Ends is (Read_End, Write_End);
   type Pipe is array (Pipe_Ends) of POSIX.IO.File_Descriptor;

   procedure Oops (Message : in     String;
                   Error   : in     POSIX.Process_Primitives.Exit_Status) is
   begin
      Ada.Text_IO.Put_Line (File => Ada.Text_IO.Standard_Error,
                            Item => "Error: " & Message);
      POSIX.Process_Primitives.Exit_Process (Status => Error);
   end Oops;

   procedure Exec (Program : in     String;
                   Arg_1   : in     String) is
      procedure Exec_1_Argument
        (Command : in Interfaces.C.Strings.chars_ptr;
         Name    : in Interfaces.C.Strings.chars_ptr;
         Arg_1   : in Interfaces.C.Strings.chars_ptr;
         The_End : in Interfaces.C.Strings.chars_ptr);
      pragma Import (C, Exec_1_Argument, "execlp");
      Program_Ptr : Interfaces.C.Strings.chars_ptr :=
                      Interfaces.C.Strings.New_String (Program);
      Arg_1_Ptr   : Interfaces.C.Strings.chars_ptr :=
                      Interfaces.C.Strings.New_String (Arg_1);
   begin
      Exec_1_Argument (Command => Program_Ptr,
                       Name    => Program_Ptr,
                       Arg_1   => Arg_1_Ptr,
                       The_End => Interfaces.C.Strings.Null_Ptr);
   end Exec;

   procedure Be_DC (Input, Output : in     Pipe) is
      File : POSIX.IO.File_Descriptor;
   begin
      File :=
        POSIX.IO.Duplicate_And_Close (File   => Input (Read_End),
                                      Target => POSIX.IO.Standard_Input);
      if File /= POSIX.IO.Standard_Input then
         Oops (Message => "dc: cannot redirect standard input.",
               Error   => 3);
      end if;
      POSIX.IO.Close (File => Input (Read_End));
      POSIX.IO.Close (File => Input (Write_End));

      File :=
        POSIX.IO.Duplicate_And_Close (File   => Output (Write_End),
                                      Target => POSIX.IO.Standard_Output);
      if File /= POSIX.IO.Standard_Output then
         Oops (Message => "dc: cannot redirect standard output.",
               Error   => 4);
      end if;
      POSIX.IO.Close (File => Output (Read_End));
      POSIX.IO.Close (File => Output (Write_End));

      Exec ("dc", "-");
      Oops (Message => "Cannot run dc.",
            Error   => 5);
   end Be_DC;

   procedure Be_BC (Input, Output : in     Pipe) is
      type Syntax_Status is (OK, Error);
      Left, Right : Integer;
      Operation   : Character;
      Syntax      : Syntax_Status;
   begin
      POSIX.IO.Close (File => Input (Write_End));
      POSIX.IO.Close (File => Output (Read_End));

      loop
         Ada.Text_IO.Put ("tinybc: ");
         declare
            Message  : constant String := Ada.Strings.Unbounded.To_String
                         (Ada.Strings.Unbounded.Text_IO.Get_Line);
            Position : Natural;
         begin
            Ada.Integer_Text_IO.Get (From => Message,
                                     Item => Left,
                                     Last => Position);
            Operation := Message (Position + 1);
            Ada.Integer_Text_IO.Get (From => Message (Position + 2 ..
                                                      Message'Length),
                                     Item => Right,
                                     Last => Position);
            if Position = Message'Length then
               Syntax := OK;
            else
               Syntax := Error;
            end if;
         exception
            when others =>
               Syntax := Error;
         end;

         case Syntax is
            when Error =>
               Ada.Text_IO.Put_Line (File => Ada.Text_IO.Standard_Error,
                                     Item => "syntax error");
            when OK =>

             Send_Commands:
               declare
                  use type POSIX.POSIX_String;
                  Written : Natural;
               begin
                  POSIX.IO.NONSTANDARD_Write
                    (File   => Output (Write_End),
                     Buffer =>
                       POSIX.To_POSIX_String (Integer'Image (Left)) &
                       POSIX.LF &
                       POSIX.To_POSIX_String (Integer'Image (Right)) &
                       POSIX.LF &
                       POSIX.POSIX_Character (Operation) &
                       POSIX.LF &
                       "p" &
                       POSIX.LF,
                     Last   => Written);
               exception
                  when others =>
                     Oops (Message => "error writing to dc",
                           Error   => 6);
               end Send_Commands;

             Receive_Result:
               declare
                  Message : POSIX.POSIX_String (1 .. 30);
                  Read    : Natural;
               begin
                  POSIX.IO.NONSTANDARD_Read
                    (File   => Input (Read_End),
                     Buffer => Message,
                     Last   => Read);

                  Ada.Integer_Text_IO.Put (Left, Width => 0);
                  Ada.Text_IO.Put (" " & Operation & " ");
                  Ada.Integer_Text_IO.Put (Right, Width => 0);
                  Ada.Text_IO.Put (" = ");
                  Ada.Text_IO.Put (POSIX.To_String (Message (1 .. Read)));
               end Receive_Result;
         end case;
      end loop;
   end Be_BC;

   To_DC, From_DC : Pipe;
   Process_ID     : Interfaces.C.int;
   Child_Status   : aliased Interfaces.C.int;
begin
   begin
      POSIX.IO.Create_Pipe (Read_End  => To_DC (Read_End),
                            Write_End => To_DC (Write_End));
      POSIX.IO.Create_Pipe (Read_End  => From_DC (Read_End),
                            Write_End => From_DC (Write_End));
   exception
      when others =>
         Oops (Message => "Pipe failed.",
               Error   => 1);
   end;

   Process_ID := Fork;
   if Process_ID = -1 then
      Oops (Message => "Cannot fork.",
            Error   => 2);
   elsif Process_ID = 0 then
      Be_DC (Input  => To_DC,
             Output => From_DC);
   else
      Be_BC (Input  => From_DC,
             Output => To_DC);
      Process_ID := Wait (Child_Status'Access);
   end if;
exception
   when others => null;
end Tiny_BC;

