% -----------------------------------------------------------------------------
%  (C) Altran Praxis Limited
% -----------------------------------------------------------------------------
% 
%  The SPARK toolset is free software; you can redistribute it and/or modify it
%  under terms of the GNU General Public License as published by the Free
%  Software Foundation; either version 3, or (at your option) any later
%  version. The SPARK toolset is distributed in the hope that it will be
%  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
%  Public License for more details. You should have received a copy of the GNU
%  General Public License distributed with the SPARK toolset; see file
%  COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
%  the license.
% 
% =============================================================================

%###############################################################################
% PURPOSE
%-------------------------------------------------------------------------------
% Provides general input and output utilities.
%###############################################################################

%###############################################################################
% MODULE
%###############################################################################
:- module(ioutilities, [read_line_from_stream/2,
                        read_up_to_number_of_chars_from_stream/3,
                        read_lines_from_file_as_char_list/3,
                        write_terms_to_file/2,
                        throw_error/2,
                        show_error/2,
                        show_error_long/4,
                        show_warning/2,
                        show_warning_long/4,
                        command_line_error/2,
                        display_header_full/1,
                        display_header_plain/1,
                        stopwith/1,
                        stopwith3/3]).

%###############################################################################
% DEPENDENCIES
%###############################################################################

:- use_module('data__formats.pro',
              [add_type/2]).

:- use_module('newutilities.pro',
              [implode_separator_content_list/3]).

:- use_module('data__system.pro',
              [get_system_toolname/1]).

:- use_module('../common/versioning/version.pro',
              [toolset_version/1,
               toolset_copyright/1,
               toolset_banner_line/1,
               toolset_distribution/1]).

:- use_module(library(file_systems), [close_all_streams/0]).

%###############################################################################
% TYPES
%###############################################################################

:- add_type('ReadText',
            ['CharList',
             end_of_file]).

:- add_type('ModuleFunctorArity',
            ['Module_Atom' : ('Functor_Atom'/ 'Arity_Int')]).

:- add_type('Lines',
            [everyLine,
             upToLine('Int')]).

:- add_type('Problem',
            [error,
             warning]).

%###############################################################################
% DATA
%###############################################################################

%###############################################################################
% PREDICATES
%###############################################################################

%===============================================================================
% write_atom_list_to_stream(+Stream, +AtomList).
%-------------------------------------------------------------------------------
% Writes out a list of atoms, each on a separate line, to the provided
% output stream (Stream).
%===============================================================================

%All done.
write_atom_list_to_stream(_Stream, []):-
    !.

%From above, more to write.
write_atom_list_to_stream(Stream, [H_Atom | T_AtomList]):-
    format(Stream, '~a\n', [H_Atom]),
    write_atom_list_to_stream(Stream, T_AtomList).

%===============================================================================
% read_line_from_stream(+Stream, -ReadText).
%-------------------------------------------------------------------------------
% Retrieves a list of characters or end_of_file (ReadText) from the
% provided input stream (Stream). The line is all characters up to the next
% new_line or end_of_file. The new_line character is never returned.
% Returns end_of_file if it is not preceded by any characters. (Thus, it is
% not possible to distinguish between chars|new_line|end_of_file and
% chars|end_of_file).
%===============================================================================




read_line_from_stream(Stream, ReadText):-
    read_line(Stream, Line_Any),
    read_line_from_stream_x(Line_Any, ReadText),
    !.

%-------------------------------------------------------------------------------

% Is end of file.
read_line_from_stream_x(end_of_file, end_of_file):-
    !.

% Is not end of file.
read_line_from_stream_x(CodeList, CharList):-
    atom_codes(Atom, CodeList),
    atom_chars(Atom, CharList),
    !.

%===============================================================================
% read_up_to_number_of_chars_from_stream(+Stream, +Int, -ReadText).
%-------------------------------------------------------------------------------
% Retrieves a list of characters or end_of_file (ReadText) from the
% provided input stream (Stream).  The line is the next (Int) characters,
% or all characters up to the next new_line, or end_of_file. The new_line
% character is never returned.  Returns end_of_file if it is encountered
% before (Int) chars or end_of_line -- with any 'pending' characters being
% silently thrown away.
%===============================================================================

read_up_to_number_of_chars_from_stream(Stream, Int, ReadText):-
    read_number_of_chars_from_stream_x(Stream, Int, CharList),
    convert_charlist_to_readtext(CharList, ReadText),
    !.

%-------------------------------------------------------------------------------

% Reached end.
read_number_of_chars_from_stream_x(_Stream, 0, []):-
    !.

