with
     openGL.Camera,
     openGL.Model.billboard.textured,
     openGL.texture_Set,

     ada.unchecked_Deallocation;

package body openGL.Impostor
is
   ---------
   --- Forge
   --

   procedure destroy (Self : in out Item)
   is
      use openGL.Visual,
          Model,
          Texture;

      the_Model   : Model.view     := Self.Visual.Model;
      the_Texture : Texture.Object := Model.billboard.textured.view (the_Model).Texture;
   begin
      free (the_Texture);
      free (the_Model);
      free (Self.Visual);
   end destroy;


   procedure free (Self : in out View)
   is
      procedure deallocate is new ada.unchecked_Deallocation (Item'Class, View);
   begin
      if Self /= null then
         destroy (Self.all);
         deallocate (Self);
      end if;
   end free;



   --------------
   --- Attributes
   --

   procedure Visual_is (Self : in out Item;   Now : in openGL.Visual.view)
   is
   begin
      Self.Visual := Now;
   end Visual_is;


   function Visual (Self : access Item) return openGL.Visual.view
   is
   begin
      return Self.Visual;
   end Visual;



   function get_Target (Self : in Item) return openGL.Visual.view
   is
   begin
      return Self.Target;
   end get_Target;


   procedure set_Target (Self : in out Item;   Target : in openGL.Visual.view)
   is
      use type openGL.Visual.view;

      Width  : constant Real := Target.Model.Bounds.Ball * 2.00;
      Height : constant Real := Target.Model.Bounds.Ball * 2.00;

   begin
      if Self.Visual = null
      then
         Self.Visual := new openGL.Visual.item;
      end if;

      Self.Target     := Target;
      Self.is_Terrain := Target.is_Terrain;

      Self.Visual.Model_is (Model.billboard.textured.Forge.new_Billboard (Size    => (Width  => Width,
                                                                                      Height => Height),
                                                                          Plane   => Model.billboard.xy,
                                                                          Texture => null_Asset,
                                                                          texture_Details => texture_Set.to_Set ([1 => null_Asset])).all'Access);
      Self.Visual.Transform_is (Target.Transform);
      --  Self.Visual.model_Transform_is (Target.model_Transform);
   end set_Target;



   function target_camera_Distance (Self : in Item'Class) return Real
   is
   begin
      return Self.target_camera_Distance;
   end target_camera_Distance;


   function is_Valid (Self : in Item'Class) return Boolean
   is
   begin
      return Self.is_Valid;
   end is_Valid;


   function never_Updated (Self : in Item'Class) return Boolean
   is
   begin
      return Self.never_Updated;
   end never_Updated;


   function frame_Count_since_last_update (Self : in Item'Class) return Natural
   is
   begin
      return Natural (Self.freshen_Count);
   end frame_Count_since_last_update;


   function face_Count (Self : in Item) return Natural
   is
      pragma unreferenced (Self);
   begin
      return 1;
   end face_Count;


   procedure set_Alpha (Self : in out Item;   Alpha : in Real)
   is
   begin
      null;   -- TODO
   end set_Alpha;


   function Bounds (Self : in Item) return openGL.Bounds
   is
      pragma Unreferenced (Self);
   begin
      return (others => <>);     -- TODO
   end Bounds;
   pragma Unreferenced (Bounds);


   function is_Transparent (Self : in Item) return Boolean
   is
      pragma unreferenced (Self);
   begin
      return True;
   end is_Transparent;


   -- Update trigger configuration.
   --

   procedure set_freshen_count_update_trigger_Mod (Self : in out Item;   To : in Positive)
   is
   begin
      Self.freshen_count_update_trigger_Mod := Counter (To);
   end set_freshen_count_update_trigger_Mod;


   function get_freshen_count_update_trigger_Mod (Self : in Item) return Positive
   is
   begin
      return Positive (Self.freshen_count_update_trigger_Mod);
   end get_freshen_count_update_trigger_Mod;



   procedure set_size_update_trigger_Delta (Self : in out Item;   To : in Positive)
   is
   begin
      Self.size_update_trigger_Delta := gl.glSizeI (To);
   end set_size_update_trigger_Delta;


   function get_size_update_trigger_Delta (Self : in Item) return Positive
   is
   begin
      return Positive (Self.size_update_trigger_Delta);
   end get_size_update_trigger_Delta;



   function general_Update_required (Self : access Item;   the_Camera_Site  : in Vector_3;
                                                           the_pixel_Region : in pixel_Region) return Boolean
   is
      pragma unreferenced (the_pixel_Region);

      use linear_Algebra_3d;
      use type gl.GLsizei;

      Camera_has_moved : constant Boolean :=  the_Camera_Site                         /= Self.prior_camera_Position;
      Target_has_moved : constant Boolean :=  get_Translation (Self.Target.Transform) /= Self.prior_target_Position;
      --  Target_has_moved : constant Boolean :=  get_Translation (Self.Target.model_Transform) /= Self.prior_target_Position;

   begin
      Self.freshen_Count := Self.freshen_Count + 1;

      if Self.freshen_Count > Self.freshen_count_update_trigger_Mod
      then
         return True;
      end if;

      if         Camera_has_moved
        and then abs (Angle (the_Camera_Site,
                             Self.prior_target_Position,
                             Self.prior_camera_Position)) > to_Radians (15.0)
      then
         return True;
      end if;

      if         Target_has_moved
        and then abs (Angle (get_Translation (Self.Target.Transform),
        --  and then abs (Angle (get_Translation (Self.Target.model_Transform),
                             Self.prior_camera_Position,
                             Self.prior_target_Position)) > to_Radians (15.0)
      then
         return True;
      end if;


      if         Self.prior_pixel_Region.Width  >  40                -- Ignore target rotation triggered updates when target is small on screen.
        and then Self.prior_pixel_Region.Height >  40                --
        and then Self.prior_target_Rotation    /= get_Rotation (Self.Target.Transform)
        --  and then Self.prior_target_Rotation    /= get_Rotation (Self.Target.model_Transform)
      then
         return True;
      end if;

      return False;
   end general_Update_required;



   function size_Update_required (Self : access Item;   the_pixel_Region : in pixel_Region) return Boolean
   is
      use      GL;
      use type gl.GLsizei;
   begin
      return     abs (the_pixel_Region.Width  - Self.prior_Width_Pixels)  > Self.size_update_trigger_Delta
             or  abs (the_pixel_Region.Height - Self.prior_Height_pixels) > Self.size_update_trigger_Delta;
   end size_Update_required;



   function get_pixel_Region (Self : access Item'Class;   camera_Spin                 : in Matrix_3x3;
                                                          camera_Site                 : in Vector_3;
                                                          camera_projection_Transform : in Matrix_4x4;
                                                          camera_Viewport             : in linear_Algebra_3d.Rectangle) return pixel_Region
   is
      use linear_Algebra_3D;

      --  target_Centre           : constant Vector_3 := camera_Spin * (  get_Translation (Self.Target.model_Transform)
      target_Centre           : constant Vector_3 := camera_Spin * (  get_Translation (Self.Target.Transform)
                                                                    - camera_Site);

      target_lower_Left       : constant Vector_3 := target_Centre - [Self.Target.Model.Bounds.Ball,
                                                                      Self.Target.Model.Bounds.Ball,
                                                                      0.0];

      target_Centre_proj      : constant Vector_4 := target_Centre     * camera_projection_Transform;
      target_Lower_Left_proj  : constant Vector_4 := target_lower_Left * camera_projection_Transform;

      target_Centre_norm      : constant Vector_3 := [target_Centre_proj (1)     / target_Centre_proj (4),
                                                      target_Centre_proj (2)     / target_Centre_proj (4),
                                                      target_Centre_proj (3)     / target_Centre_proj (4)];

      target_Lower_Left_norm  : constant Vector_3 := [target_Lower_Left_proj (1) / target_Lower_Left_proj (4),
                                                      target_Lower_Left_proj (2) / target_Lower_Left_proj (4),
                                                      target_Lower_Left_proj (3) / target_Lower_Left_proj (4)];

      target_Centre_norm_0to1 : constant Vector_3 := [target_Centre_norm (1)     * 0.5 + 0.5,
                                                      target_Centre_norm (2)     * 0.5 + 0.5,
                                                      target_Centre_norm (3)     * 0.5 + 0.5];

      target_Lower_Left_norm_0to1 : constant Vector_3 := [target_Lower_Left_norm (1) * 0.5 + 0.5,
                                                          target_Lower_Left_norm (2) * 0.5 + 0.5,
                                                          target_Lower_Left_norm (3) * 0.5 + 0.5];

      viewport_Width  : constant Integer    := camera_Viewport.Max (1) - camera_Viewport.Min (1) + 1;
      viewport_Height : constant Integer    := camera_Viewport.Max (2) - camera_Viewport.Min (2) + 1;

      Width           : constant Real       := 2.0  *  Real (viewport_Width) * (  target_Centre_norm_0to1     (1)
                                                                                - target_Lower_Left_norm_0to1 (1));

      Width_pixels   : constant gl.glSizei := gl.glSizei (  Integer (Real (viewport_Width) * target_Lower_Left_norm_0to1 (1) + Width)
                                                          - Integer (Real (viewport_Width) * target_Lower_Left_norm_0to1 (1))
                                                          + 1);

      Height         : constant Real       := 2.0  *  Real (viewport_Height) * (  target_Centre_norm_0to1     (2)
                                                                                - target_Lower_Left_norm_0to1 (2));

      Height_pixels  : constant gl.glSizei := gl.glSizei (  Integer (Real (viewport_Height) * target_Lower_Left_norm_0to1 (2) + Height)
                                                          - Integer (Real (viewport_Height) * target_Lower_Left_norm_0to1 (2))
                                                          + 1);
      use type gl.GLsizei;

   begin
      Self.all.target_camera_Distance := abs (target_Centre);   -- NB: Cache distance from camera to target.

      return (X      => gl.glInt (target_Lower_Left_norm_0to1 (1) * Real (Viewport_Width))  - 0,
              Y      => gl.glInt (target_Lower_Left_norm_0to1 (2) * Real (viewport_Height)) - 0,
              Width  => Width_pixels + 0,
              Height => Height_pixels + 0);
   end get_pixel_Region;


   --------------
   --  Operations
   --

   procedure update (Self : in out Item;   the_Camera   : access Camera.item'Class;
                                           texture_Pool : in     texture.Pool_view)
   is
      pragma unreferenced (the_Camera, texture_Pool);
      use openGL.Visual;

--        Width_size     : constant openGL.texture.Size := to_Size (Natural (Self.current_Width_pixels));
--        Height_size    : constant openGL.texture.Size := to_Size (Natural (Self.current_Height_pixels));

--        texture_Width  : constant gl.glSizei          := power_of_2_Ceiling (Natural (Self.current_Width_pixels ));
--        texture_Height : constant gl.glSizei          := power_of_2_Ceiling (Natural (Self.current_Height_pixels));

      the_Model      : constant Model.billboard.textured.view := Model.billboard.textured.view (Self.Visual.Model);
--        GL_Error       :          Boolean;

   begin
      Self.Visual.all := Self.Target.all;
      Self.Visual.Model_is (the_Model.all'Access);
   end update;


end openGL.Impostor;
