with
     ada.Characters.latin_1;

package body any_Math
is
   use ada.Containers;

   -----------
   -- Integers
   --

   procedure increment (Self : in out Integer;   By : in Integer := 1)
   is
   begin
      Self := Self + By;
   end increment;


   procedure decrement (Self : in out Integer;   By : in Integer := 1)
   is
   begin
      Self := Self - By;
   end decrement;


   procedure swap (Left, Right : in out Integer)
   is
      Pad : constant Integer := Left;
   begin
      Left  := Right;
      Right := Pad;
   end swap;


   -----------
   -- Counters
   --

   procedure increment (Self : in out Count_type;   By : in Count_type := 1)
   is
   begin
      Self := Self + By;
   end increment;


   procedure decrement (Self : in out Count_type;   By : in Count_type := 1)
   is
   begin
      Self := Self - By;
   end decrement;


   ---------
   --  Reals
   --

   --  Ada 95 Quality and Style Guide, 7.2.7:
   --  Tests for
   --
   --  (1) absolute "equality" to 0 in storage,
   --  (2) absolute "equality" to 0 in computation,
   --  (3) relative "equality" to 0 in storage, and
   --  (4) relative "equality" to 0 in computation:
   --
   --  abs X <= Float_Type'Model_Small                      -- (1)
   --  abs X <= Float_Type'Base'Model_Small                 -- (2)
   --  abs X <= abs X * Float_Type'Model_Epsilon            -- (3)
   --  abs X <= abs X * Float_Type'Base'Model_Epsilon       -- (4)
   --
   function almost_Zero (Self : Real) return Boolean
   is
   begin
      return  abs Self <= Real'Base'Model_Small;
   end almost_Zero;


   function  almost_Equals (Self,
                            Other,
                            Tolerance : in Real := Real'Base'Model_Small) return Boolean
   is
   begin
      return Self <= Other + Tolerance
        and  Self >= Other - Tolerance;
   end almost_Equals;


   function Clamped (Self : in Real;   Low, High : in Real) return Real
   is
   begin
      return Real'Max (Low,
                       Real'Min (Self, High));
   end Clamped;


   procedure clamp (Self : in out Real;   Low, High : in Real)
   is
   begin
      Self := Clamped (Self, Low, High);
   end clamp;


   procedure swap (Left, Right : in out Real)
   is
      Pad : constant Real := Left;
   begin
      Left  := Right;
      Right := Pad;
   end swap;


   -------------
   -- Percentage
   --

   function to_Percentage (From : in Real) return Percentage
   is
   begin
      return Percentage (From * Real' (100.0));
   end to_Percentage;


   function to_Real (Percent : in Percentage) return Real
   is
   begin
      return Real (Percent / 100.0);
   end to_Real;


   function Image (Percent   : in Percentage;
                   Precision : in Natural := 5) return String
   is
   begin
      return Image (Real (Percent),
                    Precision)
           & "%";
   end Image;


   function apply (Left, Right : in Percentage) return Percentage
   is
   begin
      return Percentage (Real (Left) * Real (Right) / 100.0**2);
   end apply;
   --
   -- Named "apply" (rather than "*") to prevent silently overriding the "*" function of the Real type.


   function apply (Percent : in Percentage;
                   To      : in Real) return Real
   is
   begin
      return to_Real (Percent) * To;
   end apply;
   --
   -- Named "apply" (rather than "*") to prevent ambiguous expressions when numeric literals are used.


   ---------
   -- Angles
   --

   function to_Radians (Self : in Degrees) return Radians
   is
   begin
      return Radians (Self * Pi / 180.0);
   end to_Radians;


   function to_Degrees (Self : in Radians) return Degrees
   is
   begin
      return Degrees (Self) * 180.0 / Pi;
   end to_Degrees;


   ----------
   -- Vectors
   --

   function Sum (Self : in Vector) return Real
   is
      the_Sum : Real := 0.0;
   begin
      for Each in Self'Range
      loop
         the_Sum := the_Sum + Self (Each);
      end loop;

      return the_Sum;
   end Sum;


   function Average (Self : in Vector) return Real
   is
   begin
      return Sum (Self) / Real (Self'Length);
   end Average;


   function Max (Self : in Vector) return Real
   is
      Max : Real := Self (Self'First);
   begin
      for i in Self'First + 1 .. Self'Last
      loop
         Max := Real'Max (Max, Self (i));
      end loop;

      return Max;
   end Max;


   function Min (Self : in Vector) return Real
   is
      Min : Real := Self (Self'First);
   begin
      for i in Self'First + 1 .. Self'Last
      loop
         Min := Real'Min (Min, Self (i));
      end loop;

      return Min;
   end Min;


   -----------
   -- Matrices
   --

   function Row (Self : in Matrix_2x2;   row_Id : in Index) return Vector_2
   is
   begin
      return [Self (row_Id, 1),
              Self (row_Id, 2)];
   end Row;


   function Col (Self : in Matrix_2x2;   col_Id : in Index) return Vector_2
   is
   begin
      return [Self (1, col_Id),
              Self (2, col_Id)];
   end Col;


   function Row (Self : in Matrix_3x3;   row_Id : in Index) return Vector_3
   is
   begin
      return [Self (row_Id, 1),
              Self (row_Id, 2),
              Self (row_Id, 3)];
   end Row;


   function Col (Self : in Matrix_3x3;   col_Id : in Index) return Vector_3
   is
   begin
      return [Self (1, col_Id),
              Self (2, col_Id),
              Self (3, col_Id)];
   end Col;


   function Row (Self : in Matrix_4x4;   row_Id : in Index) return Vector_4
   is
   begin
      return [Self (row_Id, 1),
              Self (row_Id, 2),
              Self (row_Id, 3),
              Self (row_Id, 4)];
   end Row;


   function Col (Self : in Matrix_4x4;   col_Id : in Index) return Vector_4
   is
   begin
      return [Self (1, col_Id),
              Self (2, col_Id),
              Self (3, col_Id),
              Self (4, col_Id)];
   end Col;


   function to_Vector_16 (Self : in Matrix_4x4) return Vector_16
   is
   begin
      return Vector_16 (  Vector_4' (Row (Self, 1))
                        & Vector_4' (Row (Self, 2))
                        & Vector_4' (Row (Self, 3))
                        & Vector_4' (Row (Self, 4)));
   end to_Vector_16;


   function to_Matrix_4x4 (Self : in Vector_16)  return Matrix_4x4
   is
   begin
      return Matrix_4x4' (1 => [Self ( 1), Self ( 2), Self ( 3), Self ( 4)],
                          2 => [Self ( 5), Self ( 6), Self ( 7), Self ( 8)],
                          3 => [Self ( 9), Self (10), Self (11), Self (12)],
                          4 => [Self (13), Self (14), Self (15), Self (16)]);
   end to_Matrix_4x4;


   --------------
   -- Quaternions
   --

   function to_Quaternion (From : in Vector_4) return Quaternion
   is
   begin
      return (From (1),
              (Vector_3 (From (2 .. 4))));
   end to_Quaternion;


   function to_Vector (From : in Quaternion) return Vector_4
   is
   begin
      return Vector_4 (From.R & From.V);
   end to_Vector;


   function "*" (Left : in Quaternion;   Right : in Real) return Quaternion
   is
   begin
      return (Left.R * Right,
              (Left.V * Right));
   end "*";


   function "*" (Left : in Real;   Right : in Quaternion) return Quaternion
   is
   begin
      return (Right.R * Left,
              (Right.V * Left));
   end "*";


   function "/" (Left : in Quaternion;   Right : in Real) return Quaternion
   is
   begin
      return (Left.R / Right,
              (Left.V / Right));
   end "/";


   function "+" (Left, Right : in Quaternion) return Quaternion
   is
   begin
      return (Left.R + Right.R,
              Left.V + Right.V);
   end "+";


   function "-" (Left, Right : in Quaternion) return Quaternion
   is
   begin
      return (Left.R - Right.R,
              Left.V - Right.V);
   end "-";


   function Image (Self : in Quaternion;   Precision : in Natural := 5) return String
   is
   begin
      return "(R => "  & Image (Self.R, Precision)
           & ", V => " & Image (Self.V, Precision)  & ")";
   end Image;


   ---------
   -- Images
   --

   -- Real Image
   --
   function Image (Self : in Real;   Precision : in Natural := 5) return String
   is
      type Fixed_1  is delta  0.1            range -100_000_000_000_000_000.0 .. 100_000_000_000_000_000.0;
      type Fixed_2  is delta  0.01           range  -10_000_000_000_000_000.0 ..  10_000_000_000_000_000.0;
      type Fixed_3  is delta  0.001          range   -1_000_000_000_000_000.0 ..   1_000_000_000_000_000.0;
      type Fixed_4  is delta  0.0001         range     -100_000_000_000_000.0 ..     100_000_000_000_000.0;
      type Fixed_5  is delta  0.00001        range      -10_000_000_000_000.0 ..      10_000_000_000_000.0;
      type Fixed_6  is delta  0.000001       range       -1_000_000_000_000.0 ..       1_000_000_000_000.0;
      type Fixed_7  is delta  0.0000001      range         -100_000_000_000.0 ..         100_000_000_000.0;
      type Fixed_8  is delta  0.00000001     range          -10_000_000_000.0 ..          10_000_000_000.0;
      type Fixed_9  is delta  0.000000001    range           -1_000_000_000.0 ..           1_000_000_000.0;
      type Fixed_10 is delta  0.0000000001   range             -100_000_000.0 ..             100_000_000.0;
      type Fixed_11 is delta  0.00000000001  range              -10_000_000.0 ..              10_000_000.0;
      type Fixed_12 is delta  0.000000000001 range               -1_000_000.0 ..               1_000_000.0;
   begin
      case Precision
      is
         when 0      => return Integer'Image  (Integer  (Self));
         when 1      => return Fixed_1'Image  (Fixed_1  (Self));
         when 2      => return Fixed_2'Image  (Fixed_2  (Self));
         when 3      => return Fixed_3'Image  (Fixed_3  (Self));
         when 4      => return Fixed_4'Image  (Fixed_4  (Self));
         when 5      => return Fixed_5'Image  (Fixed_5  (Self));
         when 6      => return Fixed_6'Image  (Fixed_6  (Self));
         when 7      => return Fixed_7'Image  (Fixed_7  (Self));
         when 8      => return Fixed_8'Image  (Fixed_8  (Self));
         when 9      => return Fixed_9'Image  (Fixed_9  (Self));
         when 10     => return Fixed_10'Image (Fixed_10 (Self));
         when 11     => return Fixed_11'Image (Fixed_11 (Self));
         when 12     => return Fixed_12'Image (Fixed_12 (Self));
         when others => return Fixed_12'Image (Fixed_12 (Self));
      end case;

   exception
      when Constraint_Error =>
         return Real'Image (Self);
   end Image;


   -- Vector Image
   --
   function Image (Self : in Vector;   Precision : in Natural := 5) return String
   is
      the_Image : String (1 .. 1 * 1024 * 1024);   -- Handles one megabyte string, excess is truncated.
      Count     : Standard.Natural := 0;

      procedure add (Text : in String)
      is
      begin
         the_Image (Count + 1 .. Count + text'Length) := Text;
         Count                                        := Count + text'Length;
      end add;

   begin
      add ("(");

      for Each in Self'Range
      loop
         if Each /= Self'First
         then
            add (", ");
         end if;

         add (Image (Self (Each),
                     Precision));
      end loop;

      add (")");
      return the_Image (1 .. Count);

   exception
      when others =>
         return the_Image (1 .. Count);
   end Image;


   -----------
   -- Vector_2
   --

   function to_Vector_2 (Self : in Vector_3) return Vector_2
   is
   begin
      return Vector_2 (Self (1 .. 2));
   end to_Vector_2;


   function Image (Self : in Vector_2;   Precision : in Natural := 5) return String
   is
   begin
      return Image (Vector (Self), Precision);
   end Image;


   overriding
   function "+" (Left, Right : in Vector_2) return Vector_2
   is
   begin
      return [Left (1) + Right (1),
              Left (2) + Right (2)];
   end "+";


   overriding
   function "-" (Left, Right : in Vector_2) return Vector_2
   is
   begin
      return [Left (1) - Right (1),
              Left (2) - Right (2)];
   end "-";


   overriding
   function "*" (Left : in Real;   Right : in Vector_2) return Vector_2
   is
   begin
      return [Right (1) * Left,
              Right (2) * Left];
   end "*";


   overriding
   function "*" (Left : in Vector_2;   Right : in Real) return Vector_2
   is
   begin
      return [Left (1) * Right,
              Left (2) * Right];
   end "*";


   overriding
   function "/" (Left : in Vector_2;   Right : in Real) return Vector_2
   is
   begin
      return [Left (1) / Right,
              Left (2) / Right];
   end "/";


   -----------
   -- Vector_3
   --

   function to_Vector_3 (Self : in Vector_2;   Z : in Real := 0.0) return Vector_3
   is
   begin
      return Vector_3 (Self & Z);
   end to_Vector_3;


   function Image (Self : in Vector_3;   Precision : in Natural := 5) return String
   is
   begin
      return Image (Vector (Self), Precision);
   end Image;


   overriding
   function "*" (Left : in Real;   Right : in Vector_3) return Vector_3
   is
   begin
      return [Right (1) * Left,
              Right (2) * Left,
              Right (3) * Left];
   end "*";


   function "*" (Left, Right : in Vector_3) return Vector_3
   is
   begin
      return [1 => Left (2) * Right (3)  -  Left (3) * Right (2),
              2 => Left (3) * Right (1)  -  Left (1) * Right (3),
              3 => Left (1) * Right (2)  -  Left (2) * Right (1)];
   end "*";


   overriding
   function "+" (Left, Right : in Vector_3) return Vector_3
   is
   begin
      return [Left (1) + Right (1),
              Left (2) + Right (2),
              Left (3) + Right (3)];
   end "+";


   overriding
   function "-" (Left, Right : in Vector_3) return Vector_3
   is
   begin
      return [Left (1) - Right (1),
              Left (2) - Right (2),
              Left (3) - Right (3)];
   exception
      when Constraint_Error =>
         raise Constraint_Error with "any_math ""-"" (Left, Right : Vector_3) => "
                                    & Image (Left) & "   " & Image (Right);
   end "-";


   overriding
   function "-" (Right : in Vector_3) return Vector_3
   is
   begin
      return [-Right (1),
              -Right (2),
              -Right (3)];
   end "-";


   overriding
   function "*" (Left : in Vector_3;   Right : in Real) return Vector_3
   is
   begin
      return [Left (1) * Right,
              Left (2) * Right,
              Left (3) * Right];
   end "*";


   overriding
   function "/" (Left : in Vector_3;   Right : in Real) return Vector_3
   is
   begin
      return [Left (1) / Right,
              Left (2) / Right,
              Left (3) / Right];
   end "/";


   overriding
   function "abs" (Right : in Vector_3) return Vector_3
   is
      use Vectors;
   begin
      return Vector_3 (Vector' (abs (Vector (Right))));
   end "abs";


   function  almost_Equals (Self, Other : in Vector_3;
                            Tolerance   : in     Real := Real'Base'Model_Small) return Boolean
   is
   begin
      return almost_Equals (Self (1), Other (1),  Tolerance)
        and  almost_Equals (Self (2), Other (2),  Tolerance)
        and  almost_Equals (Self (3), Other (3),  Tolerance);
   end almost_Equals;


   ---------
   -- Matrix
   --

   function Image (Self : Matrix) return String
   is
      Image : String (1 .. 1024);
      Last  : Natural := 0;
   begin
      for Row in Self'Range (1)
      loop
         for Col in Self'Range (2)
         loop
            declare
               Element : constant String := Real'Image (Self (Row, Col));
            begin
               Last         := Last + 1;
               Image (Last) := ' ';
               Last         := Last + 1;
               Image (Last .. Last + Element'Length - 1)
                            := Element;
               Last         := Last + Element'Length - 1;
            end;
         end loop;

         Last         := Last + 1;
         Image (Last) := ada.Characters.Latin_1.LF;
      end loop;

      return Image (1 .. Last);
   end Image;


   -------------
   -- Matrix_2x2
   --

   overriding
   function Transpose (Self : in Matrix_2x2) return Matrix_2x2
   is
   begin
      return Matrix_2x2 (Vectors.Transpose (Matrix (Self)));
   end Transpose;


   function "*" (Left : in Matrix_2x2;   Right : in Vector_2) return Vector_2
   is
      Result : Vector_2 := [others => 0.0];
   begin
      for Row in 1 .. 2
      loop
         for Col in 1 .. 2
         loop
            Result (Row) := Result (Row) +   Left (Row, Col)
                                           * Right (Col);
         end loop;
      end loop;

      return Result;
   end "*";


   function "*" (Left : in Vector_2;   Right : in Matrix_2x2) return Vector_2
   is
      use Vectors;
   begin
      return Vector_2 (  Vector (Left)
                       * Matrix (Right));
   end "*";


   -------------
   -- Matrix_3x3
   --

   overriding
   function Transpose (Self : in Matrix_3x3) return Matrix_3x3
   is
   begin
      return Matrix_3x3 (Vectors.Transpose (Matrix (Self)));
   end Transpose;


   function "*" (Left : in Matrix_3x3;   Right : in Vector_3) return Vector_3
   is
      A : Matrix_3x3 renames Left;
      B : Vector_3   renames Right;
   begin
      return [(a(1,1)*b(1) + a(1,2)*b(2) + a(1,3)*b(3)),
              (a(2,1)*b(1) + a(2,2)*b(2) + a(2,3)*b(3)),
              (a(3,1)*b(1) + a(3,2)*b(2) + a(3,3)*b(3))];
   end "*";


   function "*" (Left : in Vector_3;   Right : in Matrix_3x3) return Vector_3
   is
      A : Matrix_3x3 renames Right;
      B : Vector_3   renames Left;
   begin
      return [(a(1,1)*b(1) + a(2,1)*b(2) + a(3,1)*b(3)),
              (a(1,2)*b(1) + a(2,2)*b(2) + a(3,2)*b(3)),
              (a(1,3)*b(1) + a(2,3)*b(2) + a(3,3)*b(3))];
   end "*";


   -------------
   -- Matrix_4x4
   --

   overriding
   function Transpose (Self : in Matrix_4x4) return Matrix_4x4
   is
   begin
      return Matrix_4x4 (Vectors.Transpose (Matrix (Self)));
   end Transpose;


   function "*" (Left : in Matrix_4x4;   Right : in Vector_4) return Vector_4
   is
      A : Matrix_4x4 renames Left;
      B : Vector_4   renames Right;
   begin
      return [(a(1,1)*b(1) + a(1,2)*b(2) + a(1,3)*b(3) + a(1,4)*b(4)),
              (a(2,1)*b(1) + a(2,2)*b(2) + a(2,3)*b(3) + a(2,4)*b(4)),
              (a(3,1)*b(1) + a(3,2)*b(2) + a(3,3)*b(3) + a(3,4)*b(4)),
              (a(4,1)*b(1) + a(4,2)*b(2) + a(4,3)*b(3) + a(4,4)*b(4))];
   end "*";


   function "*" (Left : in Vector_4;   Right : in Matrix_4x4) return Vector_4
   is
      A : Matrix_4x4 renames Right;
      B : Vector_4   renames Left;
   begin
      return [(a(1,1)*b(1) + a(2,1)*b(2) + a(3,1)*b(3) + a(4,1)*b(4)),
              (a(1,2)*b(1) + a(2,2)*b(2) + a(3,2)*b(3) + a(4,2)*b(4)),
              (a(1,3)*b(1) + a(2,3)*b(2) + a(3,3)*b(3) + a(4,3)*b(4)),
              (a(1,4)*b(1) + a(2,4)*b(2) + a(3,4)*b(3) + a(4,4)*b(4))];
   end "*";


   function "*" (Left : in Matrix_4x4;   Right : in Vector_3) return Vector_3
   is
      V : Vector_4 := Vector_4 (Right & 1.0);
   begin
      V := Left * V;
      return Vector_3 (V (1..3));
   end "*";


   function "*" (Left : in Vector_3;   Right : in Matrix_4x4) return Vector_4
   is
      V : Vector_4 := Vector_4 (Left & 1.0);
   begin
      V := V * Right;
      return V;
   end "*";


   function "*" (Left : in Matrix_4x4;   Right : in Vector_3) return Vector_4
   is
      V : Vector_4 := Vector_4 (Right & 1.0);
   begin
      V := Left * V;
      return V;
   end "*";


   overriding
   function "*" (Left : in Matrix_4x4;   Right : in Matrix_4x4) return Matrix_4x4
   is
      A : Matrix_4x4 renames Left;
      B : Matrix_4x4 renames Right;
   begin
      return [[a(1,1)*b(1,1) + a(1,2)*b(2,1) + a(1,3)*b(3,1) + a(1,4)*b(4,1),  a(1,1)*b(1,2) + a(1,2)*b(2,2) + a(1,3)*b(3,2) + a(1,4)*b(4,2),  a(1,1)*b(1,3) + a(1,2)*b(2,3) + a(1,3)*b(3,3) + a(1,4)*b(4,3),  a(1,1)*b(1,4) + a(1,2)*b(2,4) + a(1,3)*b(3,4) + a(1,4)*b(4,4)],
              [a(2,1)*b(1,1) + a(2,2)*b(2,1) + a(2,3)*b(3,1) + a(2,4)*b(4,1),  a(2,1)*b(1,2) + a(2,2)*b(2,2) + a(2,3)*b(3,2) + a(2,4)*b(4,2),  a(2,1)*b(1,3) + a(2,2)*b(2,3) + a(2,3)*b(3,3) + a(2,4)*b(4,3),  a(2,1)*b(1,4) + a(2,2)*b(2,4) + a(2,3)*b(3,4) + a(2,4)*b(4,4)],
              [a(3,1)*b(1,1) + a(3,2)*b(2,1) + a(3,3)*b(3,1) + a(3,4)*b(4,1),  a(3,1)*b(1,2) + a(3,2)*b(2,2) + a(3,3)*b(3,2) + a(3,4)*b(4,2),  a(3,1)*b(1,3) + a(3,2)*b(2,3) + a(3,3)*b(3,3) + a(3,4)*b(4,3),  a(3,1)*b(1,4) + a(3,2)*b(2,4) + a(3,3)*b(3,4) + a(3,4)*b(4,4)],
              [a(4,1)*b(1,1) + a(4,2)*b(2,1) + a(4,3)*b(3,1) + a(4,4)*b(4,1),  a(4,1)*b(1,2) + a(4,2)*b(2,2) + a(4,3)*b(3,2) + a(4,4)*b(4,2),  a(4,1)*b(1,3) + a(4,2)*b(2,3) + a(4,3)*b(3,3) + a(4,4)*b(4,3),  a(4,1)*b(1,4) + a(4,2)*b(2,4) + a(4,3)*b(3,4) + a(4,4)*b(4,4)]];
   end "*";

end any_Math;
