/***************************************************************** 
File: top.pl                             June 2002 
****************************************************************** 

Hierarchical view of predicates in system:

top (top predicate of the system)
   -> meta_match/exec_rule (meta-rules manage all process)
     -> exec_originate/change_rule (occ-rules manage process occs)
       -> exec_pred/postconditions (occ-rules manage entity  occs)

-----------------------------------------------------------
Top Rule to call system: top(+Step_to_execute)

System Predicates:
Note that Begin_time usually starts from 0.

dyn(state(ID, Time)).
dyn(current_state(ID)).
dyn(current_time(Time)).

All entity/process instances are dynamic occs.
Time and states is managed by top level system predicates.
------------------------------------------------------------
Definition of predicates:

dyn(
  agenda(process(Process_type, Process_id, Actions, 
                (Begin_time, End_time)
        ) ).

dyn(state_modified(true)).
------------------------------------------------------------
Note that all trigger IDs are unique; a trigger ID becomes
a process ID of a process which this trigger invokes. 
------------------------------------------------------------
File Hierarchy:

-> top.pl (process model simulation)

    -> library.pl   % predicate library
    -> store.pl     % save the current state

    -> conflict.pl  % process conflict detector
 *  -> cond.pl      % check the pre- and post-conditions
 *  -> exe.pl       % execute the processes
    -> derive.pl    % handle deriving atts. 

  -> model.pl       % reverse and travel between states

*********************************************************************/

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% programs body 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

/*
:- working_directory(_,
          'C:/Documents and Settings/jessicac/Desktop/sim-fbpml/').

*/ 

:- working_directory(X, X). 

:- multifile dyn/1.
:- dynamic   dyn/1. 
:- abolish(dyn/1), assert(dyn(initial)). 

:- [library].    % load utility program 
:- [cond, exe].  % load WFE modules. 

:- [comm].       % load communication modules 

% load process model 
:- ['./data/process.db'].    % load processes 
:- ['./data/junction.db'].   % load junctions
:- ['./data/entity.db'].     % load entity info 
:- ['./data/event.db'].      % load initial event 

% load initial system state info 
:- ['./data/init.db'].       
:- ['./data/event.db'].       
:- ['./data/dynamic.db'].       


% load problem domain 
:- ['./domain/share.db'].      % load domain description - shared 
:- ['./domain/edinburgh.db'].  % load domain description - edinburgh 


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- nl,nl,nl,
   write('**********************************************************'),
   nl,nl, 
   write('Welcome to The Workflow Engine based on FBPML'),nl,
   write('This simulator is a part of the tool KBST-BM.'), 
   nl,nl, 
   write('If you want to run one step at a time, type run. '), nl, 
   write('To run several steps at a time, type top(X). '),nl,
   write('where X is the number of steps required for enactment.'),nl,
   write('To see the knowledge base, type look. '),
   nl,nl, 
   write('**********************************************************'),
   nl, nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% top(+Step)
% Top Rule for dynamic model reasoning/simulation
% When called, the simulator will run a specified number of Steps.
%
% operates at time and states level
% export_data_to_archive is given in store.pl 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

run :-  top(1). 

%% (1) end of enactment 
top(Step) :-
    Step =< 0, 
    dyn(current_time(Time)),
    W is Time - 1, 
    write('* Enactment stops at the end of time: '), 
    write(W),nl. 
    % export_data_to_archive, !. 

%% (2) execute processes
top(Step) :-
    Step > 0,
    \+     dyn(flag(done_execution)), 
    assert(dyn(flag(done_execution))),  
    meta_exec_process, 
    top(Step).

%% (3) find matching processes 
top(Step) :-
    Step > 0,
    \+     dyn(flag(done_matching)), 
    assert(dyn(flag(done_matching))), 
    meta_match_process,
    top(Step).


%% (4) update time and state_id, and get new triggers
top(Step) :-

    dyn(flag(done_matching )), 
    dyn(flag(done_execution )), 
    retract(dyn(flag(done_matching ))), 
    retract(dyn(flag(done_execution ))), 

    Step > 0,
    Current_step is Step - 1,
    
    dyn(current_time(Time)),

    writeln('**************************************'),
    writeln('* Finish one enactment step/time-unit. '),
      write('* Current time is: '), writeln(Time), 
    writeln('**************************************'),

    % update time
    Newtime is Time + 1,
    retract(dyn(current_time(Time))),
    assert(dyn(current_time(Newtime))),

    % If any changes are made to the state, update state.
    % Otherwise, do not. 

    (          dyn(flag(state_changed)), 
       retract(dyn(flag(state_changed ))), 
       update_state;  
       true
    ),
  
