% -----------------------------------------------------------------------------
%  (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
%-------------------------------------------------------------------------------
% Generic utility predicates.
%###############################################################################

%###############################################################################
% MODULE
%###############################################################################
:- module(newutilities, [atom_to_integer/2,
                         integer_to_atom/2,
                         spacer/1,
                         spacer/2,
                         implode_separator_content_list/3,
                         explode_separator_content_as_list/3,
                         pad_number_as_atom/3,
                         trim_atom/3,
                         generate_int_list/3,
                         flatten_list/2,
                         contains_no_dups/1,
                         month_numeric_to_name/2,
                         generate_integer/1,
                         unique_atom/2,
                         fetch_date_and_time/2]).

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

:- use_module(library(lists)).
:- use_module(library(system)).

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

:- use_module('ioutilities.pro',
              [throw_error/2]).


:- set_prolog_flag(double_quotes, chars).

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

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

:- add_state(get_base_unique_id,
             get_base_unique_id('Base_Atom', 'Int')).
:- dynamic(get_base_unique_id/2).


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



%===============================================================================
% atom_to_integer(+Atom, -Int).
%-------------------------------------------------------------------------------
% Convert an atom to an integer. Will raise an exception if the atom can
% not be transformed into an integer.
%===============================================================================

atom_to_integer(Atom, Int):-
    atom_chars(Atom, CharList),
    number_chars(Int, CharList),
    !.

%===============================================================================
% integer_to_atom(+Int, -Atom).
%-------------------------------------------------------------------------------
% Convert an integer to an atom. This is always possible.
%===============================================================================

integer_to_atom(Int, Atom):-
    number_chars(Int, CharList),
    atom_chars(Atom, CharList),
    !.

%===============================================================================
% spacer(+Int).
%-------------------------------------------------------------------------------
% Writes a number of spaces to the current output stream.
%===============================================================================

spacer(0):-
    !.

spacer(Int):-
    write(' '),
    Next_Int is Int-1,
    spacer(Next_Int).


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

spacer(_Stream, 0):-
    !.

spacer(Stream, Int):-
    write(Stream, ' '),
    Next_Int is Int-1,
    spacer(Stream, Next_Int).

%===============================================================================
% implode_separator_content_list(+Separator_Atom,
%                               +Item_AtomList,
%                               -Content_Atom).
%-------------------------------------------------------------------------------
% Given a separator atom (Separator_Atom) and a list of atoms
% (Item_AtomList) generates (Content_Atom) by joining together all of the
% atoms in the list, placing the separators in-between. Note that the
% separator atom may be empty '' -- thus this predicate may also be used to
% join a list of atoms.
%===============================================================================

% Empty case.
implode_separator_content_list(_Separator_Atom,
                               [],
                               ''):-
    !.

% Single case.
implode_separator_content_list(_Separator_Atom,
                               [Item_Atom],
                               Item_Atom):-
    !.

% More than one.
implode_separator_content_list(Separator_Atom,
                               [H_Item_Atom | T_Item_AtomList],
                               Content_Atom):-
    implode_separator_content_list_x(Separator_Atom,
                                     T_Item_AtomList,
                                     H_Item_Atom,
                                     Content_Atom),
    !.

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

% Last item.
implode_separator_content_list_x(_Separator_Atom,
                                 [],
                                 SoFar__Final__Atom,
                                 SoFar__Final__Atom):-
    !.

% Place separator between all non last items.
implode_separator_content_list_x(Separator_Atom,
                                 [H_Item_Atom | T_Item_AtomList],
                                 SoFar_Atom,
                                 Final_Atom):-
    atom_concat(SoFar_Atom, Separator_Atom, SoFarSeparator_Atom),
    atom_concat(SoFarSeparator_Atom, H_Item_Atom, NewSoFar_Atom),
    implode_separator_content_list_x(Separator_Atom,
                                     T_Item_AtomList,
                                     NewSoFar_Atom,
                                     Final_Atom).

%===============================================================================
%explode_separator_content_as_list(+Separator_Atom,
%                                  +Content_Atom,
%                                  -Item_AtomList).
%-------------------------------------------------------------------------------
% Given a separator atom (Separator_Atom) and content (Content_Atom) a list
% of atoms (Item_AtomList) is generated by splitting the content at each
% occurrence of the separator. The separator is not included in the item
% list.  If the separator is empty ('') then the content will be split
% between every character.
%===============================================================================

% Note that a simplistic two step solution is presented here, rather than
% trying to cleverly (and confusingly) mix the identification of separators
% and collection of items.
explode_separator_content_as_list(Separator_Atom,
                                  Content_Atom,
                                  Item_AtomList):-
    atom_chars(Separator_Atom, Separator_CharList),
    atom_chars(Content_Atom, Content_CharList),

    % Replace all occurrences of separator in content with the single atom
    % 'split'.
    replace_separator_with_split(Separator_CharList,
                                 Content_CharList,
                                 SplitContent_CharList),

    % Split off atoms at each occurrence of atom 'split'.
    split_off_atoms(SplitContent_CharList,
                    [],
                    Item_AtomList),
    !.

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

%No more content to consider.
replace_separator_with_split(_Separator_CharList,
                             [],
                             []):-
    !.

%If separator is empty ('') at last character, then do not add an extra
%split.
replace_separator_with_split([],
                             [H_Content_Char],
                             [H_Content_Char]):-
    !.

%If separator is empty ('') then split between every character.
replace_separator_with_split([],
                             [H_Content_Char | T_Content_CharList],
                             [H_Content_Char, split | T_SplitContent_CharList]):-
    replace_separator_with_split([],
                                 T_Content_CharList,
                                 T_SplitContent_CharList).

%Next block of context matches with separator. Make a split.
replace_separator_with_split(Separator_CharList,
                             Content_CharList,
                             [split | T_SplitContent_CharList]):-
    append(Separator_CharList, RemainingContent_CharList, Content_CharList),
    replace_separator_with_split(Separator_CharList,
                                 RemainingContent_CharList,
                                 T_SplitContent_CharList).

%From above, do not make a split here.
replace_separator_with_split(Separator_CharList,
                             [H_Content_Char | T_Content_CharList],
                             [H_Content_Char | T_SplitContent_CharList]):-
    replace_separator_with_split(Separator_CharList,
                                 T_Content_CharList,
                                 T_SplitContent_CharList).

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

%No more content to consider.
split_off_atoms([],
                Collected_CharList,
                [Collected_Atom]):-
    atom_chars(Collected_Atom, Collected_CharList),
    !.

%Is a split point.
split_off_atoms([split | T_SplitContent_CharList],
                Collected_CharList,
                [H_Item_Atom | T_Item_AtomList]):-
    atom_chars(H_Item_Atom, Collected_CharList),
    split_off_atoms(T_SplitContent_CharList,
                    [],
                    T_Item_AtomList).

%From above is not a split point.
split_off_atoms([H_SplitContent_Char | T_SplitContent_CharList],
                Collected_CharList,
                T_Item_AtomList):-
    append(Collected_CharList, [H_SplitContent_Char], NewCollected_CharList),
    split_off_atoms(T_SplitContent_CharList,
                    NewCollected_CharList,
                    T_Item_AtomList).

%===============================================================================
% pad_number_as_atom(Input_Int, PadToLength_Int, Padded_Atom).
%-------------------------------------------------------------------------------
%Given an integer (Input_Int) and a length (PadToLength_Int) the integer is
%converted to an atom and prefixed with zeros until the atom is the
%requested length.
%===============================================================================

pad_number_as_atom(Input_Int, PadToLength_Int, Padded_Atom):-
    number_chars(Input_Int, Input_CharList),
    length(Input_CharList, InputLength_Int),
    pad_number_as_atom_x(Input_Int,
                          Input_CharList,
                          InputLength_Int,
                          PadToLength_Int,
                          Padded_Atom),
    !.

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

% Number too big is an error.
pad_number_as_atom_x(Input_Int,
                      _Input_CharList,
                      InputLength_Int,
                      PadToLength_Int,
                      _Padded_Atom):-
    InputLength_Int>PadToLength_Int,
    throw_error('Can not pad number: ~k to size: ~k as number is too big.\n',
                [Input_Int,
                 PadToLength_Int]).

pad_number_as_atom_x(_Input_Int,
                      Input_CharList,
                      InputLength_Int,
                      PadToLength_Int,
                      Padded_Atom):-
    retrieve_padding(InputLength_Int,
                     PadToLength_Int,
                     Padding_CharList),
    append(Padding_CharList, Input_CharList, Padded_CharList),
    atom_chars(Padded_Atom, Padded_CharList),
    !.

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

retrieve_padding(Start__Stop__Int,
                 Start__Stop__Int,
                 []):-
    !.

retrieve_padding(Start_Int,
                 Stop_Int,
                 ['0' | T_Padding_CharList]):-
    NextStart_Int is Start_Int+1,
    retrieve_padding(NextStart_Int,
                     Stop_Int,
                     T_Padding_CharList).

%===============================================================================
% month_numeric_to_name(?Month_Atom, ?MonthName_Atom).
%===============================================================================
month_numeric_to_name('01', 'JAN').
month_numeric_to_name('02', 'FEB').
month_numeric_to_name('03', 'MAR').
month_numeric_to_name('04', 'APR').
month_numeric_to_name('05', 'MAY').
month_numeric_to_name('06', 'JUN').
month_numeric_to_name('07', 'JUL').
month_numeric_to_name('08', 'AUG').
month_numeric_to_name('09', 'SEP').
month_numeric_to_name('10', 'OCT').
month_numeric_to_name('11', 'NOV').
month_numeric_to_name('12', 'DEC').

%===============================================================================
% trim_atom(Atom, Trim_Int, Trimmed_Atom).
%-------------------------------------------------------------------------------
%Given an atom (Atom) and a length (Trimmed_Atom) any characters beyond the
%length are trimmed and the resulting atom is returned as (Trimmed_Atom).
%===============================================================================

trim_atom(Full_Atom, Trim_Int, Trimmed_Atom):-
    atom_chars(Full_Atom, Full_CharList),
    trim_atom_x(1, Trim_Int, Full_CharList, Trimmed_CharList),
    atom_chars(Trimmed_Atom, Trimmed_CharList),
    !.

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

% Halt at end of list.
trim_atom_x(_At_Int, _Stop_Int, [], []):-
    !.

% Halt when exceeding trim report.
trim_atom_x(At_Int, Trim_Int, _Full_CharList, []):-
    At_Int>Trim_Int,
    !.

% Continue otherwise.
trim_atom_x(At_Int,
            Trim_Int,
            [H_Full__Trimmed__Char | T_Full_CharList],
            [H_Full__Trimmed__Char | T_Trimmed_CharList]):-
    NextAt_Int is At_Int+1,
    trim_atom_x(NextAt_Int,
                Trim_Int,
                T_Full_CharList,
                T_Trimmed_CharList).

%===============================================================================
% generate_int_list(+Lower_Int, +Upper_Int, -IntList).
%-------------------------------------------------------------------------------
% Given a lower and upper integer, generate a list that includes every
% value (inclusive) between these values.
%===============================================================================

% Reached upper value.
generate_int_list(Lower_m_Upper_m_Int,
		  Lower_m_Upper_m_Int,
		  [Lower_m_Upper_m_Int]) :-
    !.

generate_int_list(Lower_Int,
		  Upper_Int,
		  [Lower_Int | T_IntList]) :-
        Lower_Int < Upper_Int,
        NextLower_Int is Lower_Int+1,
        !,
    generate_int_list(NextLower_Int,
		      Upper_Int,
		      T_IntList),
        !.

%===============================================================================
% flatten_list(+In_AnyList, -Out_AnyList).
%-------------------------------------------------------------------------------
% Given a list of any number of nested lists (In_AnyList), this retrieves
% every element as a single flat list (Out_AnyList).
%===============================================================================

flatten_list(In_AnyList, Out_AnyList):-
    flatten_list_x(In_AnyList, FirstOut_AnyList),
    !,
    Out_AnyList=FirstOut_AnyList,
    !.

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

flatten_list_x([], []).

flatten_list_x([H_In_Any | T_In_AnyList], Out_AnyList):-
    flatten_list_x(H_In_Any, FlatH_AnyList),
    flatten_list_x(T_In_AnyList, FlatT_AnyList),
    append(FlatH_AnyList, FlatT_AnyList, Out_AnyList).

% From above, is not a list. Return item in flat list.
flatten_list_x(In_m_Out_m_Any, [In_m_Out_m_Any]).

%===============================================================================
% contains_no_dups(+In_AnyList).
%-------------------------------------------------------------------------------
% Is successful where the provided list (In_AnyList) does not contain any
% duplicates.
%===============================================================================

contains_no_dups(In_AnyList):-
    list_to_set(In_AnyList, NoDup_AnyList),
    length(In_AnyList, LengthIn_Int),
    length(NoDup_AnyList, LengthNoDup_Int),
    LengthIn_Int=LengthNoDup_Int,
    !.

%===============================================================================
% generate_integer(Number_Int).
%-------------------------------------------------------------------------------
% Through backtracking, returns (Number_Int) as incremental additions from
% one.
%===============================================================================

generate_integer(1).

generate_integer(NowNumber_Int):-
    generate_integer(Number_Int),
    NowNumber_Int is Number_Int+1.

%===============================================================================
% unique_atom(+Base_Atom, -Unique_Atom).
%-------------------------------------------------------------------------------
% On successive invocations generates unique atoms as (Unique_Atom). The
% base portion of the unique atoms is specified as (Base_Atom).
%===============================================================================

unique_atom(Base_Atom, Unique_Atom):-
    retrieve_next_id(Base_Atom, Id_Int),
    integer_to_atom(Id_Int, Id_Atom),
    implode_separator_content_list('_',
                                   [Base_Atom, Id_Atom],
                                   Unique_Atom),
    !.

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

% Previous id is available.
retrieve_next_id(Base_Atom, Next_Int):-
    retract(get_base_unique_id(Base_Atom, Current_Int)),
    Next_Int is Current_Int+1,
    assert(get_base_unique_id(Base_Atom, Next_Int)),
    !.

% No previous id. Always start at 1.
retrieve_next_id(Base_Atom, 1):-
    assert(get_base_unique_id(Base_Atom, 1)),
    !.

%===============================================================================
% fetch_date_and_time(-Date_Atom, -Time_Atom).
%===============================================================================
% Date is of the form:  09/Jan/1980
% Time is of the form:  01:59:01
%===============================================================================

fetch_date_and_time(Date_Atom, Time_Atom):-
    datime(datime(Year_Int,
                  Month_Int,
                  Day_Int,
                  Hour_Int,
                  Min_Int,
                  Sec_Int)),
    pad_number_as_atom(Year_Int, 4, Year_Atom),
    pad_number_as_atom(Month_Int, 2, Month_Atom),
    pad_number_as_atom(Day_Int, 2, Day_Atom),
    pad_number_as_atom(Hour_Int, 2, Hour_Atom),
    pad_number_as_atom(Min_Int, 2, Min_Atom),
    pad_number_as_atom(Sec_Int, 2, Sec_Atom),

    month_numeric_to_name(Month_Atom, MonthName_Atom),

    implode_separator_content_list('-',
                                   [Day_Atom, MonthName_Atom, Year_Atom],
                                   Date_Atom),
    implode_separator_content_list(':',
                                   [Hour_Atom, Min_Atom, Sec_Atom],
                                   Time_Atom),
    !.


:- set_prolog_flag(double_quotes, codes).

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