read_number_of_chars_from_stream_x(Stream, Int, CharList):-
    get_char(Stream, Char),
    read_number_of_chars_from_stream_xx(Stream, Int, Char, CharList),
    !.

% Is end_of_file. Record this, and stop.
read_number_of_chars_from_stream_xx(_Stream, _Int, end_of_file, [end_of_file]):-
    !.

% Is newline. Do not record this, and stop.
read_number_of_chars_from_stream_xx(_Stream, _Int, '\n', []):-
    !.

% Is character. Record this and continue.
read_number_of_chars_from_stream_xx(Stream, Int, H_Char, [H_Char | T_CharList]):-
    Next_Int is Int-1,
    read_number_of_chars_from_stream_x(Stream, Next_Int, T_CharList),
    !.

%-------------------------------------------------------------------------------

% If encountered end_of_file, then only return end_of_file.
convert_charlist_to_readtext(CharList, end_of_file):-
        member(end_of_file, CharList),
    !.

% From above, did not encounter end_of_file.
convert_charlist_to_readtext(CharList, CharList):-
    !.

%===============================================================================
% read_lines_from_file_as_char_list(+File_Atom, +Lines, -CharList).
%-------------------------------------------------------------------------------
% Retrieves a specified number of lines (Lines) from file (File_Atom) as a
% list of characters (CharList). If Lines is everyLine then every line of
% the file will be returned. If Lines is upToLine(Int) then Int lines will
% be returned - or if the number of lines specified is greater than the
% number contained in the file, no error is raised, and all of the lines in
% the file will be returned. The list of characters is unfiltered, and so
% may contain instances of new line.
%===============================================================================

read_lines_from_file_as_char_list(File_Atom, Lines, CharList):-
    open(File_Atom, read, Stream),
    read_lines_from_file_as_char_list_x(Stream, Lines, 0, CharList),
    close(Stream),
    !.

%-------------------------------------------------------------------------------

%Reached line limit.
read_lines_from_file_as_char_list_x(_Stream, upToLine(LineAt_Int), LineAt_Int, []):-
    !.

%From above, not reached line limit.
read_lines_from_file_as_char_list_x(Stream, Lines, LineAt_Int, CharList):-
    get_char(Stream, NextChar_Any),
    read_lines_from_file_as_char_list_xx(Stream, Lines, LineAt_Int, NextChar_Any, CharList),
    !.

%-------------------------------------------------------------------------------

%Reached the end of the file.
read_lines_from_file_as_char_list_xx(_Stream, _Lines, _LineAt, end_of_file, []):-
    !.

%Reached a newline.
read_lines_from_file_as_char_list_xx(Stream, Lines, LineAt_Int, '\n', ['\n' | T_CharList]):-
    NextLineAt_Int is LineAt_Int + 1,
    read_lines_from_file_as_char_list_x(Stream, Lines, NextLineAt_Int, T_CharList),
    !.

%From above, neither the end of the file nor newline.
read_lines_from_file_as_char_list_xx(Stream, Lines, LineAt_Int, H_Char, [H_Char | T_CharList]):-
    read_lines_from_file_as_char_list_x(Stream, Lines, LineAt_Int, T_CharList),
    !.

%===============================================================================
% write_terms_to_file(+File_Atom, +ModuleFunctorArityList).
%-------------------------------------------------------------------------------
% Writes out to file (File_Atom) the contents of every predicate described
% by (ModuleFunctorArityList).
%===============================================================================

write_terms_to_file(File_Atom, ModuleFunctorArityList):-
    open(File_Atom, write, Stream),
    write_terms_to_file_x(Stream, ModuleFunctorArityList),
    close(Stream),
    !.

%-------------------------------------------------------------------------------

write_terms_to_file_x(_Stream, []):-
    !.

write_terms_to_file_x(Stream, [Module_Atom : (Functor_Atom / Arity_Int) |
                               T_ModuleFunctorArityList]):-
    write_terms_to_file_xx(Stream,
                           Module_Atom,
                           Functor_Atom,
                           Arity_Int),
    write_terms_to_file_x(Stream, T_ModuleFunctorArityList).


%-------------------------------------------------------------------------------

write_terms_to_file_xx(Stream,
                       Module_Atom,
                       Functor_Atom,
                       Arity_Int):-

    % Construct the predicate to call.
    functor(Predicate, Functor_Atom, Arity_Int),

    % Call and instantiate the predicate.
    Module_Atom:Predicate,
    format(Stream, '~q\n', Predicate),
    fail.

write_terms_to_file_xx(_Stream,
                       _Module_Atom,
                       _Functor_Atom,
                       _Arity_Int):-
    !.

%===============================================================================
% command_line_error(+FormattedText_Atom, +Arguments_AnyList).
%-------------------------------------------------------------------------------
% Raises an error.
%===============================================================================



