--                                                                    --
--  package                         Copyright (c)  Dmitry A. Kazakov  --
--     Gtk.Tree_Model.Extension_Store              Luebeck            --
--  Implementation                                 Autumn, 2007       --
--                                                                    --
--                                Last revision :  22:45 07 Apr 2016  --
--                                                                    --
--  This  library  is  free software; you can redistribute it and/or  --
--  modify it under the terms of the GNU General Public  License  as  --
--  published by the Free Software Foundation; either version  2  of  --
--  the License, or (at your option) any later version. This library  --
--  is distributed in the hope that it will be useful,  but  WITHOUT  --
--  ANY   WARRANTY;   without   even   the   implied   warranty   of  --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  --
--  General  Public  License  for  more  details.  You  should  have  --
--  received  a  copy  of  the GNU General Public License along with  --
--  this library; if not, write to  the  Free  Software  Foundation,  --
--  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.    --
--                                                                    --
--  As a special exception, if other files instantiate generics from  --
--  this unit, or you link this unit with other files to produce  an  --
--  executable, this unit does not by  itself  cause  the  resulting  --
--  executable to be covered by the GNU General Public License. This  --
--  exception  does not however invalidate any other reasons why the  --
--  executable file might be covered by the GNU Public License.       --
--____________________________________________________________________--

with Ada.Exceptions;  use Ada.Exceptions;
with GLib.Messages;   use GLib.Messages;
with Gtk.Missed;      use Gtk.Missed;
with System;          use System;

with Ada.Unchecked_Deallocation;
with Ada.Unchecked_Conversion;
with GNAT.Traceback.Symbolic;
with System.Address_To_Access_Conversions;

