with Io;
with Table_Formatter;
with Directory_Tools;
with List_Generic;
with Bounded_String;
with Direct_Io;
with Log;
with Profile;
with Transport_Name;
with Time_Utilities;
package body World_Space is

    package Space_Io is new Direct_Io (Space_Info);

    function Name_Of_World (In_Item : Space_Info) return String is
    begin
        return Bs.Image (In_Item.Name);
    end Name_Of_World;

    function Size_Of_World (In_Item : Space_Info) return Long_Integer is
    begin
        return In_Item.Size_In_Pages;
    end Size_Of_World;

    function Volume_Of_World (In_Item : Space_Info) return Volume is
    begin
        return In_Item.Volume_Id;
    end Volume_Of_World;

    procedure Collect_Info (On_Worlds_In : String := "!";
                            Data_File : String := "Space_Data") is
        use Directory_Tools;
        Object_Iter : Object.Iterator :=
           Naming.Resolution (On_Worlds_In & "??'c(world)");
        Output_File : Space_Io.File_Type;
        This_World : Object.Handle;
        World_Iter : Object.Iterator;
        World_Total : Long_Integer := 0;
        World_Entry : Space_Info;
    begin
        Space_Io.Create (Output_File, Space_Io.Out_File, Data_File);
        while not Object.Done (Object_Iter) loop
            This_World := Object.Value (Object_Iter);
            World_Iter := Naming.Resolution
                             (Naming.Full_Name (This_World) & "?");
            World_Total := 0;
            while not Object.Done (World_Iter) loop
                World_Total := World_Total + Statistics.Total_Size
                                                (Object.Value (World_Iter));
                Object.Next (World_Iter);
            end loop;  
            declare
                World_Name : constant String := Naming.Full_Name (This_World);
            begin
                if World_Name'Length <= Max_Name_Length then
                    Bs.Copy (World_Entry.Name, World_Name);
                else
                    Bs.Copy (World_Entry.Name,
                             World_Name (World_Name'Last - 255 ..
                                            World_Name'Last));
                end if;  
                World_Entry.Size_In_Pages := World_Total / (8 * 1024);
                World_Entry.Volume_Id := Library_Object.Volume (This_World);
                Space_Io.Write (File => Output_File, Item => World_Entry);
            end;
            Object.Next (Object_Iter);
        end loop;
        Space_Io.Close (Output_File);
    exception
        when Space_Io.Name_Error =>
            Log.Put_Line ("Error creating data file => " & Data_File,
                          Profile.Error_Msg);
            Log.Put_Line ("Collect_Info quitting after errors",
                          Profile.Negative_Msg);
    end Collect_Info;

    function Read_Data (From_File : String := "Space_Data") return Iterator is
        Input_File : Space_Io.File_Type;
        Result : Iterator := Nil;
        Info_Entry : Space_Info;
    begin
        Space_Io.Open (Input_File, Space_Io.In_File, From_File);
        while not Space_Io.End_Of_File (Input_File) loop
            Space_Io.Read (Input_File, Info_Entry);
            Result := Make (Info_Entry, Result);
        end loop;  
        return Result;
    exception
        when Space_Io.Name_Error =>
            Log.Put_Line ("Name error opening data file => " & From_File,
                          Profile.Error_Msg);
        when Space_Io.Use_Error =>
            Log.Put_Line ("Use error opening data file => " & From_File,
                          Profile.Error_Msg);
    end Read_Data;

    function Done (Iter : Iterator) return Boolean is
    begin
        return Is_Empty (Iter);
    end Done;

    function Value (Iter : Iterator) return Space_Info is
    begin
        return First (Iter);
    end Value;

    procedure Next (Iter : in out Iterator) is
    begin
        Iter := Rest (Iter);
    end Next;

    procedure Report (From_File : String := "Space_Data";
                      Include_Worlds_On : Volume_Filter :=
                         World_Space.All_Volumes;
                      Sort_By : Sort_Kind := World_Space.By_Volume;
                      Output : String := "") is

        use Directory_Tools;
        Iter : Iterator := Read_Data (From_File => From_File);  
        Column_Count : Natural := 2;
        Output_File : Io.File_Type;
        type Sum_Array is array (1 .. 4) of Long_Integer;
        Sums : Sum_Array := (others => 0);
        Total_Total : Long_Integer := 0;
    begin
        if Output = "" then
            Output_File := Io.Current_Output;
        else
            begin
                Io.Create (Output_File, Io.Out_File, Output);  
            exception
                when Io.Name_Error =>
                    Log.Put_Line ("ERROR - Output file could not be created",
                                  Profile.Negative_Msg);
                    return;
            end;
        end if;  
        for I in Volume loop
            if Include_Worlds_On (I) then
                Column_Count := Column_Count + 1;
            end if;
        end loop;
        declare
            package Table is new Table_Formatter (Column_Count);
            This_Entry : Space_Info;
        begin  
            Table.Header ("World Name");
            for I in Volume loop
                if Include_Worlds_On (I) then
                    Table.Header ("V" & Natural'Image (I));
                end if;
            end loop;  
            Table.Header ("World Context");
            while not Done (Iter) loop
                This_Entry := Value (Iter);
                Table.Item (Naming.Simple_Name (Name_Of_World (This_Entry)));
                for I in Volume loop
                    if Include_Worlds_On (I) then
                        if I = Volume_Of_World (This_Entry) then
                            Table.Item (Long_Integer'Image
                                           (Size_Of_World (This_Entry)));
                            Sums (I) := Sums (I) + Size_Of_World (This_Entry);
                        else
                            Table.Item ("");
                        end if;
                    end if;
                end loop;  
                Table.Item (Naming.Prefix (Name_Of_World (This_Entry)));
                Next (Iter);
            end loop;  
            Table.Item ("Totals");
            for I in Volume loop
                if Include_Worlds_On (I) then
                    Table.Item (Long_Integer'Image (Sums (I)));
                    Total_Total := Total_Total + Sums (I);
                end if;
            end loop;
            Table.Item ("Total for all volumes = " &
                        Long_Integer'Image (Total_Total));
            case Sort_By is
                when By_Volume =>
                    declare
                        Sort_List : Table.Field_List (1 .. Column_Count - 2);
                    begin
                        for I in Sort_List'First .. Sort_List'Last loop
                            Sort_List (I) := Column_Count - I;
                        end loop;
                        Table.Sort (Sort_List);
                    end;
                when By_Name =>
                    Table.Sort (Column_Count);
                when By_Volume_And_Name =>
                    declare
                        Sort_List : Table.Field_List (1 .. Column_Count - 1);
                    begin
                        for I in Sort_List'First .. Sort_List'Last - 1 loop
                            Sort_List (I) := Column_Count - I;
                        end loop;
                        Sort_List (Sort_List'Last) := Column_Count;
                        Table.Sort (Sort_List);
                    end;
                when By_Name_And_Volume =>
                    declare
                        Sort_List : Table.Field_List (1 .. Column_Count - 1);
                    begin
                        for I in Sort_List'First + 1 .. Sort_List'Last loop
                            Sort_List (I) := Column_Count - I + 1;
                        end loop;
                        Sort_List (Sort_List'First) := Column_Count;
                        Table.Sort (Sort_List);
                    end;
            end case;
            Io.Put_Line (Output_File,
                         "World Space Report generated on " &
                            Time_Utilities.Image (Time_Utilities.Get_Time));
            Io.Put_Line ("From data file => " &
                         Naming.Full_Name (Naming.Resolution (From_File)));
            Io.Put_Line ("Data collected on => " &
                         Time_Utilities.Image
                            (Time_Utilities.Convert_Time
                                (Statistics.Time_Of_Last_Update
                                    (Naming.Resolution (From_File)))));

            Io.Put_Line (Output_File,
                         "Machine Name => " &
                            Transport_Name.Local_Host_Name ("tcp/ip"));
            Io.New_Line;
            Table.Display (Output_File);
        end;

    end Report;
end World_Space;