with AAA.ANSI;
with AAA.Debug;
with AAA.Filesystem;

with Ada.Directories;
with Ada.Strings.Unbounded;

package body AAA.Text_IO is

   -------------------
   -- Put_Paragraph --
   -------------------

   procedure Put_Paragraph (Text        : String;
                            Line_Width  : Line_Widths := Default_Line_Width;
                            Line_Prefix : String := "";
                            Filling     : Filling_Modes := Greedy;
                            File        : Ada.Text_IO.File_Access :=
                              Ada.Text_IO.Standard_Output)
   is
      pragma Unreferenced (Filling);
      use Ada.Text_IO;

      Pos : Integer := Text'First;

      ---------------
      -- Next_Word --
      ---------------

      function Next_Word return String is
      begin
         for I in Pos .. Text'Last loop
            if Text (I) = ' ' then
               return Text (Pos .. I - 1);
            elsif Text (I) = '-' then
               return Text (Pos .. I);
            end if;
         end loop;

         --  No breaker found...
         return Text (Pos .. Text'Last);
      end Next_Word;

      --------------
      -- Put_Line --
      --------------

      procedure Put_Line is
         use Ada.Strings.Unbounded;

         Line : Unbounded_String;
         --  Doing this with fixed strings and ANSI unknown extra lengths is
         --  unnecessary trouble.

         ----------
         -- Used --
         ----------

         function Used return Natural
         is (ANSI.Length (To_String (Line)));

         PPos : constant Integer := Pos;
         --  Initial Pos, to check we had some progress

         ---------
         -- Put --
         ---------

         procedure Put (Word : String) is
         begin
            Append (Line, Word);
         end Put;

      begin
         --  Set prefix, if it fits
         if Line_Prefix'Length < Line_Width - 2 then
            Put (Line_Prefix);
         end if;

         --  Eat words until line is complete
         while Used + ANSI.Length (Next_Word) - 1 <= Line_Width loop
            Put (Next_Word);
            Pos := Pos + Next_Word'Length;

            exit when Used > Line_Width or else Pos > Text'Last;

            --  Advance on spaces
            if Text (Pos) = ' ' then
               Put (" ");
               Pos := Pos + 1;
            end if;
         end loop;

         --  Forcefully break a word if line is still empty. This won't work
         --  with ANSI codes... So don't have too short lines, I guess.
         if Pos = PPos then
            declare
               Remain : constant Positive := Line_Width - Used;
               --  Space for text (without counting the '-')
            begin
               Put (Text (Pos .. Pos + Remain - 1));
               Pos := Pos + Remain;
               Put ("-");
            end;
         end if;

         --  Final dump to file
         Put_Line (File.all, To_String (Line));

         --  Eat spaces that would start the next line:
         while Pos <= Text'Last and then Text (Pos) = ' ' loop
            Pos := Pos + 1;
         end loop;
      end Put_Line;

   begin
      --  Trivial case of empty line:
      if Text = "" then
         New_Line (File.all);
      else
         --  Regular case:
         while Pos <= Text'Last loop
            Put_Line;
         end loop;
      end if;
   end Put_Paragraph;

   --------------------
   -- Put_Paragraphs --
   --------------------

   procedure Put_Paragraphs (Text        : Strings.Vector;
                             Line_Width  : Line_Widths := Default_Line_Width;
                             Line_Prefix : String := "";
                             Filling     : Filling_Modes := Greedy;
                             File        : Ada.Text_IO.File_Access :=
                               Ada.Text_IO.Standard_Output)
   is
   begin
      for Line of Text loop
         Put_Paragraph (Line, Line_Width, Line_Prefix, Filling, File);
      end loop;
   end Put_Paragraphs;

   ------------------
   -- Append_Lines --
   ------------------

   procedure Append_Lines (File       : String;
                           Lines      : Strings.Vector;
                           Backup     : Boolean := True;
                           Backup_Dir : String  := "")
   is
      F : AAA.Text_IO.File := Load (File, Backup, Backup_Dir);
   begin
      F.Lines.Append (Lines);
   end Append_Lines;

   --------------
   -- Finalize --
   --------------

   overriding
   procedure Finalize (This : in out File) is
      use Ada.Text_IO;
      use type Strings.Vector;

      File : File_Type;
   begin
      if This.Lines = This.Orig then
         return;
      end if;

      declare
         Replacer : Filesystem.Replacer :=
                      Filesystem.New_Replacement (This.Name,
                                                  This.Backup,
                                                  This.Backup_Dir);
      begin
         Open (File, Out_File, Replacer.Editable_Name);
         for Line of This.Lines loop
            Put_Line (File, Line);
         end loop;
         Close (File);
         Replacer.Replace;
      end;
   exception
      when E : others =>
         Debug.Put_Exception (E);
   end Finalize;

   -----------
   -- Lines --
   -----------

   function Lines (This : aliased in out File) return access Strings.Vector
   is (This.Lines'Access);

   ----------
   -- Load --
   ----------

   function Load (From       : String;
                  Backup     : Boolean := True;
                  Backup_Dir : String := "")
                  return File
   is
      use Ada.Text_IO;
      F : File_Type;

      Backup_To : constant String :=
                    (if Backup_Dir /= ""
                     then Backup_Dir
                     else Ada.Directories.Containing_Directory (From));
   begin
      return This : File := (Ada.Finalization.Limited_Controlled with
                             Length     => From'Length,
                             Backup_Len => Backup_To'Length,
                             Name       => From,
                             Backup     => Backup,
                             Backup_Dir => Backup_To,
                             Lines      => <>,
                             Orig       => <>)
      do
         Open (F, In_File, From);
         while not End_Of_File (F) loop
            This.Orig.Append (Get_Line (F));
         end loop;
         Close (F);

         This.Lines := This.Orig;
      end return;
   end Load;

end AAA.Text_IO;
