-- This is the implementation for the BigNum abstract data type, which supports
-- arithmetic with VERY LARGE natural values.  The operations provided
-- work as one would expect for natural numbers.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

package body BigNumPkg is

   -- Currently the implementation uses a base 10 system for
   -- storing numbers and performing arithmetic operations.
   --
   InternalBase : constant Positive := 10;

   -- This is a stub routine
   -- You are to complete the implementation of this routine
   function toString(X: BigNum) return String is
   begin
      return "";
   end toString;

   function "<"  (X, Y : BigNum) return Boolean is
   begin
      for I in 0..Size-1 loop
         if X(I) < Y(I) then
            return True;
         elsif X(I) > Y(I) then
            return False;
         end if;
      end loop;
      return False;
   end "<";

   function ">"  (X, Y : BigNum) return Boolean is
   begin
      return not (X < Y or X = Y);
   end ">";

   function "<=" (X, Y : BigNum) return Boolean is
   begin
      return X = Y or X < Y;
   end "<=";

   function ">=" (X, Y : BigNum) return Boolean is
   begin
      return not (X < Y);
      -- return Standard.">="(X, Y); -- Causes error on intel platform
   end ">=";

   function "+" (X, Y : BigNum) return BigNum is
      Overflow : Boolean;
      Result : BigNum;
   begin
      plus_ov (X, Y, Result, Overflow);

      if Overflow then
         raise BigNumOverFlow;
      end if;
      return Result;
   end "+";

   -- To add two numbers, start in the right most part of the two
   -- arrays and add column by column working toward the left.  The
   -- value of carry indicates how much of a previous column's value
   -- should be carried forward to the next column's.
   --
   procedure plus_ov (X, Y : BigNum; Result : out BigNum; Overflow : out Boolean) is
      Carry : Natural := 0;
      Sum : Integer;
   begin
      for I in reverse 0..Size-1 loop
         Sum := Carry + X(I) + Y(I);

         -- Determine the amount of carry.
         if Sum >= InternalBase then
            Sum := Sum - InternalBase;
            Carry := 1;
         else
            Carry := 0;
         end if;

         Result(I) := Sum;
      end loop;

      -- The result is too big if the left-most column gave
      -- a carry.
      Overflow := Carry > 0;
   end plus_ov;

   -- Currently implemented as repeated addition.  This make "*" slow,
   -- but at least the result is correct. :-)
   --
   function "*" (X, Y : BigNum) return BigNum is
      Count, Result : BigNum := Zero;
   begin
      while Count /= Y loop
         Count := Count + One;
         Result := Result + X;
      end loop;
      return Result;
   end "*";

   -- Skips leading whitespace (spaces, tabs, end of lines) before
   -- the data to be read.
   --
   procedure Get (Item : out BigNum) is
      Letter : Character;
      LineEnd : Boolean;
      LastI : Natural := 0;
   begin
      -- Skip leading whitespace
      loop
         if End_Of_File then
            raise DATA_ERROR;
         elsif End_Of_Line then
            Skip_Line;
         else
            Look_Ahead(Letter, LineEnd);

            -- exit if find a digit
            exit when Letter in '0'..'9';
            -- Original version skipped leading zeros, as follows:
            -- exit when Letter in '1'..'9';

            Get(Letter);
            if Letter /= ' ' and Letter /= ASCII.HT
               and Letter /= '0' then
               raise DATA_ERROR;
            end if;
         end if;
      end loop;

      -- Read in digits of number
      for I in 0..Size-1 loop
         exit when End_Of_Line;
         Look_Ahead(Letter, LineEnd);
         exit when LineEnd;
         exit when Letter not in '0'..'9';
         Get(Item(I), Width => 1);
         LastI := I;
      end loop;

      -- If there's still more digits, then raise DATA_ERROR.
      Look_Ahead(Letter, LineEnd);
      if Letter in '0'..'9' then
         raise DATA_ERROR;
      end if;

      -- Shift digits to the left within the array
      for I in reverse 0..LastI loop
         Item(I+Size-1-LastI) := Item(I);
      end loop;
      for I in 0..Size-2-LastI loop
         Item(I) := 0;
      end loop;
   end Get;

   -- Writes a BigNum to the output, padding with leading spaces
   -- if the width given is larger than the length of the number
   -- (leading zeros are not printed).
   --
   procedure Put (Item : BigNum; Width : Natural := 1) is
      First : Integer := Size-1;
   begin
      -- Determine where the first digit of the number is,
      -- and thus the length of the number.
      for I in 0..Size-1 loop
         if Item(I) /= 0 then
            First := I;
            exit;
         end if;
      end loop;

      -- Put any leading blanks that are necessary.
      for I in Size-First+1..Width loop
         Put(' ');
      end loop;

      -- Write out the digits of the number.
      for I in First..Size-1 loop
         Put(Item(I), Width => 1);
      end loop;
   end Put;

end BigNumPkg;