%--------------------------------------------------------------
% File: proc.pl                        17/4/1998

% Top program to deal with one process at a time.

% This is a formal interpretation of Procedure Models, there is
% a close mapping to process_procedure model. It is at the 
% operation level of a process.

% Algorithm of executing a process:
% 1. examine all preconditions, if one is not satisfied then
%    report error, otherwise
% 2. if all preconditions are true, then carry out the process actions,
%    and check that all postconditions are true, otherwise,
% 3. if any postconditions are false then report error as well.

% I use Process_type and Tri_id to find all involved entity functions .
% And I also use the text content embedded 
% in a precondition to decide what should be done.

% user-interface, to get on-line input sentences from the user.
:- [inout].

% exec_process((+begin_state, +end_state), 
%               +Process_type, +Tri_id, 
%               +Trigger_task_list, 
%               +(Begin_time, End_time), 
%               -execution_result)

% (1) check all preconditions
% If the user has specified special condition statements for the
% process type, then use the user specified conditions.
% Otherwise, use the generic condition statements !!
% current_predicate(_, Predicate) is a built-in predicate 
% which checks for the existence of the Predicate.

% format of preconditons:
% 1. userdefined preconditions -> process specified
%     precondition(Process_type, Ent_fun, L)
% 2. default preconditions -> process not specified
%     precondition(Ent_fun, L)

exec_process(_states, Process_type, Tri_id, _content, (_, E), error_pre) :-

   write('--- calling exec_process preconds ---'), nl,

 (  % exists user defined conds, use specific conds
    current_predicate(_, precondition(Process_type, _, _)), 
    findall(pre(Process_type, Tri_id, Occ, L), 
          ( dyn(ent_fun(Process_type, Tri_id, Ent_fun, Occ)),
            precondition(Process_type, Ent_fun, L)
          ),
	    List);
    % no user defined conds, then use generic conditions            
    \+ current_predicate(_, precondition(Process_type, _, _)), 
    findall(pre(Process_type, Tri_id, Occ, L), 
          ( dyn(ent_fun(Process_type, Tri_id, Ent_fun, Occ)),
            precondition(Ent_fun, L)
          ),
          List)
  ),

  test_precond(List, Error), % find failed preconds
  Error = [ErrOcc, ErrMsg],
  write('* Error: one precondition requirement failed:'), nl,
  report_error(ErrMsg),nl,
  write('Corresponding Occurrence: '), nl,write(ErrOcc),nl,nl,
  report_error(['Process:', Process_type]),
  report_error(['Process ID:', Tri_id]), 
  report_error(['Time:', E]), !.

%% (2) execute process actions and no failed postconds.
exec_process(States, Process_type, Tri_id, Content, Time, ok) :-

  write('--- exec_process pre-cond cl-2 ---'), nl,

  exec_process1(States, Process_type, Tri_id, Content, Time),

  write('--- exec process successfully ---'),nl,

  %% add all relevent entity occurrences to the process scope
  add_ref_obj(Process_type, Tri_id),
  
  ( current_predicate(_, postcondition(Process_type, _, _)), 
    findall(post(Process_type, Tri_id, C2, L2), 
           (dyn(ent_fun(Process_type, Tri_id, Ent_fun, C2)),
            postcondition(Process_type, Ent_fun, L2)            
           ),
	    List2);
    \+ current_predicate(_, postcondition(Process_type, _, _)),
    findall(post(Process_type, Tri_id, C2, L2), 
           (dyn(ent_fun(Process_type, Tri_id, Ent_fun, C2)),
            postcondition(Ent_fun, L2)   
        ),
        List2)
  ),
  test_postcond(List2, []), !.   


% (3) check postconditions
% note: Post-condition is supposed to be not to fail, since 
% Prolog can always assert instances, therefore the actions of 
% processes should not fail normally.