%   query_for_adding_triggers,
    top(Current_step). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 1. identify all external events
% 2. check and add all eligible processes to agenda 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

meta_match_process :-

        write('*************************************************'),nl,
        write('* meta_match_process: Searching for events '),nl,
        write('*************************************************'),nl,

        dyn(event_occ(Instance, Event, TStatus, Priority, 
                      Begin_time/End_time, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                       Event_content)), 

        E = dyn(event_occ(Instance, Event, TStatus, Priority, 
                Begin_time/End_time, 
                (Provider/Provider_type, Receiver/Receiver_type), 
                 Event_content)), 

        writeln(E), 

        %% (1) check timing 
        dyn(current_time(Time)), Time >= Begin_time,

        TStatus = Status/_IDlist, 

        \+ Status = suspended, \+ Status = cancelled, 
        \+ Status = finished, 

        write('-- the timing is right: '), writeln(Time), 

        %%% Identify and Add triggered process to process agenda 
        add_process_to_agenda(E), 

        %% to allow all eligible events to be processed in the same state
        fail. 


meta_match_process. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Given one external event, trigger ALL relevant processes and 
% add them to the agenda, if appropriate. 
% 
% Note: in the current implementation, a process may or may not 
%       have a trigger. 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

add_process_to_agenda(E) :- 
    
  write('>>>>> calling add_process_to_agenda '), nl, 
  read(_), nl,

  %%%%%% get event

  E = dyn(event_occ(Instance, _Event, EStatus, Priority, _Begin/_End, 
          (Provider/Provider_type, Receiver/Receiver_type), 
          _Event_content )), 

  writeln('--- got event '), 
  writeln(E), 

  E =.. [dyn | Trigger], 
  EStatus = _/_ID_list, 

  %%%%% get process 

  process(Instance, Process/PID, PStatus, Priority, 
          _/_, Time_cost, 
          (Provider/Provider_type, Receiver/Receiver_type), 
          P_Trigger, Precond, Action, Postcond), 

  P =  process(Instance, Process/PID, PStatus, Priority, 
               _/_, Time_cost, 
               (Provider/Provider_type, Receiver/Receiver_type), 
               P_Trigger, Precond, Action, Postcond), 

  writeln('--- got process --- '), 
  writeln(P), 
  read(_), nl, 

  %%% check if trigger(s) are true or not %%% 

    ( P_Trigger = [true]; 
      P_Trigger = Trigger ;
      \+ P_Trigger = Trigger, 
      test_all_cond( P_Trigger) ), 

  write('--- the trigger is valid  '),nl, 
  writeln(P), 

  %%% match junctions: if it is followed by a start junction, or 
  %%%                  if it is followed by a control-link, or 
  %%%                  if it is followed by an and-join junction, or 
  %%%                  if it is followed by an and-split junction, or 
  %%%                  if it is followed by an or-join junction, or 
  %%%                  if it is followed by an or-split junction, 
  %%% and it fits the process, add it to the agenda

    ( EStatus = received/[], start(PID), ! ;
      (
        EStatus = _/List, control_link(A, PID),
        member(A, List) ; 

        EStatus = _/List,  or_join(Alist, PID), 
        member(X, List), member(X, Alist); 

        EStatus = _/List, and_split(A, Blist), 
        member(A, List), member(PID, Blist);

        EStatus = _/List, or_split(A, Blist), 
        member(A, List), member(PID, Blist);

        EStatus = _/List, and_join(Alist, PID), 
        subset(Alist, List)   )), 

  write('--- Pass junction'),nl, 
  writeln(EStatus), 

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % record that the process has been triggered by the event 

  \+ dyn(triggered_process(Instance, PID )), 
  assert(dyn(triggered_process(Instance, PID ))), 
   
  writeln('--- the process is triggered'), 

  %%% update process time, and %%%%%%%%%%%%%%%%%%%%
  %%% add process to process agenda 

      dyn(current_time(Time)), 
      Begin_time = Time,       
      End_time is Time + Time_cost,

      NewP =  process(Instance, Process/PID, PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                     (Provider/Provider_type, Receiver/Receiver_type), 
                      P_Trigger, Precond, Action, Postcond), 

      assert(dyn(agenda(NewP))), 

  writeln('--- add process to agenda'), 
  writeln(NewP), 
  read(_), nl, 

  %%% inspect all processes for the same event. 
  fail. 

