------------------------------------------------------------------------------
--
--  package International_Standard_Book_Numbers (spec)
--
--  International Standard Book Numbers.
--
--  Source: "Find fem fejl", Kjeld Bagger Laursen, in Hovedområdet 1/1998,
--          Københavns Universitets Naturvidenskabelige Fakultet.
--
------------------------------------------------------------------------------
--  Update information:
--
--  1998.02.06 (Jacob Sparre Andersen)
--    Written.
--
--  (Insert additional update information above this line.)
------------------------------------------------------------------------------

package International_Standard_Book_Numbers is

   ---------------------------------------------------------------------------
   --  type ISBN:

   type ISBN is private;

   ---------------------------------------------------------------------------
   --  function Valid:
   --
   --  Checks if an object of type ISBN has a valid value.
   --
   --  Exceptions:
   --    (none)

   function Valid (Item : in ISBN) return Boolean;

   ---------------------------------------------------------------------------
   --  function Image:
   --
   --  Converts an object of type ISBN to a string.
   --
   --  Exceptions:
   --    (none)

   function Image (Item : in ISBN) return String;

   ---------------------------------------------------------------------------
   --  function Value:
   --
   --  Converts a string to an object of type ISBN.
   --
   --  Exceptions:
   --    Constraint_Error - if the string does not represent a valid ISBN.

   function Value (Item : in String) return ISBN;

   ---------------------------------------------------------------------------
   --  function To_ISBN:
   --
   --  Composes an ISBN based on country, publisher, and book index.
   --
   --  Exceptions:
   --    Constraint_Error - if one of the indices are longer than there is
   --                       space for.

   function To_ISBN (Country, Publisher, Book         : in Natural;
                     Country_Length, Publisher_Length : in Positive)
     return ISBN;

   ---------------------------------------------------------------------------
   --  procedure Split:
   --
   --  Extracts country, publisher, and book index from an ISBN.
   --
   --  Exceptions:
   --    (none)

   procedure Split (Item      : in     ISBN;
                    Country   :    out Natural;
                    Publisher :    out Natural;
                    Book      :    out Natural);

   ---------------------------------------------------------------------------

private

   ---------------------------------------------------------------------------
   --  type Base_11_Digits:

   type Base_11_Digits is mod 11;

   ---------------------------------------------------------------------------
   --  Base_11_Digit_Image:

   Base_11_Digit_Image : constant array (Base_11_Digits) of Character :=
     ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'X');

   ---------------------------------------------------------------------------
   --  Base_11_Digit_Value:

   Base_11_Digit_Value : constant array (Character) of Base_11_Digits :=
                           ('0'    => 0,
                            '1'    => 1,
                            '2'    => 2,
                            '3'    =>  3,
                            '4'    =>  4,
                            '5'    =>  5,
                            '6'    =>  6,
                            '7'    =>  7,
                            '8'    =>  8,
                            '9'    =>  9,
                            'X'    => 10,
                            others => 10); --  Error!

   ---------------------------------------------------------------------------
   --  type Digit_Array:

   type Digit_Array is array (Base_11_Digits range 1 .. 10) of Base_11_Digits;

   ---------------------------------------------------------------------------
   --  subtype Separator_Positions:

   subtype Separator_Positions is Positive range 1 .. 8;

   ---------------------------------------------------------------------------
   --  type ISBN:

   type ISBN is
      record
         Number    : Digit_Array;
         Country   : Separator_Positions;
         Publisher : Separator_Positions;
      end record;

   ---------------------------------------------------------------------------

end International_Standard_Book_Numbers;
------------------------------------------------------------------------------
--
--  package International_Standard_Book_Numbers (body)
--
--  International Standard Book Numbers.
--
--  Source: "Find fem fejl", Kjeld Bagger Laursen, in Hovedområdet 1/1998,
--          Københavns Universitets Naturvidenskabelige Fakultet.
--
------------------------------------------------------------------------------
--  Update information:
--
--  1998.02.06 (Jacob Sparre Andersen)
--    Written.
--
--  (Insert additional update information above this line.)
------------------------------------------------------------------------------

with Ada.Strings.Fixed;