exec_process(_, Process_type,Tri_id, _content, (_, End), error_post) :- 

  write('--- exec_process pre-cond cl-3 ---'), nl,

  ( current_predicate(_, postcondition(Process_type, _, _)),
    findall(post(Process_type, Tri_id, C2, L2), 
           (dyn(ent_fun(Process_type, Tri_id, Ent_fun, C2)),
            postcondition(Process_type, Ent_fun, L2)            
           ),
	    List2);
    \+ current_predicate(_, postcondition(Process_type, _, _)),
    findall(post(Process_type, Tri_id, C2, L2), 
           (dyn(ent_fun(Process_type, Tri_id, Ent_fun, C2)),
            postcondition(Ent_fun, L2)   
        ),
        List2)
  ),

  test_postcond(List2, Error), % find failed postconds.
  Error = [ErrOcc, ErrMsg],
  report_error(['Error - one postcondition fail:']),nl,
  report_error(ErrMsg),nl,
  write('Corresponding Occurrence: '), nl,write(ErrOcc),nl,nl,
  report_error(['Time:', End]),
  report_error(['Process type:', Process_type]),
  report_error(['Process ID:', Tri_id]).

%------------------------------------------------------------
% Top predicate of process action execution.
% Note that the actions are stored in a list, and each of 
% them will be carried one by one.

% exec_process1((+Previous_state, +Current_state), +Process,
%                +Process_id,     +Action_list, 
%               (+Begin_time, +End_time) )

exec_process1(_states, _P, _ID, [], _time).

% (1) create originating focal, normal, if entities

exec_process1((S, S1), Process_type, Tri_id, [Content|Rest], (Begin, End)) :-

   write('--- calling exec_process1 ---'),nl,

   ( Content = originate_focal_entity; 
     Content = originate_normal_entity;
     Content = originate_if_entity ),

   dyn(ent_fun(Process_type, Tri_id,
                     Content, Obj)),
   assert(Obj),
   assert(dyn(occ_begin(Obj, End))),
   assert(dyn(change((S, S1), (Process_type, Tri_id),
          [],
          [Obj,
           dyn(occ_begin(Obj, End))] 
         ))),

   add_att_to_occ(Obj, (Process_type, Tri_id) ),
   exec_process1((S, S1), Process_type, Tri_id, Rest, (Begin, End)).

% (2) update attributes of change focal/normal entities
% work

%% no old att
exec_process1((S, S1), Process_type, Tri_id, [Content|Rest], (Begin, End)) :-

   Content =.. [Head, Att, nil, New],
   ( Head = change_focal_entity; 
     Head = change_normal_entity ),

   dyn(ent_fun(Process_type, Tri_id, Head, Occ)),

   Add = dyn(ent_occ_att(Occ, (Att, New))),
   assert(Add),
   assert(dyn(change((S, S1), (Process_type, Tri_id),
          [], [Add]))),
   exec_process1((S, S1), Process_type, Tri_id, Rest, (Begin, End)).

%% has old att
exec_process1((S, S1), Process_type, Tri_id, [Content|Rest], (Begin, End)) :-

   Content =.. [Head, Att, Old, New], 
   \+ Old = nil,

   dyn(ent_fun(Process_type, Tri_id, Head, Occ)),
   ( Head = change_focal_entity; 
     Head = change_normal_entity ),

   dyn(ent_occ_att(Occ, (Att, Old))),
   Del = dyn(ent_occ_att(Occ, (Att, Old))),
   retract(Del),

   Add = dyn(ent_occ_att(Occ, (Att, New))),
   assert(Add),
   assert(dyn(change((S, S1), (Process_type, Tri_id),
          [Del], [Add]))),
   exec_process1((S, S1), Process_type, Tri_id, Rest, (Begin, End)).

%--------------------- Dedicated Predicates ----------------
% add all relevant entity occurrences to the process scope

