with ada.text_io; use ada.text_io;

with ada.integer_text_io; use ada.integer_text_io;

-- When using gnat, requires -gnato option to enable overflow checks
procedure table5 is

    type Alignment is (left, center, right);

    package AlignmentIO is new Ada.text_io.enumeration_IO(Alignment);
    use AlignmentIO;

    ColumnWidth: constant Integer := 11;

    --------------------------------------------------------
    -- Input one integer
    --------------------------------------------------------
    procedure getOneItem(

        item: out Integer; 
        prompt: String; 
        lowerBound: Integer; 
        errorMsg: String) is

    begin
        loop
            begin
                put(prompt);
                get(item);

                exit when item > lowerBound;
                put_line(errorMsg);
            exception
                when Data_Error =>
                    new_line;
                    put_line("Must enter a valid integer value.");

                    -- Discard anything left on input line
                    skip_line;

                when others =>
                    new_line;
                    put_line("Unknown exception occurred.");
                    put_line("Bailing out!");
                    exit;
            end;
        end loop;
    end getOneItem;

    --------------------------------------------------------
    -- Input the alignment
    --------------------------------------------------------
    procedure getAlignment(prompt: string; align: out Alignment) is
    begin
        loop
            begin
                put(prompt);
                get(align);
                exit;

                exception
                    when Data_Error => 
                        put_Line("Invalid alignment");
            end;
        end loop;
    end getAlignment;


    --------------------------------------------------------
    -- Input all the data
    --------------------------------------------------------
    procedure getData(sI, fI, sP, fP: out Integer; align: out Alignment) is
    begin
        getAlignment("Enter alignment: ", align);

        getOneItem(sI, "Enter starting integer: ", 
                    -1, "Integer must be non-negative");

        getOneItem(fI, "Enter ending integer: ", 
                    sI, "Ending integer must be larger than starting");

        getOneItem(sP, "Enter starting power: ", 
                    -1, "Power must be non-negative");

        getOneItem(fP, "Enter ending power: ", 
                    sP, "Ending power must be larger than starting");
    end getData;
        

    --------------------------------------------------------
    -- Output the table's header rows
    -- Precondition: sPow, fPow in 1 .. 99
    --------------------------------------------------------
    procedure putHeader(sPow, fPow: Integer; align: Alignment) is
    begin
        -- Start with some white space
        new_line;

        -- First column
        if align = left then
            put("I          ");
            for i in sPow .. fPow loop
                put("I**");
                put(i, 1);

                if i in 1 .. 9 then 
                    put("       ");
                else
                    put("      ");
                end if;
            end loop;

        elsif align = center then
            put("      I    ");
            for i in sPow .. fPow loop
                put("    I**");
                put(i, 1);

                if i in 1 .. 9 then 
                    put("   ");
                else
                    put("  ");
                end if;
            end loop;

        else  -- must be right
            put("          I");
            for i in sPow .. fPow loop

                if i in 1 .. 9 then 
                    put("       I**");
                else
                    put("      I**");
                end if;

                put(i, 1);

            end loop;

        end if;


        -- Columns for powers
        new_line;

        -- Add one to fPow to account for column for I
        for i in sPow .. fPow + 1 loop
            put("-----------");
        end loop;
        new_line;

    end putHeader;


    --------------------------------------------------------
    -- Return the number of digits in i
    --------------------------------------------------------
    function length(i: Integer) return Natural is
    begin
        -- A quick and dirty method:
        -- Subtract 1 because of the leading space
        return Integer'Image(i)'length - 1;
    end length;



    --------------------------------------------------------
    -- New procedure
    -- Output v, aligned left, right, or center in ColumnWidth wide column
    --------------------------------------------------------
    procedure put(v: Integer; align: Alignment) is
        lenV: constant Integer := length(v);
        blanks: String(1..10) := (others => ' ');
        numLeft, numRight: Natural;
    begin
        --put(v); put(lenV);
        case align is
            when left =>
                put(v, 1);
                put(blanks(1 .. ColumnWidth - lenV));

            when right =>
                put(blanks(1 .. ColumnWidth - lenV));
                put(v, 1);

            when center =>
                numLeft := (ColumnWidth - lenV) / 2;
                numRight := ColumnWidth - lenV - numLeft;
                put(blanks(1 .. numLeft));
                put(v, 1);
                put(blanks(1 .. numRight));
        end case;
    end put;



    --------------------------------------------------------
    -- Output rows of the table from sInt to fInt,
    --     with powers from sPow to fPow,
    --     aligned as specified
    --------------------------------------------------------
    procedure putRows(sInt, fInt, sPow, fPow: Integer; 
        align: Alignment) is

        tempInt: Integer;
        overflow: Boolean;

    begin
        for i in sInt .. fInt loop

            put(i, align);

            for j in sPow .. fPow loop

                overflow := false;
                begin
                    tempInt := i ** j;
                exception
                    when Constraint_Error => 
                        overflow := true;
                end;

                if not overflow then 
                    put(tempInt, align);   -- MOdified statement
                else
                    put("   ********");
                end if;

            end loop;

            new_line;
        end loop;

    end putRows;

    -- Start and finish values for the rows
    startI, finishI: Integer;
    
    -- Start and finish values for the columns
    startPower, finishPower: Integer;

    -- Start and finish values for the columns
    align: Alignment;

begin

    getData(StartI, finishI, startPower, finishPower, align);

    putHeader(startPower, finishPower, align);
    
    putRows(startI, finishI, startPower, finishPower, align);

end table5;