--
--  Copyright (C) 2022-2024, AdaCore
--
--  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
--

with Ada.Command_Line;
with Ada.Containers.Indefinite_Hashed_Sets;
with Ada.Strings.Fixed.Hash;
with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Interfaces;

with VSS.JSON.Implementation.Numbers;
with VSS.Strings.Conversions;

with Test_Support;

procedure Test_JSON_Decimal_To_Number is

   use type Interfaces.IEEE_Float_64;

   procedure Run_Test (File_Name : String);

   package Unsigned_64_IO is
     new Ada.Text_IO.Modular_IO (Interfaces.Unsigned_64);

   function To_Unsigned_64 is
     new Ada.Unchecked_Conversion
           (Interfaces.IEEE_Float_64, Interfaces.Unsigned_64);

   package String_Sets is
     new Ada.Containers.Indefinite_Hashed_Sets
           (String, Ada.Strings.Fixed.Hash, "=");

   Exceptions : String_Sets.Set;

   procedure Load_Exceptions (File_Name : String);

   procedure Process_String
     (State  : in out VSS.JSON.Implementation.Numbers.Parsing_State;
      Image  : String);

   T : Natural := 0;

   ---------------------
   -- Load_Exceptions --
   ---------------------

   procedure Load_Exceptions (File_Name : String) is
      File      : Ada.Text_IO.File_Type;
      Buffer    : String (1 .. 4096);
      Last      : Natural;

   begin
      if File_Name = "/dev/null" then
         --  There is no /dev/null file on Windows, so don't attempt to open it
         --  to avoid unexpected test failure on Windows.

         return;
      end if;

      Ada.Text_IO.Open (File, Ada.Text_IO.In_File, File_Name);

      while not Ada.Text_IO.End_Of_File (File) loop
         Ada.Text_IO.Get_Line (File, Buffer, Last);

         if Last >= Buffer'First then
            Exceptions.Include (Buffer (Buffer'First .. Last));
         end if;
      end loop;
   end Load_Exceptions;

   --------------------
   -- Process_String --
   --------------------

   procedure Process_String
     (State  : in out VSS.JSON.Implementation.Numbers.Parsing_State;
      Image : String)
   is
      type Modes is (Initial, Int, Frac, Exp);

      Mode  : Modes := Initial;
      Index : Positive := Image'First;
      C     : Character;

   begin
      T := T + 1;

      while Index <= Image'Last loop
         C := Image (Index);

         case Mode is
            when Initial =>
               if C in '0' .. '9' then
                  VSS.JSON.Implementation.Numbers.Int_Digit
                    (State, Character'Pos (C));
                  Mode := Int;

               elsif C = '.' then
                  VSS.JSON.Implementation.Numbers.Decimal_Point (State);
                  Mode := Frac;

               else
                  raise Program_Error
                    with "Unexpected character '"
                            & C & "' in " & Modes'Image (Mode);
               end if;

            when Int =>
               if C in '0' .. '9' then
                  VSS.JSON.Implementation.Numbers.Int_Digit
                    (State, Character'Pos (C));

               elsif C = '.' then
                  VSS.JSON.Implementation.Numbers.Decimal_Point (State);
                  Mode := Frac;

               elsif C in 'e' | 'E' then
                  Mode := Exp;

               else
                  raise Program_Error
                    with "Unexpected character '"
                            & C & "' in " & Modes'Image (Mode);
               end if;

            when Frac =>
               if C in '0' .. '9' then
                  VSS.JSON.Implementation.Numbers.Frac_Digit
                    (State, Character'Pos (C));

               elsif C in 'e' | 'E' then
                  Mode := Exp;

               else
                  raise Program_Error
                    with "Unexpected character '"
                            & C & "' in " & Modes'Image (Mode);
               end if;

            when Exp =>
               if C in '0' .. '9' then
                  VSS.JSON.Implementation.Numbers.Exp_Digit
                    (State, Character'Pos (C));

               elsif C = '+' then
                  null;

               elsif C = '-' then
                  State.Exp_Minus := True;

               else
                  raise Program_Error
                    with "Unexpected character '"
                            & C & "' in " & Modes'Image (Mode);
               end if;
         end case;

         Index := Index + 1;
      end loop;
   end Process_String;

   --------------
   -- Run_Test --
   --------------

   procedure Run_Test (File_Name : String) is
      use type VSS.JSON.JSON_Number_Kind;

      File      : Ada.Text_IO.File_Type;
      Buffer    : String (1 .. 4096);
      Last      : Natural;
      Separator : Positive;
      First     : Positive;
      Binary    : Interfaces.Unsigned_64;

   begin
      Ada.Text_IO.Open (File, Ada.Text_IO.In_File, File_Name);

      while not Ada.Text_IO.End_Of_File (File) loop
         Ada.Text_IO.Get_Line (File, Buffer, Last);

         if Last >= Buffer'First then
            Separator := Buffer'First;

            --  Skip representation of 16-bit float

            while Separator <= Last and then Buffer (Separator) /= ' ' loop
               Separator := Separator + 1;
            end loop;

            Separator := Separator + 1;

            --  Skip representation of 32-bit float

            while Separator <= Last and then Buffer (Separator) /= ' ' loop
               Separator := Separator + 1;
            end loop;

            Separator := Separator + 1;

            --  Read representation of 64-bit float

            First := Separator;

            while Separator <= Last and then Buffer (Separator) /= ' ' loop
               Separator := Separator + 1;
            end loop;

            Binary :=
              Interfaces.Unsigned_64'Value
                ("16#" & Buffer (First .. Separator - 1) & '#');

            Separator := Separator + 1;

            --  Read decimal representation of the value
            --
            --  Correct format is assumed

            if not Exceptions.Contains (Buffer (Separator .. Last)) then
               declare
                  use type Interfaces.Unsigned_64;

                  State : VSS.JSON.Implementation.Numbers.Parsing_State;
                  R     : VSS.JSON.JSON_Number;
                  B1    : String (1 .. 20);
                  B2    : String (1 .. 20);

               begin
                  First := Separator;

                  Process_String (State, Buffer (Separator .. Last));

                  --  Force float mode

                  State.Has_Fractional := True;

                  VSS.JSON.Implementation.Numbers.To_JSON_Number
                    (State,
                     VSS.Strings.Conversions.To_Virtual_String
                       (Buffer (First .. Last)),
                     R);

                  if Binary = 16#0000_0000_0000_0000#
                    and R.Kind = VSS.JSON.Out_Of_Range
                  then
                     --  Out_Of_Range means value is too small to be
                     --  represented as float.

                     null;

                  elsif Binary = 16#7FF0_0000_0000_0000# then
                     --  Special case for Inf

                     Test_Support.Assert (R.Kind = VSS.JSON.Out_Of_Range);

                  else
                     Unsigned_64_IO.Put (B1, Binary, 16);

                     if R.Kind /= VSS.JSON.Out_Of_Range then
                        Unsigned_64_IO.Put
                          (B2, To_Unsigned_64 (R.Float_Value), 16);

                     else
                        B2 := "Out-Of-Range        ";
                     end if;

                     if R.Kind = VSS.JSON.Out_Of_Range
                       or else To_Unsigned_64 (R.Float_Value) /= Binary
                     then
                        Ada.Text_IO.Put ("FAIL: ");
                        Ada.Text_IO.Put (Buffer (First .. Last));
                        Ada.Text_IO.Put (' ');
                        Ada.Text_IO.Put (B1);
                        Ada.Text_IO.Put (' ');
                        Ada.Text_IO.Put (B2);
                        Ada.Text_IO.New_Line;
                     end if;

                     Test_Support.Assert
                       (To_Unsigned_64 (R.Float_Value) = Binary,
                        B2 & " (expected " & B1 & ')');
                  end if;
               end;
            end if;
         end if;
      end loop;

      Ada.Text_IO.Close (File);
   end Run_Test;

   --  This code is commented out intentionally, it is useful for debug of
   --  particular test case.
   --
   --  P : VSS.JSON.Implementation.Numbers.Parsing_State;
   --  N : VSS.JSON.JSON_Number;
   --  S : constant String := "358416272e-33";
   --  F : constant Interfaces.IEEE_Float_64 := 358416272.0e-33;

begin
   --  Process_String (P, S);
   --  P.Has_Fractional := True;
   --  VSS.JSON.Implementation.Numbers.To_JSON_Number
   --    (P, VSS.Strings.Conversions.To_Virtual_String (S), N);
   --  Test_Support.Assert (N.Float_Value = F);

   Load_Exceptions (Ada.Command_Line.Argument (1));

   for J in 2 .. Ada.Command_Line.Argument_Count loop
      Run_Test (Ada.Command_Line.Argument (J));
   end loop;

   Ada.Text_IO.Put_Line (Integer'Image (T));

exception
   when others =>
      Ada.Text_IO.Put_Line (Integer'Image (T));

      raise;
end Test_JSON_Decimal_To_Number;