package body Gtk.Tree_Model.Extension_Store is

   GTK_Type : GType := GType_Invalid;

   function Where (Text : String) return String is
   begin
      return " in Gtk.Tree_Model.Extension_Store." & Text;
   end Where;

   procedure Free is
      new Ada.Unchecked_Deallocation
          (  Gtk_Extension_Store_Record'Class,
             Gtk_Extension_Store
          );

   subtype Flat_GInt_Array is GInt_Array (Natural'Range);
   package Address_To_GInt_Array is
      new System.Address_To_Access_Conversions (Flat_GInt_Array);

   function From_Extension
            (  Model : not null access Gtk_Extension_Store_Record;
               Iter  : Gtk_Tree_Iter
            )  return Gtk_Tree_Iter is
   begin
      if Iter = Null_Iter then
         return Null_Iter;
      else
         declare
            Path   : constant Gtk_Tree_Path :=
                     Model.Columns.Get_Path (Iter);
            Result : Gtk_Tree_Iter;
         begin
            if Path = Null_Gtk_Tree_Path then
               Log
               (  GtkAda_Contributions_Domain,
                  Log_Level_Critical,
                  "Invalid iterator" & Where ("From_Extension")
               );
               return Null_Iter;
            else
               Result := Get_Iter (Model.Reference, Path);
               if Result = Null_Iter then
                  Log
                  (  GtkAda_Contributions_Domain,
                     Log_Level_Critical,
                     (  "Path "
                     &  To_String (Path)
                     &  " has no iterator"
                     &  Where ("From_Extension")
                  )  );
               end if;
               Path_Free (Path);
               return Result;
            end if;
         end;
      end if;
   end From_Extension;

   function From_Extension
            (  Model : not null access Gtk_Extension_Store_Record;
               Path  : Gtk_Tree_Path
            )  return Gtk_Tree_Path is
   begin
      if Path = Null_Gtk_Tree_Path then
         return Null_Gtk_Tree_Path;
      else
         return Copy (Path);
      end if;
   end From_Extension;

   procedure Changed
             (  Model : not null access Gtk_Extension_Store_Record;
                Path  : Gtk_Tree_Path;
                Iter  : Gtk_Tree_Iter
             )  is
   begin
      if Get_Depth (Path) > 0 then
         Row_Changed
         (  To_Interface (Model),
            Path,
            Model.Columns.Get_Iter (Path)
         );
      end if;
   end Changed;

   function Children
            (  Model  : not null access Gtk_Extension_Store_Record;
               Parent : Gtk_Tree_Iter
            )  return Gtk_Tree_Iter is
   begin
      return Model.Columns.Children (Parent);
   end Children;

   procedure Deleted
             (  Model : not null access Gtk_Extension_Store_Record;
                Path  : Gtk_Tree_Path
             )  is
   begin
      if Path /= Null_Gtk_Tree_Path and then Get_Depth (Path) > 0 then
         Row_Deleted (To_Interface (Model), Path);
      end if;
   end Deleted;

   procedure Deleting
             (  Model : not null access Gtk_Extension_Store_Record;
                Path  : Gtk_Tree_Path;
                Iter  : Gtk_Tree_Iter
             )  is
   begin
      null;
   end Deleting;

   procedure Erase (Model : access Gtk_Extension_Store_Record'Class) is
      Path : Gtk_Tree_Path;

      procedure Remove (Row : Gtk_Tree_Iter) is
      begin
         for Count in reverse 0..Model.Columns.N_Children (Row) - 1
         loop
            Append_Index (Path, Count);
            Remove (Model.Columns.Nth_Child (Row, Count));
            if Up (Path) then
               null;
            end if;
         end loop;
         if Row /= Null_Iter then
            declare
               Removed : Gtk_Tree_Iter := Row;
            begin
               Model.Deleting (Path, Row);
               Model.Columns.Remove (Removed);
               Row_Deleted (To_Interface (Model), Path);
            end;
         end if;
      end Remove;
   begin
      Gtk_New (Path);
      Remove (Null_Iter);
      Path_Free (Path);
   end Erase;

   procedure Finalize
             (  Model : not null access Gtk_Extension_Store_Record
             )  is
   begin
      Unref (-Model.Reference);
      Model.Columns.Unref;
   end Finalize;

   function Get_Column_Type
            (  Model : not null access Gtk_Extension_Store_Record;
               Index : GInt
            )  return GType is
   begin
      if Index < Model.Offset then
         return Get_Column_Type (Model.Reference, Index);
      else
         return Model.Columns.Get_Column_Type (Index - Model.Offset);
      end if;
   end Get_Column_Type;

   function Get_Extension_Types
            (  Model : not null access Gtk_Extension_Store_Record
            )  return GType_Array is
      Length : constant GInt := Model.Columns.Get_N_Columns;
   begin
      if Length = 0 then
         return (1..0 => GType_Invalid);
      else
         declare
            Result : GType_Array
                     (  GUInt (Model.Offset)
                     .. GUInt (Model.Offset + Length - 1)
                     );
         begin
            for Index in Result'Range loop
               Result (Index) :=
                  Model.Columns.Get_Column_Type
                  (  GInt (Index) - Model.Offset
                  );
            end loop;
            return Result;
         end;
      end if;
   end Get_Extension_Types;

   function Get_Flags
            (  Model : not null access Gtk_Extension_Store_Record
            )  return Tree_Model_Flags is
   begin
      return Get_Flags (Model.Reference);
   end Get_Flags;

   function Get_Iter
            (  Model : not null access Gtk_Extension_Store_Record;
               Path  : Gtk_Tree_Path
            )  return Gtk_Tree_Iter is
   begin
      if Path = Null_Gtk_Tree_Path or else Get_Depth (Path) = 0 then
         return Null_Iter;
      else
         return Model.Columns.Get_Iter (Path);
      end if;
   end Get_Iter;

   function Get_N_Columns
            (  Model : not null access Gtk_Extension_Store_Record
            )  return GInt is
   begin
      return Model.Columns.Get_N_Columns + Model.Offset;
   end Get_N_Columns;

   function Get_Path
            (  Model : not null access Gtk_Extension_Store_Record;
               Iter  : Gtk_Tree_Iter
            )  return Gtk_Tree_Path is
   begin
      if Iter = Null_Iter then
         return Null_Gtk_Tree_Path;
      else
         return Model.Columns.Get_Path (Iter);
      end if;
   end Get_Path;

   function Get_Reference
            (  Model : not null access Gtk_Extension_Store_Record
            )  return Gtk_Tree_Model is
   begin
      return Model.Reference;
   end Get_Reference;

   procedure Get_Value
             (  Model  : not null access Gtk_Extension_Store_Record;
                Iter   : Gtk_Tree_Iter;
                Column : GInt;
                Value  : out GValue
             )  is
   begin
      if Iter = Null_Iter then
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Null extension model iterator"
            &  GInt'Image (Column)
            &  Where ("Get_Value")
         )  );
      elsif Column < Model.Offset then
         declare
            Row : constant Gtk_Tree_Iter :=
                  From_Extension (Model, Iter);
         begin
            if Row = Null_Iter then
               Log
               (  GtkAda_Contributions_Domain,
                  Log_Level_Critical,
                  (  "Null iterator of the reference column"
                  &  GInt'Image (Column)
                  &  Where ("Get_Value")
               )  );
               Init (Value, GType_Invalid);
            else
               Get_Value (Model.Reference, Row, Column, Value);
            end if;
         end;
      else
         Model.Columns.Get_Value (Iter, Column - Model.Offset, Value);
      end if;
   end Get_Value;

   procedure Gtk_New
             (  Model     : out Gtk_Extension_Store;
                Reference : not null access
                            Gtk_Root_Tree_Model_Record'Class;
                Types     : GType_Array
             )  is
   begin
      Model := new Gtk_Extension_Store_Record;
      Initialize (Model, Reference, Types);
   exception
      when others =>
         Free (Model);
         raise;
   end Gtk_New;

   procedure Gtk_New
             (  Model : out Gtk_Extension_Store;
                Types : GType_Array
             )  is
   begin
      Model := new Gtk_Extension_Store_Record;
      Gtk.Tree_Model.Extension_Store.Initialize (Model, Types);
   exception
      when others =>
         Free (Model);
         raise;
   end Gtk_New;

   function Has_Child
            (  Model : not null access Gtk_Extension_Store_Record;
               Iter  : Gtk_Tree_Iter
            )  return Boolean is
   begin
      return Model.Columns.Has_Child (Iter);
   end Has_Child;

   procedure Initial_Sync
             (  Model      : not null access
                             Gtk_Extension_Store_Record'Class;
                Ref_Parent : Gtk_Tree_Iter;
                Ext_Parent : Gtk_Tree_Iter
             )  is
      Ref_Child : Gtk_Tree_Iter;
      Ext_Child : Gtk_Tree_Iter;
      Position  : GInt := 0;
   begin
      if Ref_Parent = Null_Iter then
         Ref_Child := Get_Iter_First (Model.Reference);
      else
         Ref_Child := Children (Model.Reference, Ref_Parent);
      end if;
      while Ref_Child /= Null_Iter loop
         Model.Columns.Insert (Ext_Child, Ext_Parent, Position);
         Position := Position + 1;
         Initial_Sync (Model, Ref_Child, Ext_Child);
         Next (Model.Reference, Ref_Child);
      end loop;
   end Initial_Sync;

   procedure Initialize
             (  Model     : not null access
                            Gtk_Extension_Store_Record'Class;
                Reference : not null access
                            Gtk_Root_Tree_Model_Record'Class;
                Types     : GType_Array
             )  is
   begin
      Initialize (Model, Types);
      Set_Reference (Model, Reference);
   end Initialize;

   procedure Initialize
             (  Model : not null access
                        Gtk_Extension_Store_Record'Class;
                Types : GType_Array
             )  is
   begin
      if GTK_Type = GType_Invalid then
         GTK_Type := Register ("GtkExtensionStore");
      end if;
      Initialize (Model, GTK_Type);
      Gtk.Tree_Store.Gtk_New (Model.Columns, Types);
      Tree_Handlers.Connect
      (  Model.Columns,
         "rows-reordered",
         On_Rows_Reordered'Access,
         Model.all'Access
      );
   end Initialize;

   procedure Inserted
             (  Model : not null access Gtk_Extension_Store_Record;
                Path  : Gtk_Tree_Path;
                Iter  : Gtk_Tree_Iter
             )  is
   begin
      if Path /= Null_Gtk_Tree_Path and then Get_Depth (Path) > 0 then
         Row_Inserted (To_Interface (Model), Path, Iter);
      end if;
   end Inserted;

   procedure Next
             (  Model : not null access Gtk_Extension_Store_Record;
                Iter  : in out Gtk_Tree_Iter
             )  is
   begin
      Model.Columns.Next (Iter);
   end Next;

   function Nth_Child
            (  Model  : not null access Gtk_Extension_Store_Record;
               Parent : Gtk_Tree_Iter;
               N      : GInt
            )  return Gtk_Tree_Iter is
   begin
      return Model.Columns.Nth_Child (Parent, N);
   end Nth_Child;

   function N_Children
            (  Model : not null access Gtk_Extension_Store_Record;
               Iter  : Gtk_Tree_Iter := Null_Iter
            )  return GInt is
   begin
      return Model.Columns.N_Children (Iter);
   end N_Children;

   procedure On_Changed_Row
             (  Reference  : access Gtk_Root_Tree_Model_Record'Class;
                Params     : GValues;
                Model      : Gtk_Extension_Store
             )  is
      Path : constant Gtk_Tree_Path :=
             Convert (Get_Address (Nth (Params, 1)));
      Row  : Gtk_Tree_Iter;
   begin
      if Path /= Null_Gtk_Tree_Path and then Get_Depth (Path) > 0 then
         Row := Model.Columns.Get_Iter (Path);
         if Row = Null_Iter then
            On_Row_Inserted (Reference, Params, Model);
         end if;
         Changed (Model, Path, Row);
      end if;
   exception
      when Error : others =>
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Fault: "
            &  Exception_Information (Error)
            &  Where ("On_Changed_Row")
         )  );
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            GNAT.Traceback.Symbolic.Symbolic_Traceback (Error)
         );
   end On_Changed_Row;

   procedure On_Deleted_Row
             (  Reference : access Gtk_Root_Tree_Model_Record'Class;
                Params    : GValues;
                Model     : Gtk_Extension_Store
             )  is
      Path : constant Gtk_Tree_Path :=
             Convert (Get_Address (Nth (Params, 1)));
      Row  : Gtk_Tree_Iter;
   begin
      if Path /= Null_Gtk_Tree_Path and then Get_Depth (Path) > 0 then
         Row := Model.Columns.Get_Iter (Path);
         if Row /= Null_Iter then
            Deleting (Model, Path, Row);
            Model.Columns.Remove (Row);
            Row_Deleted (To_Interface (Model), Path);
         end if;
      end if;
   exception
      when Error : others =>
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Fault: "
            &  Exception_Information (Error)
            &  Where ("On_Deleted_Row")
         )  );
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            GNAT.Traceback.Symbolic.Symbolic_Traceback (Error)
         );
   end On_Deleted_Row;

   procedure On_Row_Has_Child_Toggled
             (  Reference  : access Gtk_Root_Tree_Model_Record'Class;
                Params     : GValues;
                Model      : Gtk_Extension_Store
             )  is
      Path : constant Gtk_Tree_Path :=
             Convert (Get_Address (Nth (Params, 1)));
   begin
      Row_Has_Child_Toggled
      (  To_Interface (Model),
         Path,
         Model.Columns.Get_Iter (Path)
      );
   exception
      when Error : others =>
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Fault: "
            &  Exception_Information (Error)
            &  Where ("On_Row_Has_Child_Toggled")
         )  );
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            GNAT.Traceback.Symbolic.Symbolic_Traceback (Error)
         );
   end On_Row_Has_Child_Toggled;

   procedure On_Row_Inserted
             (  Reference  : access Gtk_Root_Tree_Model_Record'Class;
                Params     : GValues;
                Model      : Gtk_Extension_Store
             )  is
      Path : constant Gtk_Tree_Path :=
                Convert (Get_Address  (Nth (Params, 1)));
      No   : constant GInt :=
                GInt'Max
                (  0,
                   Get_Row_No (To_Interface (Model.Columns), Path)
                );
      This : constant Gtk_Tree_Path := Copy (Path);
      Row  : Gtk_Tree_Iter;
   begin
      if Up (This) and then Get_Depth (This) > 0 then
         Model.Columns.Insert
         (  Row,
            Model.Columns.Get_Iter (This),
            No
         );
      else
         Model.Columns.Insert (Row, Null_Iter, No);
      end if;
      if This /= Null_Gtk_Tree_Path then
         Path_Free (This);
      end if;
      Inserted (Model, Path, Row);
   exception
      when Error : others =>
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Fault: "
            &  Exception_Information (Error)
            &  Where ("On_Row_Inserted")
         )  );
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            GNAT.Traceback.Symbolic.Symbolic_Traceback (Error)
         );
   end On_Row_Inserted;

   procedure On_Rows_Reordered
             (  Reference  : access Gtk_Root_Tree_Model_Record'Class;
                Params     : GValues;
                Model      : Gtk_Extension_Store
             )  is
      use Address_To_GInt_Array;
      Path    : constant Gtk_Tree_Path :=
                   Convert (Get_Address (Nth (Params, 1)));
      Indices : GInt_Array renames
                   To_Pointer (Get_Address (Nth (Params, 3))).all;
   begin
      Model.Columns.Reorder
      (  Model.Columns.Get_Iter (Path),
         Indices
      );
   exception
      when Error : others =>
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            (  "Fault: "
            &  Exception_Information (Error)
            &  Where ("On_Rows_Reordered")
         )  );
         Log
         (  GtkAda_Contributions_Domain,
            Log_Level_Critical,
            GNAT.Traceback.Symbolic.Symbolic_Traceback (Error)
         );
   end On_Rows_Reordered;

   function Parent
            (  Model : not null access Gtk_Extension_Store_Record;
               Child : Gtk_Tree_Iter
            )  return Gtk_Tree_Iter is
   begin
      return Model.Columns.Parent (Child);
   end Parent;

   procedure Previous
             (  Model : not null access Gtk_Extension_Store_Record;
                Iter  : in out Gtk_Tree_Iter
             )  is
   begin
      Model.Columns.Previous (Iter);
   end Previous;

   procedure Ref_Node
             (  Model : not null access Gtk_Extension_Store_Record;
                Iter  : Gtk_Tree_Iter
             )  is
   begin
      Ref_Node (Model.Reference, From_Extension (Model, Iter));
      Ref_Node (Model.Reference, Iter);
   end Ref_Node;

   procedure Set_Null_Reference
             (  Model : not null access Gtk_Extension_Store_Record
             )  is
   begin
      if Model.Reference /= Null_Gtk_Tree_Model then
         -- Disconnect callbacks
         for Index in Model.Callbacks'Range loop
            Disconnect (-Model.Reference, Model.Callbacks (Index));
         end loop;
         Erase (Model);
         Unref (-Model.Reference);
         Model.Reference := Null_Gtk_Tree_Model;
      end if;
   end Set_Null_Reference;

   procedure Set_Reference
             (  Model     : not null access Gtk_Extension_Store_Record;
                Reference : not null access
                            Gtk_Root_Tree_Model_Record'Class
             )  is
   begin
      Ref (Reference);
      Set_Null_Reference (Model);
      Model.Reference := To_Interface (Reference);
      Model.Offset := Get_N_Columns (Model.Reference);
      Initial_Sync (Model, Null_Iter, Null_Iter);
      Model.Callbacks (Changed) :=
         Tree_Handlers.Connect
         (  Reference,
            "row-changed",
            On_Changed_Row'Access,
            Model.all'Access,
            True
         );
      Model.Callbacks (Deleted) :=
         Tree_Handlers.Connect
         (  Reference,
            "row-deleted",
            On_Deleted_Row'Access,
            Model.all'Access,
            True
         );
      Model.Callbacks (Inserted) :=
         Tree_Handlers.Connect
         (  Reference,
            "row-inserted",
            On_Row_Inserted'Access,
            Model.all'Access,
            True
         );
   exception
      when others =>
         Model.Reference := Null_Gtk_Tree_Model;
         Unref (Reference);
         raise;
   end Set_Reference;

   procedure Unref_Node
             (  Model : not null access Gtk_Extension_Store_Record;
                Iter  : Gtk_Tree_Iter
             )  is
   begin
      Unref_Node (Model.Reference, From_Extension (Model, Iter));
      Model.Columns.Unref_Node (Iter);
   end Unref_Node;

   function To_Extension
            (  Model : not null access Gtk_Extension_Store_Record;
               Iter  : Gtk_Tree_Iter
            )  return Gtk_Tree_Iter is
   begin
      if Iter = Null_Iter then
         return Null_Iter;
      else
         declare
            Path   : constant Gtk_Tree_Path :=
                     Get_Path (Model.Reference, Iter);
            Result : Gtk_Tree_Iter := Null_Iter;
         begin
            if Path /= Null_Gtk_Tree_Path then
               if Get_Depth (Path) = 0 then
                  Result := Null_Iter;
               else
                  Result := Model.Columns.Get_Iter (Path);
               end if;
               Path_Free (Path);
            end if;
            return Result;
         end;
      end if;
   end To_Extension;

   function To_Extension
            (  Model : not null access Gtk_Extension_Store_Record;
               Path  : Gtk_Tree_Path
            )  return Gtk_Tree_Path is
   begin
      if Path = Null_Gtk_Tree_Path then
         return Null_Gtk_Tree_Path;
      else
         return Copy (Path);
      end if;
   end To_Extension;

   procedure Set_Extension
             (  Model  : not null access
                         Gtk_Extension_Store_Record'Class;
                Iter   : Gtk_Tree_Iter;
                Column : Positive;
                Value  : Boolean
             )  is
   begin
      Model.Columns.Set (Iter, GInt (Column) - 1, Value);
   end Set_Extension;

   procedure Set_Extension
             (  Model  : not null access
                         Gtk_Extension_Store_Record'Class;
                Iter   : Gtk_Tree_Iter;
                Column : Positive;
                Value  : GInt
             )  is
   begin
      Model.Columns.Set (Iter, GInt (Column) - 1, Value);
   end Set_Extension;

   procedure Set_Extension
             (  Model  : not null access
                         Gtk_Extension_Store_Record'Class;
                Iter   : Gtk_Tree_Iter;
                Column : Positive;
                Value  : UTF8_String
             )  is
   begin
      Model.Columns.Set (Iter, GInt (Column) - 1, Value);
   end Set_Extension;

   procedure Set_Extension
             (  Model  : not null access
                         Gtk_Extension_Store_Record'Class;
                Iter   : Gtk_Tree_Iter;
                Column : Positive;
                Value  : GValue
             )  is
   begin
      Model.Columns.Set_Value (Iter, GInt (Column) - 1, Value);
   end Set_Extension;

end Gtk.Tree_Model.Extension_Store;