add_process_to_agenda(_). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% meta_exec_process
%
% Function: Allow the user to execute all of the qualified processes.
% Note: The unique event_id later on becomes a process ID
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

meta_exec_process :-

        write('***************************************************'),nl,
        write(' meta_exec_process: execute processes in the agenda'),nl,
        write('***************************************************'),nl,
        read(_), nl, 
 
        % find all processes in the agenda ready to be executed
        findall( (Process, Instance), 
                 ( dyn(agenda(process(Instance, Process/_PID, 
                                _PStatus, _Priority, 
                                _Begin_time/End_time, _Time_cost, 
                          (_Provider/_Provider_type, _Receiver/_Receiver_type), 
                           _P_Trigger, _Precond, _Action, _Postcond ))), 
                   dyn(current_time(Time)),        % check time 
                   Time >= End_time ), 
                   Process_list 
               ), 
   
        \+  Process_list = [], 

        %%% when there is any process in the agenda, 
        %%% report these processes 

        write('********************************************'),nl,
        write('The content of Process Agenda (Process/ID):'),nl,nl,
        write_list(Process_list),nl,
        write('********************************************'),nl,

        % checking that no conflict processes are to be executed 
        % at the same time !
        % calling process conflict detector in 'conflict.pl'

        % write('* Searching for contradicatory processes...'),nl, 
        % \+ detect_conflicted_processes(Process_list),
        % write('  No contradicatory processes found.'),nl, 

        % choose and execute these processes

        choose_process(Process_list). 

meta_exec_process :-
        write('--- No process found for execution.'), nl, 
        read(_),nl. 


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% choose process for execution from process agenda 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

choose_process([]) :- !. 

choose_process( [(Process, Instance)|Tail] ) :-

       dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
              Begin_time/End_time, Time_cost, 
              (Provider/Provider_type, Receiver/_Receiver_type), 
              P_Trigger, Precond, Action, Postcond))), 

       P = process(Instance, Process/PID, PStatus, Priority, 
              Begin_time/End_time, Time_cost, 
              (Provider/Provider_type, Receiver/Receiver_type), 
              P_Trigger, Precond, Action, Postcond),

        % decide whether to execute the process or not, 
        % this is determined by the user

        nl, 
        write('*****************************************'),nl,
        write('Would you like to execute this process:'),nl,nl,
        write('Process/ProcessID: '), write(Process/PID),nl,
        write('Event ID: '),write(Instance),nl,nl, 
        write('The full process:'),nl, 
        write(P), nl, 
        write('*****************************************'),nl,
        write('Execution? (y/n) '), read(Ans), nl, 

       (\+ Ans = y, choose_process(Tail); 

          Ans = y, 

        write('**********************************'),nl,
        write('     Process Execution Phase      '),nl,       
        write('**********************************'),nl,

        %%% (A) check all triggers are correct, 'top.pl'
        write('*** (1) Checking the trigger ***'),nl, 
        triggering_conditions_satisfied(Process_type, Instance),
        write('* Checking the trigger succeed.'),nl,

        %%% (1) check all preconditions are satisfied, 'cond.pl'
        write('*** (2) Checking preconditions ***'),nl, 
        all_preconditions_satisfied(Process_type, Instance),  
        write('* Checking preconditions succeed.'),nl,

        % (B) execute the process, 'exe.pl'
        write('*** (3) Executing the process ***'),nl,
        execute_process(Process_type, Instance),
        write('* Executing the process succeed.'),nl,

        % (C) all postconditions are satisfied, 'cond.pl'
        %     if an error occurs, it is reported, but 
        %     it normally does not happen.

        write('*** (4) Verifying postconditions ***'),nl,
        all_postconditions_satisfied(Process_type, Instance), 
        write('* Verifying postconditions succeed.'),nl,

        %%% delete process from agenda 

         New = process(Instance, Process/PID, finished, Priority, 
               Begin_time/End_time, Time_cost, 
               (Provider/Provider_type, Receiver/Receiver_type), 
               P_Trigger, Precond, Action, Postcond),

          retract(dyn(agenda(P))), assert(dyn(New)), 

        %%% update the system state status 
        (\+ dyn(flag(state_changed)), 
         assert(flag(state_changed)) 
         ;
         true), 

        %%%%%% update event lifecycle 

          dyn(event_occ(Instance, EE, ES/IDlist, PP, TT, HH, CC)), 

          OldEvent = dyn(event_occ(Instance, EE, ES/IDlist, PP, TT, HH, CC)), 

    	  ( ES = received, NEW_ES = processed; 
            NEW_ES = ES 
          ), 

          append(IDlist, [PID], NewIDlist),
 
          NewEvent = 
          dyn(event_occ(Instance, EE, NEW_ES/NewIDlist, PP, TT, HH, CC )), 
 
          writeln('--- update event lifecycle'),
          writeln(OldEvent),nl, 
          writeln(NewEvent), 

          retract(OldEvent), assert(NewEvent), 

        %%% exec next process 
        choose_process( Tail ), ! 
      ). 

