with Chests.Ring_Buffers;
with USB.Device.HID.Keyboard;

package body Click is
   ----------------
   --  DEBOUNCE  --
   ----------------

   --  Ideally, in a separate package.

   --  should be [], but not fixed yet in GCC 11.
   Current_Status : Key_Matrix := [others => [others => False]];
   New_Status : Key_Matrix := [others => [others => False]];
   Since : Natural := 0;
   --   Nb_Bounce : Natural := 5;

   function Update (NewS : Key_Matrix) return Boolean is
   begin
      --  The new state is the same as the current stable state => Do nothing.
      if Current_Status = NewS then
         Since := 0;
         return False;
      end if;

      if New_Status /= NewS then
         --  The new state differs from the previous
         --  new state (bouncing) => reset
         New_Status := NewS;
         Since := 1;
      else
         --  The new state hasn't changed since last
         --  update => towards stabilization.
         Since := Since + 1;
      end if;

      if Since > Nb_Bounce then
         declare
            Tmp : constant Key_Matrix := Current_Status;
         begin
            --  New state has been stable enough.
            --  Latch it and notifies caller.
            Current_Status := New_Status;
            New_Status := Tmp;
            Since := 0;
         end;

         return True;
      else
         --  Not there yet
         return False;
      end if;
   end Update;

   procedure Get_Matrix;
   --  Could use := []; but GNAT 12 has a bug (fixed in upcoming 13)
   Read_Status : Key_Matrix := [others => [others => False]];

   function Get_Events return Events is
      Num_Evt : Natural := 0;
      New_S : Key_Matrix renames Read_Status;
   begin
      Get_Matrix;
      if Update (New_S) then
         for I in Current_Status'Range (1) loop
            for J in Current_Status'Range (2) loop
               if (not New_Status (I, J) and then Current_Status (I, J))
                 or else (New_Status (I, J) and then not Current_Status (I, J))
               then
                  Num_Evt := Num_Evt + 1;
               end if;
            end loop;
         end loop;

         declare
            Evts : Events (Natural range 1 .. Num_Evt);
            Cursor : Natural range 1 .. Num_Evt + 1 := 1;
         begin
            for I in Current_Status'Range (1) loop
               for J in Current_Status'Range (2) loop
                  if not New_Status (I, J)
                    and then Current_Status (I, J)
                  then
                     --  Pressing I, J
                     Evts (Cursor) := [
                                       Evt => Press,
                                       Col => I,
                                       Row => J
                                      ];
                     Cursor := Cursor + 1;
                  elsif New_Status (I, J)
                    and then not Current_Status (I, J)
                  then
                     --  Release I, J
                     Evts (Cursor) := [
                                       Evt => Release,
                                       Col => I,
                                       Row => J
                                      ];
                     Cursor := Cursor + 1;
                  end if;
               end loop;
            end loop;
            return Evts;
         end;
      end if;

      return [];
   end Get_Events;

   procedure Get_Matrix  is -- return Key_Matrix is
   begin
      for Row in Keys.Rows'Range loop
         Keys.Rows (Row).Clear;

         for Col in Keys.Cols'Range loop
            Read_Status (Col, Row) := not Keys.Cols (Col).Set;
         end loop;
         Keys.Rows (Row).Set;
      end loop;
   end Get_Matrix;

   --  End of DEBOUNCE

   --------------
   --  Layout  --
   --------------

   package Events_Ring_Buffers is new Chests.Ring_Buffers
     (Element_Type => Event,
      Capacity     => 16);

   Queued_Events : Events_Ring_Buffers.Ring_Buffer;

   type Statet is (Normal_Key, Layer_Mod, None);
   type State is record
      Typ : Statet;
      Code : Key_Code_T;
      Layer_Value : Natural;
      --  Col : ColR;
      --  Row : RowR;
   end record;

   type State_Array is array (ColR, RowR) of State;
   States : State_Array := [others => [others => (Typ => None, Code => No, Layer_Value => 0)]];

   function Kw (Code : Key_Code_T) return Action is
   begin
      return (T => Key, C => Code, L => 0);
   end Kw;

   function Lw (V : Natural) return Action is
   begin
      return (T => Layer, C => No, L => V);
   end Lw;

   --  FIXME: hardcoded max number of events
   subtype Events_Range is Natural range 0 .. 60;
   type Array_Of_Reg_Events is array (Events_Range) of Event;

   Stamp : Natural := 0;

   procedure Register_Events (L : Layout; Es : Events) is
   begin
      Stamp := Stamp + 1;

      Log ("Reg events: " & Stamp'Image);
      Log (Es'Length'Image);
      for E of Es loop
         declare
         begin
            if Events_Ring_Buffers.Is_Full (Queued_Events) then
               raise Program_Error;
            end if;

            Events_Ring_Buffers.Append (Queued_Events, E);
         end;
         --         Log ("Reg'ed events:" &  Events_Mark'Image);
         Log ("Reg'ed events:" &  Events_Ring_Buffers.Length (Queued_Events)'Image);
      end loop;
   end Register_Events;

   procedure Release (Col: Colr; Row: Rowr) is
   begin
      if States (Col, Row).Typ = None then
         raise Program_Error;
      end if;
      States (Col, Row) := (Typ => None, Code => No, Layer_Value => 0);
   end Release;

   function Get_Current_Layer return Natural is
      L : Natural := 0;
   begin
      for S of States loop
         if S.Typ = Layer_Mod then
            L := L + S.Layer_Value;
         end if;
      end loop;

      return L;
   end Get_Current_Layer;

   --  Tick the event.
   --  Returns TRUE if it needs to stay in the queued events
   --  FALSE if the event has been consumed.

   function Tick (L: Layout; E : in out Event) return Boolean is
      Current_Layer : Natural := Get_Current_Layer;
      A : Action renames L (Current_Layer, E.Row, E.Col);
   begin
      case E.Evt is
         when Press =>
            case A.T is
               when Key =>
                  States (E.Col, E.Row) :=
                    (Typ => Normal_Key,
                     Code => A.C,
                     Layer_Value => 0);
               when Layer =>
                  States (E.Col, E.Row) := (Typ => Layer_Mod, Layer_Value => A.L, Code => No);
               when others =>
                  raise Program_Error;
            end case;

         when Release =>
            Release (E.Col, E.Row);
      end case;
      return False;
   end Tick;

   Last_Was_Empty_Log : Boolean := False;

   procedure Tick (L : Layout) is
   begin
      for I in 1 .. Events_Ring_Buffers.Length(Queued_Events) loop
         declare
            E : Event := Events_Ring_Buffers.Last_Element (Queued_Events);
         begin
            Events_Ring_Buffers.Delete_Last (Queued_Events);
            if Tick (L, E) then
               Events_Ring_Buffers.Prepend (Queued_Events, E);
            end if;
         end;
      end loop;
      if not Last_Was_Empty_Log or else Events_Ring_Buffers.Length(Queued_Events) /= 0 then
         Log ("End Tick layout, events: " & Events_Ring_Buffers.Length(Queued_Events)'Image);
         Last_Was_Empty_Log := Events_Ring_Buffers.Length(Queued_Events) = 0;
      end if;
   end Tick;

   function Get_Key_Codes return Key_Codes_T is
      Codes : Key_Codes_T (0 .. 10);
      Wm: Natural := 0;
   begin
      for S of States loop
         if S.Typ = Normal_Key and then
            (S.Code < LCtrl or else S.Code > RGui)
         then
            Codes (Wm) := S.Code;
            Wm := Wm + 1;
         end if;
      end loop;

      if Wm = 0 then
         return [];
      else
         return Codes (0 .. Wm - 1);
      end if;
   end Get_Key_Codes;

   function Get_Modifiers return Key_Modifiers is
      use USB.Device.HID.Keyboard;
      KM : Key_Modifiers (1..8);
      I : Natural := 0;
   begin
      for S of States loop
         if S.Typ = Normal_Key then
            I := I + 1;
            case S.Code is
              when LCtrl =>
                 KM(I) := Ctrl_Left;
              when RCtrl =>
                 KM(I) := Ctrl_Right;
              when LShift =>
                 KM(I) := Shift_Left;
              when RShift =>
                 KM(I) := Shift_Right;
              when LAlt =>
                 KM(I) := Alt_Left;
              when RAlt =>
                 KM(I) := Alt_Right;
              when LGui =>
                 KM(I) := Meta_Left;
              when RGui =>
                 KM(I) := Meta_Right;
              when others =>
                 I := I - 1;
            end case;
         end if;
      end loop;
      return KM (1..I);
   end Get_Modifiers;

   procedure Init is
   begin
      Events_Ring_Buffers.Clear (Queued_Events);
   end Init;

end Click;