package body International_Standard_Book_Numbers is

   ---------------------------------------------------------------------------
   --  function Valid:
   --
   --  Checks if an object of type ISBN has a valid value.
   --
   --  Exceptions:
   --    (none)

   function Valid (Item : in ISBN) return Boolean is

      Sum : Base_11_Digits := 0;

   begin --  Valid
      for Index in Item.Number'Range loop
         if Item.Number (Index)'Valid then
            null;
         else
            return False;
         end if;
      end loop;

      if Item.Country'Valid and then Item.Publisher'Valid then
         null;
      else
         return False;
      end if;

      for Index in Item.Number'First .. Item.Number'Last - 1 loop
         if Item.Number (Index) = 10 then
            return False;
         end if;
      end loop;

      for Index in Item.Number'Range loop
         Sum := Sum + Index * Item.Number (Index);
      end loop;

      return Sum = 0;
   end Valid;

   ---------------------------------------------------------------------------
   --  function Image:
   --
   --  Converts an object of type ISBN to a string.
   --
   --  Exceptions:
   --    (none)

   function Image (Item : in ISBN) return String is

      Number_Image : String (1 .. 10);

   begin --  Image
      for Index in Item.Number'Range loop
         Number_Image (Integer (Index)) :=
           Base_11_Digit_Image (Item.Number (Index));
      end loop;

      return Number_Image (1 .. Item.Country) & '-'
               & Number_Image (Item.Country + 1 .. Item.Publisher) & '-'
               & Number_Image (Item.Publisher + 1 .. 9) & '-'
               & Number_Image (10);
   end Image;

   ---------------------------------------------------------------------------
   --  function Value:
   --
   --  Converts a string to an object of type ISBN.
   --
   --  Exceptions:
   --    Constraint_Error - if the string does not represent a valid ISBN.

   function Value (Item : in String) return ISBN is

      use Ada.Strings.Fixed;

      Result : ISBN;

      Digit           : Base_11_Digits := 0;
      Separator_Count : Natural range 0 .. 3 := 0;

   begin --  Value
      if Item'Length = 13 and then Count (Source  => Item,
                                          Pattern => "-") = 3 then
         for Index in Item'Range loop
            case Item (Index) is
               when '0' .. '9' | 'X' =>
                  Digit := Digit + 1;
                  Result.Number (Digit) := Base_11_Digit_Value (Item (Index));
               when '-' =>
                  Separator_Count := Separator_Count + 1;

                  case Separator_Count is
                     when 0 =>
                        raise Program_Error;
                     when 1 =>
                        Result.Country := Index - 1;
                     when 2 =>
                        Result.Publisher := Index - 2;
                     when 3 =>
                        if Index + 1 = Item'Last then
                           null;
                        else
                           raise Constraint_Error;
                        end if;
                  end case;
               when others =>
                  raise Constraint_Error;
            end case;
         end loop;

         if Valid (Result) then
            return Result;
         else
            raise Constraint_Error;
         end if;
      else
         raise Constraint_Error;
      end if;
   end Value;

   ---------------------------------------------------------------------------  
   --  function ISBN:
   --
   --  Composes an ISBN based on country, publisher, and book index.
   --
   --  Exceptions:
   --    Constraint_Error - if one of the indices are longer than there is
   --                       space for.

   function To_ISBN (Country, Publisher, Book         : in Natural;
                     Country_Length, Publisher_Length : in Positive)
     return ISBN is

      Sum    : Base_11_Digits;
      Result : ISBN;

   begin --  To_ISBN
      if Country_Length + Publisher_Length > 8 then
         raise Constraint_Error;
      end if;

      Result.Country := Country_Length;
      Result.Publisher := Country_Length + Publisher_Length;

      for Index in 1 .. Result.Country loop
         Result.Number (Base_11_Digits (Index)) :=
           Base_11_Digits
             ((Country / (10 ** (Result.Country - Index))) mod 10);
      end loop;

      for Index in Result.Country + 1 .. Result.Publisher loop
         Result.Number (Base_11_Digits (Index)) :=
           Base_11_Digits
             ((Publisher / (10 ** (Result.Publisher - Index))) mod 10);
      end loop;

      for Index in Result.Publisher + 1 .. 9 loop
         Result.Number (Base_11_Digits (Index)) :=
           Base_11_Digits
             ((Book / (10 ** (9 - Index))) mod 10);
      end loop;

      Sum := 0;
      for Index in Result.Number'First .. Result.Number'Last - 1 loop
         Sum := Sum + Index * Result.Number (Index);
      end loop;

      for Digit in Base_11_Digits loop
         if Sum + 10 * Digit = 0 then
            Result.Number (Result.Number'Last) := Digit;
         end if;
      end loop;

      if Valid (Result) then
         return Result;
      else
         raise Constraint_Error;
      end if;
   end To_ISBN;

   ---------------------------------------------------------------------------
   --  procedure Split:
   --
   --  Extracts country, publisher, and book index from an ISBN.
   --
   --  Exceptions:
   --    (none)

   procedure Split (Item      : in     ISBN;
                    Country   :    out Natural;
                    Publisher :    out Natural;
                    Book      :    out Natural) is

   begin --  Split
      Country := 0;
      Publisher := 0;
      Book := 0;

   Extract_Country:
      for Index in 1 .. Base_11_Digits (Item.Country) loop
         declare

            Digit : constant Natural range 0 .. 9
                      := Natural (Item.Number (Index));
            Power : constant Natural range 0 .. 9
                      := Item.Country - Natural (Index);

         begin
            Country := Country + Digit * (10 ** Power);
         end;
      end loop Extract_Country;

   Extract_Publisher:
      for Index in Base_11_Digits (Item.Country + 1) ..
                     Base_11_Digits (Item.Publisher) loop
         declare

            Digit : constant Natural range 0 .. 9
                      := Natural (Item.Number (Index));
            Power : constant Natural range 0 .. 9
                      := Item.Publisher - Natural (Index);

         begin
            Publisher := Publisher + Digit * (10 ** Power);
         end;
      end loop Extract_Publisher;

   Extract_Book:
      for Index in Base_11_Digits (Item.Publisher + 1) .. 9 loop
         declare

            Digit : constant Natural range 0 .. 9
                      := Natural (Item.Number (Index));
            Power : constant Natural range 0 .. 9
                      := 9 - Natural (Index);

         begin
            Book := Book + Digit * (10 ** Power);
         end;
      end loop Extract_Book;
   end Split;

   ---------------------------------------------------------------------------

end International_Standard_Book_Numbers;
------------------------------------------------------------------------------
--
--  procedure Test_International_Standard_Book_Numbers (body)
--
--  For testing package International_Standard_Book_Numbers.
--
--  Source: "Find fem fejl", Kjeld Bagger Laursen, in Hovedområdet 1/1998,
--          Københavns Universitets Naturvidenskabelige Fakultet.
--
------------------------------------------------------------------------------
--  Update information:
--
--  1998.02.06 (Jacob Sparre Andersen)
--    Written.
--
--  (Insert additional update information above this line.)
------------------------------------------------------------------------------

with Ada.Text_IO;

with International_Standard_Book_Numbers;
with UStrings;

procedure Test_International_Standard_Book_Numbers is

   ---------------------------------------------------------------------------
   --  As strings:

   Book_Images : array (1 .. 4) of String (1 .. 13) :=
                   ("87-502-0440-8", "0-201-82753-4",
                    "0-19-859665-0", "0-201-86253-4");

   ---------------------------------------------------------------------------
   --  As ISBN objects:

   type ISBN_Data is
      record
         Country, Publisher, Book         : Natural;
         Country_Length, Publisher_Length : Positive;
      end record;

   Book_Codes : array (1 .. 4) of ISBN_Data :=
                  ((87, 502,   0440, 2, 3), ( 0, 201,  82753, 1, 3),
                   ( 0,  19, 859665, 1, 2), ( 0, 201,  86253, 1, 3));

   ---------------------------------------------------------------------------

   use Ada.Text_IO;
   use International_Standard_Book_Numbers;

   Data           : ISBN;
   ISBN_As_String : UStrings.UString;

begin --  Test_International_Standard_Book_Numbers
   if Valid (Data) then
      Put_Line (Image (Data) & " (valid)");
   else
      Put_Line ("(invalid ISBN)");
   end if;
   New_Line;

   for Index in Book_Images'Range loop
      begin
         Put (Index'Img & ": """);
         Put (Book_Images (Index) & """ ");

         Data := Value (Book_Images (Index));

         Put ("""" & Image (Data) & """ ");

         if Valid (Data) then
            Put_Line ("(valid) ");
         else
            Put_Line ("(invalid) ");
         end if;

         New_Line;
      exception
         when Constraint_Error =>
            Put_Line ("A Constraint_Error was raised.");
         when others =>
            Put_Line ("Some exception occurred.");
      end;
   end loop;
   New_Line;

   for Index in Book_Images'Range loop
      begin
         Put (Index'Img & ": ");

         Data :=
           To_ISBN (Country          => Book_Codes (Index).Country,
                    Publisher        => Book_Codes (Index).Publisher,
                    Book             => Book_Codes (Index).Book,
                    Country_Length   => Book_Codes (Index).Country_Length,
                    Publisher_Length => Book_Codes (Index).Publisher_Length);

         Put ("""");
         Put (Image (Data) & """ ");

         if Valid (Data) then
            Put_Line ("(valid) ");
         else
            Put_Line ("(invalid) ");
         end if;
      exception
         when Constraint_Error =>
            Put_Line ("A Constraint_Error was raised.");
         when others =>
            Put_Line ("Some exception occurred.");
      end;
   end loop;
   New_Line;

   loop
      Put ("ISBN ");
      UStrings.Get_Line (ISBN_As_String);

      begin
         Data := Value (UStrings.S (ISBN_As_String));

         Put_Line ("  ISBN " & Image (Data) & " - OK!");
      exception
         when Constraint_Error =>
            Put_Line ("  ISBN " & UStrings.S (ISBN_As_String) & " - Bad!");
            exit;
         when others =>
            raise;
      end;
   end loop;
end Test_International_Standard_Book_Numbers;