add_ref_obj(P, ID) :-
   \+ var(P),
   \+ var(ID),
   dyn(ent_fun(P, ID, Ent_fun, Obj)),
   Del = dyn(ent_fun(P, ID, Ent_fun, Obj)),
   
   create_name(Ent_fun, Obj_ent),
   Form =.. [Obj_ent, P, ID, Obj],
   Add = dyn(Form),
  
   assert(Add),
   retract(Del),
  
   dyn(current_state(S1)),
   read_next_state(S2),
   assert(dyn(change( (S1, S2), (P, ID), [Del], [Add]
         ))),
   add_ref_obj(P, ID).

add_ref_obj(P, ID) :-
   \+ dyn(ent_fun(P, ID, _Ent_fun, _Obj)).
%------------------------------------------------------------
%% need programming, debugging

%% Retrieve attribute value of occurrence from the user
%% and add them to the working memory.
%%% WARNING: assuming 1 or 2 parents policy

%% (1) Derivable attributes
%% WARNING: no validation for the att values for derivable atts.

add_att_to_occ(Occ, (Process, PID) ) :-
   Occ = dyn(ent_occ(Ent, ID, Parents)),
   Occ_att = dyn(ent_occ_att(Occ, Att, Value)),
   \+ current_predicate(_, Occ_att), %% The created att must not existed !!
   derived_att(Occ_att, Other_ents, Description), % identify att+rules
   calculate_derived_att(Occ_att, Other_ents, Description), 
   assert_att(Occ_att, (Process, PID)),
   add_att_to_occ(Occ, (Process, PID)), !.  % calculate the next derivable att

%% (2) None derivable attributes !!

add_att_to_occ(Occ, (Process, PID) ) :-
   Occ = dyn(ent_occ(Ent, ID, [Ent1, Ent2])),
   Occ_att = dyn(ent_occ_att(Occ, Att, Value)),
   attribute(Ent, Att, LandMark),
   \+ current_predicate(_, Occ_att), %% The created att must not existed !!
   \+ derived_att(Occ_att, Other_ents, Description), 
   get_user_att_value(Occ, Att, Value, LandMark),  %% IO to user for dynamic input
   assert_att(Occ_att, (Process, PID)),
   add_att_to_occ(Occ, (Process, PID)), !.  % calculate the next derivable att

add_att_to_occ(_, _).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% local sub-predicate, need debugging. 

get_user_att_value(Occ, Att, Value, LandMark) :-
   write('*** Please give initial attribute value ***'),nl,
   write('Occurrence: '),write(Occ), nl,
   write('Attribute: '),write(Attribute), nl,
   read(Value),nl,
   valid_att_value(Value, LandMark),!.

get_user_att_value(Occ, Att, Value, LandMark) :-
   get_user_att_value(Occ, Att, Value, LandMark).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% calling derive.pl
%% Allowing multi-descriptions, stored in [X|Descriptions], 
%% and that only one "simple" function in each of this
%% descriptions !! 
%% All results are returned to Occ_att, and descriptions
%% automatically. 

calculate_derived_att(_, _, []).

calculate_derived_att(Occ_att, Other_ents, [X|Descriptions]) :-
    phrase(statement(Out), X),                    % parse one description
    calculate_one_des(Occ_att, Other_ents, Out),  % calculate one des at a time.
    calculate_derived_att(Occ_att, Other_ents, Descriptions).
%%%
% calling derive.pl

calculate_one_des(Occ_att, Other_ents, Out) :-
    member(function(Fun), Out), % only one function per description
    exec_function(Fun, Out).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%  assert derived attributes, and mark the changes of states

assert_att(Occ_att, (Process, PID) ) :-
   dyn(current_state(S)),
   read_next_state(S1),
   assert(Occ_att),
   assert(dyn(change((S, S1), (Process, PID), [],
                    [Occ_att] ))).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% note that landmark_list field can only be string, 
%% integer, number, or a list of predetermined landmark 
%%  values. 

valid_att_value(Value, string) :- !.
valid_att_value(Value, integer) :-
   integer(Value), !.
valid_att_value(Value, number) :-
   number(Value), !. 
valid_att_value(Value, LandMark) :-
   member(Value, LandMark).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Library Function