choose_process( [_|Tail] ) :-
  write('*****************************************'),nl,
  write('WARNING: Failed to execute the process, execute the next one.'),nl, 
  write('*****************************************'), nl,nl, 
  choose_process( Tail ). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

triggering_conditions_satisfied(Process, Instance) :- 

   write('>>>> calling triggering_conditions_satisfied '),nl, 

   dyn(agenda(process(Instance, Process/_PID, _PStatus, Priority, 
                      _Begin_time/_End_time, _Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      Trigger, _Precond, _Action, _Postcond))), 

       (Trigger = true ; 

        dyn(event_occ(Instance, _Event, TStatus, Priority, 
                 _Begin/_End,
                (Provider/Provider_type, Receiver/Receiver_type), 
                 _Event_content )), 

        TStatus = Status/_, 

        \+ Status = suspended, \+ Status = cancelled, 
        \+ Status = finished 

       ), !.  %% end ; the or-sign 

triggering_conditions_satisfied(_Process, _Instance) :- 

  write('********************************************'),nl,
  write('Error: the trigger has been suspended, cancelled or ended.'), nl, 
  write('********************************************'),nl,
  nl, fail. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% execute one process, in 'exe.pl'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

execute_process(Process, Instance) :-

       writeln('--- calling execute_process '), 

       dyn(agenda(process(Instance, Process/PID, _PStatus, _Priority, 
              _Begin_time/_End_time, _Time_cost, 
              (_Provider/_Provider_type, _Receiver/_Receiver_type), 
               _P_Trigger, _Precond, Action, _Postcond))), 

       writeln(Process/PID),  
       
       exec_process(Instance/Process, Action). 

%%%%%%%%%%%%%%

exec_process(_, []). 
exec_process(Instance/Process, [X|Action]) :-

        writeln('--- calling exec_process '), 
        writeln(X), 

        exec(Instance/Process, X),

        exec_process(Instance/Process, Action). 

%----------------------------------------------------------------------
% adding new trigger occurrences from the dynamic business model !!

query_for_adding_triggers :-
   
    write('Would you like to add new triggers to the system? (y./n.)'),nl,
    read(Ans),
    (Ans = y, ['/hame/jessicac/kbst-bm/simulator/data/new.db'],
     write('* Finish importing a new trigger.'), nl; 
     \+ Ans = y ). 

%----------------------------------------------------------------------
release_all_inspected_process :-

    dyn(process_inspected(_ )), 
    retract( dyn(process_inspected(_ ))), 
    release_all_inspected_process. 

release_all_inspected_process. 

%-------------------------- Common Predicates-------------------------
% move current state to the next state.

update_state :-
    dyn(current_state(S)),
    read_next_state(ID),

    retract(dyn(current_state(S))),
    assert(dyn(current_state(ID))),

    dyn(state_id(X)),
    retract(dyn(state_id(X))), 
    assert(dyn(state_id(ID))),

    dyn(current_time(Time)),
    assert(dyn(state(ID, time(Time)))).

%-----------------------------------------------------------------------
%% Initial Value. state_id/1 is separated from current_state ID so that
%% state_id always generated with an unique ID (since state ID can be a
%% revisited state number). 

read_next_state(ID1) :-
   dyn(state_id(ID)),
   ID1 is ID + 1.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
look :-
    report_system_state. 

report_system_state :-
    write('------- report dynamic system state -------'),nl,
    dyn(current_state(S)), dyn(state(S, time(T))),
    write('Shown State: '), write(S),
    write('  State Time: '), write(T),
    dyn(current_time(Tc)),
    Tw is Tc -1,
    write('. It is now time: '), write(Tw),nl,nl,
    findall(X, ( dyn(X), portray_clause(dyn(X)),nl), _). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% end of file 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%