command_line_error(FormattedText_Atom, Arguments_AnyList):-
    % Append common leading text.
    atom_concat('ERROR IN COMMAND LINE SYNTAX\n!!! Involving: ',
                FormattedText_Atom,
                LeadingFormattedText_Atom),
    show_error(LeadingFormattedText_Atom, Arguments_AnyList).

%===============================================================================
% show_error(+LineFormattedText_Atom, +LineArguments_AnyList).
%-------------------------------------------------------------------------------
% Raises an error (one line description).
%
% The line formatted text (LineFormattedText_Atom) should be on a single
% line, and thus never contain a newline. A single trailing newline is
% added automatically when displayed.
%===============================================================================


throw_error(LineFormattedText_Atom, LineArguments_AnyList):-
    show_error(LineFormattedText_Atom, LineArguments_AnyList),
    !.

show_error(LineFormattedText_Atom, LineArguments_AnyList):-
    highlight_problem(error,
                      user_output,
                      LineFormattedText_Atom,
                      LineArguments_AnyList,
                      [],
                      []),
    !.

%===============================================================================
%show_error_long(+LineFormattedText_Atom,
%                +LineArguments_AnyList,
%                +ParagraphFormattedText_Atom,
%                +ParagraphArguments_AnyList).
%-------------------------------------------------------------------------------
% Raises an error (one line plus longer description).
%
% The line formatted text (LineFormattedText_Atom) should be on a single
% line, and thus never contain a newline. A single trailing newline is
% added automatically when displayed. The paragraph formatted text
% (ParagraphFormattedText_Atom) may contain as many newlines as desired. No
% trailing newlines are automatically displayed.
%===============================================================================

show_error_long(LineFormattedText_Atom,
                LineArguments_AnyList,
                ParagraphFormattedText_Atom,
                ParagraphArguments_AnyList):-
    highlight_problem(error,
                      user_output,
                      LineFormattedText_Atom,
                      LineArguments_AnyList,
                      ParagraphFormattedText_Atom,
                      ParagraphArguments_AnyList),
    !.

%===============================================================================
% show_warning(+LineFormattedText_Atom, +LineArguments_AnyList).
%-------------------------------------------------------------------------------
% Raises an warning (one line description).
%
% The line formatted text (LineFormattedText_Atom) should be on a single
% line, and thus never contain a newline. A single trailing newline is
% added automatically when displayed.
%===============================================================================

show_warning(LineFormattedText_Atom, LineArguments_AnyList):-
    highlight_problem(warning,
                      user_output,
                      LineFormattedText_Atom,
                      LineArguments_AnyList,
                      [],
                      []),
    !.

%===============================================================================
%show_warning_long(+LineFormattedText_Atom,
%                +LineArguments_AnyList,
%                +ParagraphFormattedText_Atom,
%                +ParagraphArguments_AnyList).
%-------------------------------------------------------------------------------
% Raises an warning (one line plus longer description).
%
% The line formatted text (LineFormattedText_Atom) should be on a single
% line, and thus never contain a newline. A single trailing newline is
% added automatically when displayed. The paragraph formatted text
% (ParagraphFormattedText_Atom) may contain as many newlines as desired. No
% trailing newlines are automatically displayed.
%===============================================================================

show_warning_long(LineFormattedText_Atom,
                  LineArguments_AnyList,
                  ParagraphFormattedText_Atom,
                  ParagraphArguments_AnyList):-
    highlight_problem(warning,
                      user_output,
                      LineFormattedText_Atom,
                      LineArguments_AnyList,
                      ParagraphFormattedText_Atom,
                      ParagraphArguments_AnyList),
    !.

%===============================================================================
%highlight_problem(+Problem,
%                  +LineFormattedText_Atom,
%                  +LineArguments_AnyList,
%                  +ParagraphFormattedText_Atom,
%                  +ParagraphArguments_AnyList).
%-------------------------------------------------------------------------------
% Support predicate, to centrally manage all problem messages. The form of
% formatted text and arguments should conform to the standard format
% predicate.  Note that the description line (LineFormattedText_Atom)
% should never contain embedded new lines, as this is handled here. The
% extended description (ParagraphFormattedText_Atom) may contain as many
% embedded newlines as desired and will not have a trailing newline
% automatically inserted.
%===============================================================================