get_choice(Choice, List, Res) :-
   get_choice1(0, Choice, List, Res).

get_choice1(Choice,  Choice, [X|_], X).
get_choice1(Current, Choice, [_|L], Res) :-
   Next is Current + 1,
   get_choice1(Next, Choice, L, Res).
%-----------------------------------------------------------   
%            Test pre-conditions
%-----------------------------------------------------------   
% test_precond(+Preconds_list, -Error_precond)

% 1. all preconds are satisfied

test_precond([], []).

% 2. found a failed precond, return error-precond

test_precond([pre(Process_type, Tri_id, Occ, List)|_], [Occ, List]) :-
  \+ exec_precondition(Process_type, Tri_id, Occ, List).

% 3. succeeds a precond, test the next one.

test_precond([pre(Process_type, Tri_id, Occ, List)|L], Error) :-
  exec_precondition(Process_type, Tri_id, Occ, List),
  test_precond(L, Error).

%-----------------------------------------------------------   
%            Test post-conditions
%-----------------------------------------------------------   
% If any one requirements for any trigger failed, then 
% test_postcond succeed and returns the failed condition.

test_postcond([], []).

test_postcond([post(Process_type, Tri_id,  Occ, List)|_], [Occ,List]) :-
  \+ exec_postcondition(Process_type, Tri_id, Occ, List).

test_postcond([post(Process_type, Tri_id,  Occ, List)|Rest], Error) :-
  exec_postcondition(Process_type, Tri_id, Occ, List),
  test_postcond(Rest, Error).

%-----------------------------------------------------------   
%                       local predicates 
%-----------------------------------------------------------
%%%%%
write_menu([], _) :- nl.
write_menu([V|List], Pos) :-
   write(Pos), write(':'), write(V),write('  '),
   Pos1 is Pos + 1,
   write_menu(List, Pos1).

%%%%%
special_ave(List, Ave) :-
  length(List, Len),
  special_sum(List, 0, Sum),
  Ave is Sum / Len.
  
/*
special_ave([(1,1), (2,2), (3,3), (4,4),(5,5)], Ave).
*/

%% 
special_sum([], Res, Res).
special_sum([(_, X)|L], Sofar, Res) :-
  New is X + Sofar,
  special_sum(L, New, Res).

%------------------------------------------------------------
% predicate library
%------------------------------------------------------------
% Transform input string into specific output string.
% e.g. input  = originate_focal_entity, 
% then output = occ_originate_focal (which is the head
% of reference occurrence of a process.)

create_name(Ent_fun, Obj_ent) :-
   name(Ent_fun, Ent_code), % embedded function name
   del_sublist(Ent_code, Mid),
   name(Obj_ent, [111,99,99,95|Mid]).

%------------------------------------------------------------
% NOTE: name(occ_, L).  L = [111,99,99,95] ?  and
% name('_entity', L).  L = [95,101,110,116,105,116,121] ?  

del_sublist(L, []) :-
  L = [95,101,110,116,105,116,121].

del_sublist([X|T], [X|Rest] ) :-
  Y = [95,101,110,116,105,116,121],
  \+ [X|T] = Y,
  del_sublist(T, Rest).
%-----------------------------------------------------------   
%                     common predicates 
%-----------------------------------------------------------   
% A modification of write list

report_error([]) :- nl. 
report_error([X|L]) :-
 write(X), write(' '),
 report_error(L). 

%%%

ancester(Child, Parent) :-
  Child = dyn(ent_occ(_, _, Parents)), 
  member(Parent, Parents).

ancester(Child, Ancester) :-
  Child = dyn(ent_occ(_, _, Parents)),
  member(Parent, Parents),
  ancester(Parent, Ancester).

%% 
set([], []).
set([X|L], [X|Res] ) :-
  \+ member(X, L),
  set(L, Res).

set([X|L], Res ) :-
  member(X, L),
  set(L, Res).
%-------------------- End of Document -------------------   