% On error: display problem and halt with error code 1.
highlight_problem(error,
                  Stream,
                  LineFormattedText_Atom,
                  LineArguments_AnyList,
                  ParagraphFormattedText_Atom,
                  ParagraphArguments_AnyList):-

    % Get the tool name.
    get_system_toolname(ToolName_Atom),

    % Double line break.
    format(Stream, '~n~n', []),

    % Make it clear which system encountered the problem.
    format(Stream, '*****************************************************************************~n', []),
    format(Stream, '* An error has occurred in the ~a~n', [ToolName_Atom]),

    % Write out the error string that is detected by sparksimp.
    format(Stream, '*** ERROR - ', []),

    % Display the one-line problem description.
    format(Stream, LineFormattedText_Atom, LineArguments_AnyList),
    format(Stream, '~n', []),

    % Display any additional problem description parts.
    format(Stream, ParagraphFormattedText_Atom, ParagraphArguments_AnyList),

    % Close block.
    format(Stream, '*****************************************************************************~n', []),

    % Double line break.
    format(Stream, '~n~n', []),

    close_all_streams,
    halt(1),
    !.

% On warning: display problem and continue.
highlight_problem(warning,
                  Stream,
                  LineFormattedText_Atom,
                  LineArguments_AnyList,
                  ParagraphFormattedText_Atom,
                  ParagraphArguments_AnyList):-
    % Get the tool name.
    get_system_toolname(ToolName_Atom),

    % Double line break.
    format(Stream, '~n~n', []),

    % Make it clear which system encountered the problem.
    format(Stream, '*****************************************************************************~n', []),
    format(Stream, '* A warning was encountered in the ~a~n', [ToolName_Atom]),

    % Write out a warning string. This is not processed by any external
    % tool.
    format(Stream, '*** WARNING - ', []),

    % Display the one-line problem description.
    format(Stream, LineFormattedText_Atom, LineArguments_AnyList),
    format(Stream, '~n', []),

    % Display any additional problem description parts.
    format(Stream, ParagraphFormattedText_Atom, ParagraphArguments_AnyList),

    % Close block.
    format(Stream, '*****************************************************************************~n', []),

    % Force double line break.
    format(Stream, '~n~n', []),
    !.

%===============================================================================
% stopwith(+Text_Atom).
%-------------------------------------------------------------------------------
% Raise an error.
%===============================================================================





stopwith(Text_Atom) :-
    implode_separator_content_list('',
                                   [Text_Atom, '\n', '%PRESIMP-F-ABORT, Presimplification terminated.', '\n'],
                                   Content_Atom),
    throw_error(Content_Atom, []).

%===============================================================================
% stopwith3(+X, +Y, +Z).
%-------------------------------------------------------------------------------
% Raise an error.
%===============================================================================



stopwith3(X, Y, Z) :-
        name(X, XL),
        name(Y, YL),
        name(Z, ZL),
        append(YL, ZL, IL),
        !,
        append(XL, IL, RL),
        name(R, RL),
        !,
        stopwith(R).

%===============================================================================
% display_header_full(+Stream).
%-------------------------------------------------------------------------------
% Display the header in full.
%===============================================================================

display_header_full(Stream):-
    % Grammar for the toolset wide standard banner lines (not in plain mode):
    %
    % BANNER       := "SPARK . " " .
    %                 TOOLNAME . " " . BANNERLINE . <NEWLINE> .
    %                 COPYRIGHT . "<NEWLINE>"
    %
    % TOOLNAME     := Value of get_system_toolname.
    % BANNERLINE   := Value of toolset_banner_line from versioning.
    % COPYRIGHT    := Value of toolset_copyright from versioning.
    get_system_toolname(ToolName_Atom),
    toolset_banner_line(BannerLine_Atom),
    toolset_copyright(Copyright_Atom),

    format(Stream, 'SPARK ~a ~a~n~a~n',
           [ToolName_Atom,       % SPARK ~a
            BannerLine_Atom,     % ~a
            Copyright_Atom       % ~n~a
            ]),                  % ~n
    !.

%===============================================================================
% display_header_plain(+Stream).
%-------------------------------------------------------------------------------
% Display the plain header.
%===============================================================================

display_header_plain(Stream):-
    % Grammar for the toolset wide standard banner lines (in plain mode):
    %
    % BANNER       := "SPARK . " " .
    %                 TOOLNAME . " " . DISTRIBUTION . " Edition, " . "<NEWLINE>" .
    %                 "<NEWLINE>"
    %
    % TOOLNAME     := Value of get_system_toolname.
    % DISTRIBUTION := Value of toolset_distribution from versioning.
    get_system_toolname(ToolName_Atom),
    toolset_distribution(Distribution_Atom),

    format(Stream, 'SPARK ~a ~a Edition~n~n',
           [ToolName_Atom,       % SPARK ~a
            Distribution_Atom    %  ~a
            ]),                  %  Edition~n~n
    !.

%###############################################################################
% END-OF-FILE
